Add get device tokens api

This commit is contained in:
Hongbo Wu
2022-11-21 17:03:12 +08:00
parent 88416bc178
commit f358094bbf
7 changed files with 166 additions and 2 deletions

View File

@ -614,6 +614,23 @@ export type DeviceToken = {
token: Scalars['String'];
};
export type DeviceTokensError = {
__typename?: 'DeviceTokensError';
errorCodes: Array<DeviceTokensErrorCode>;
};
export enum DeviceTokensErrorCode {
BadRequest = 'BAD_REQUEST',
Unauthorized = 'UNAUTHORIZED'
}
export type DeviceTokensResult = DeviceTokensError | DeviceTokensSuccess;
export type DeviceTokensSuccess = {
__typename?: 'DeviceTokensSuccess';
deviceTokens: Array<DeviceToken>;
};
export type Feature = {
__typename?: 'Feature';
createdAt: Scalars['Date'];
@ -1416,6 +1433,7 @@ export type Query = {
article: ArticleResult;
articleSavingRequest: ArticleSavingRequestResult;
articles: ArticlesResult;
deviceTokens: DeviceTokensResult;
feedArticles: FeedArticlesResult;
getFollowers: GetFollowersResult;
getFollowing: GetFollowingResult;
@ -2844,6 +2862,10 @@ export type ResolversTypes = {
DeleteWebhookResult: ResolversTypes['DeleteWebhookError'] | ResolversTypes['DeleteWebhookSuccess'];
DeleteWebhookSuccess: ResolverTypeWrapper<DeleteWebhookSuccess>;
DeviceToken: ResolverTypeWrapper<DeviceToken>;
DeviceTokensError: ResolverTypeWrapper<DeviceTokensError>;
DeviceTokensErrorCode: DeviceTokensErrorCode;
DeviceTokensResult: ResolversTypes['DeviceTokensError'] | ResolversTypes['DeviceTokensSuccess'];
DeviceTokensSuccess: ResolverTypeWrapper<DeviceTokensSuccess>;
Feature: ResolverTypeWrapper<Feature>;
FeedArticle: ResolverTypeWrapper<FeedArticle>;
FeedArticleEdge: ResolverTypeWrapper<FeedArticleEdge>;
@ -3227,6 +3249,9 @@ export type ResolversParentTypes = {
DeleteWebhookResult: ResolversParentTypes['DeleteWebhookError'] | ResolversParentTypes['DeleteWebhookSuccess'];
DeleteWebhookSuccess: DeleteWebhookSuccess;
DeviceToken: DeviceToken;
DeviceTokensError: DeviceTokensError;
DeviceTokensResult: ResolversParentTypes['DeviceTokensError'] | ResolversParentTypes['DeviceTokensSuccess'];
DeviceTokensSuccess: DeviceTokensSuccess;
Feature: Feature;
FeedArticle: FeedArticle;
FeedArticleEdge: FeedArticleEdge;
@ -3888,6 +3913,20 @@ export type DeviceTokenResolvers<ContextType = ResolverContext, ParentType exten
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type DeviceTokensErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['DeviceTokensError'] = ResolversParentTypes['DeviceTokensError']> = {
errorCodes?: Resolver<Array<ResolversTypes['DeviceTokensErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type DeviceTokensResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['DeviceTokensResult'] = ResolversParentTypes['DeviceTokensResult']> = {
__resolveType: TypeResolveFn<'DeviceTokensError' | 'DeviceTokensSuccess', ParentType, ContextType>;
};
export type DeviceTokensSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['DeviceTokensSuccess'] = ResolversParentTypes['DeviceTokensSuccess']> = {
deviceTokens?: Resolver<Array<ResolversTypes['DeviceToken']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type FeatureResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['Feature'] = ResolversParentTypes['Feature']> = {
createdAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
expiresAt?: Resolver<Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
@ -4303,6 +4342,7 @@ export type QueryResolvers<ContextType = ResolverContext, ParentType extends Res
article?: Resolver<ResolversTypes['ArticleResult'], ParentType, ContextType, RequireFields<QueryArticleArgs, 'slug' | 'username'>>;
articleSavingRequest?: Resolver<ResolversTypes['ArticleSavingRequestResult'], ParentType, ContextType, RequireFields<QueryArticleSavingRequestArgs, 'id'>>;
articles?: Resolver<ResolversTypes['ArticlesResult'], ParentType, ContextType, Partial<QueryArticlesArgs>>;
deviceTokens?: Resolver<ResolversTypes['DeviceTokensResult'], ParentType, ContextType>;
feedArticles?: Resolver<ResolversTypes['FeedArticlesResult'], ParentType, ContextType, Partial<QueryFeedArticlesArgs>>;
getFollowers?: Resolver<ResolversTypes['GetFollowersResult'], ParentType, ContextType, Partial<QueryGetFollowersArgs>>;
getFollowing?: Resolver<ResolversTypes['GetFollowingResult'], ParentType, ContextType, Partial<QueryGetFollowingArgs>>;
@ -5127,6 +5167,9 @@ export type Resolvers<ContextType = ResolverContext> = {
DeleteWebhookResult?: DeleteWebhookResultResolvers<ContextType>;
DeleteWebhookSuccess?: DeleteWebhookSuccessResolvers<ContextType>;
DeviceToken?: DeviceTokenResolvers<ContextType>;
DeviceTokensError?: DeviceTokensErrorResolvers<ContextType>;
DeviceTokensResult?: DeviceTokensResultResolvers<ContextType>;
DeviceTokensSuccess?: DeviceTokensSuccessResolvers<ContextType>;
Feature?: FeatureResolvers<ContextType>;
FeedArticle?: FeedArticleResolvers<ContextType>;
FeedArticleEdge?: FeedArticleEdgeResolvers<ContextType>;

View File

@ -540,6 +540,21 @@ type DeviceToken {
token: String!
}
type DeviceTokensError {
errorCodes: [DeviceTokensErrorCode!]!
}
enum DeviceTokensErrorCode {
BAD_REQUEST
UNAUTHORIZED
}
union DeviceTokensResult = DeviceTokensError | DeviceTokensSuccess
type DeviceTokensSuccess {
deviceTokens: [DeviceToken!]!
}
type Feature {
createdAt: Date!
expiresAt: Date
@ -1028,6 +1043,7 @@ type Query {
article(slug: String!, username: String!): ArticleResult!
articleSavingRequest(id: ID!): ArticleSavingRequestResult!
articles(after: String, first: Int, includePending: Boolean, query: String, sharedOnly: Boolean, sort: SortParams): ArticlesResult!
deviceTokens: DeviceTokensResult!
feedArticles(after: String, first: Int, sharedByUser: ID, sort: SortParams): FeedArticlesResult!
getFollowers(userId: ID): GetFollowersResult!
getFollowing(userId: ID): GetFollowingResult!

View File

@ -37,6 +37,7 @@ import {
deleteNewsletterEmailResolver,
deleteReminderResolver,
deleteWebhookResolver,
deviceTokensResolver,
generateApiKeyResolver,
getAllUsersResolver,
getArticleResolver,
@ -204,6 +205,7 @@ export const functionResolvers = {
integrations: integrationsResolver,
recentSearches: recentSearchesResolver,
rules: rulesResolver,
deviceTokens: deviceTokensResolver,
},
User: {
async sharedArticles(
@ -616,4 +618,5 @@ export const functionResolvers = {
...resultResolveTypeResolver('OptInFeature'),
...resultResolveTypeResolver('SetRule'),
...resultResolveTypeResolver('Rules'),
...resultResolveTypeResolver('DeviceTokens'),
}

View File

@ -1,6 +1,9 @@
import { authorized } from '../../utils/helpers'
import {
DeviceToken,
DeviceTokensError,
DeviceTokensErrorCode,
DeviceTokensSuccess,
MutationSetDeviceTokenArgs,
SetDeviceTokenError,
SetDeviceTokenErrorCode,
@ -13,6 +16,7 @@ import {
deleteDeviceToken,
getDeviceToken,
getDeviceTokenByToken,
getDeviceTokensByUserId,
} from '../../services/user_device_tokens'
import { UserDeviceToken } from '../../entity/user_device_tokens'
import { QueryFailedError } from 'typeorm'
@ -122,6 +126,41 @@ export const setDeviceTokenResolver = authorized<
}
})
export const deviceTokensResolver = authorized<
DeviceTokensSuccess,
DeviceTokensError
>(async (_parent, _args, { claims: { uid }, log }) => {
try {
log.info('deviceTokensResolver', {
labels: {
source: 'resolver',
resolver: 'deviceTokensResolver',
uid,
},
})
const deviceTokens = await getDeviceTokensByUserId(uid)
console.log('deviceTokens', deviceTokens)
return {
deviceTokens: deviceTokens.map(deviceTokenToData),
}
} catch (e) {
log.error('Error getting device tokens', {
e,
labels: {
source: 'resolver',
resolver: 'rulesResolver',
uid,
},
})
return {
errorCodes: [DeviceTokensErrorCode.BadRequest],
}
}
})
const deviceTokenToData = (deviceToken: UserDeviceToken): DeviceToken => {
return {
id: deviceToken.id,

View File

@ -2033,6 +2033,21 @@ const schema = gql`
NOT_FOUND
}
union DeviceTokensResult = DeviceTokensSuccess | DeviceTokensError
type DeviceTokensSuccess {
deviceTokens: [DeviceToken!]!
}
type DeviceTokensError {
errorCodes: [DeviceTokensErrorCode!]!
}
enum DeviceTokensErrorCode {
UNAUTHORIZED
BAD_REQUEST
}
# Mutations
type Mutation {
googleLogin(input: GoogleLoginInput!): LoginResult!
@ -2155,6 +2170,7 @@ const schema = gql`
integrations: IntegrationsResult!
recentSearches: RecentSearchesResult!
rules(enabled: Boolean): RulesResult!
deviceTokens: DeviceTokensResult!
}
`

View File

@ -20,7 +20,7 @@ export const getDeviceTokenByToken = async (
export const getDeviceTokensByUserId = async (
userId: string
): Promise<UserDeviceToken[] | undefined> => {
): Promise<UserDeviceToken[]> => {
return getRepository(UserDeviceToken).find({
where: { user: { id: userId } },
})

View File

@ -9,16 +9,19 @@ import { expect } from 'chai'
import { UserDeviceToken } from '../../src/entity/user_device_tokens'
import { SetDeviceTokenErrorCode } from '../../src/generated/graphql'
import 'mocha'
import { User } from '../../src/entity/user'
import { getRepository } from '../../src/entity/utils'
describe('Device tokens API', () => {
const username = 'fakeUser'
let authToken: string
let deviceToken: UserDeviceToken
let user: User
before(async () => {
// create test user and login
const user = await createTestUser(username)
user = await createTestUser(username)
const res = await request
.post('/local/debug/fake-user-login')
.send({ fakeEmail: user.email })
@ -63,6 +66,11 @@ describe('Device tokens API', () => {
`
})
after(async () => {
// clean up
await getRepository(UserDeviceToken).delete({ user: { id: user.id } })
})
context('when id in input is not null', () => {
context('when token exists', () => {
before(() => {
@ -137,4 +145,43 @@ describe('Device tokens API', () => {
return graphqlRequest(query, invalidAuthToken).expect(500)
})
})
describe('Get device tokens', () => {
const token = 'Some token'
const query = `
query {
deviceTokens {
... on DeviceTokensSuccess {
deviceTokens {
id
token
createdAt
}
}
... on DeviceTokensError {
errorCodes
}
}
}
`
before(async () => {
// create test device token
await getRepository(UserDeviceToken).save({
user: { id: user.id },
token,
})
})
after(async () => {
// clean up
await getRepository(UserDeviceToken).delete({ token })
})
it('responds with status code 200 and returns all device tokens', async () => {
const response = await graphqlRequest(query, authToken).expect(200)
expect(response.body.data.deviceTokens.deviceTokens).to.have.lengthOf(1)
})
})
})