-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from BrewInteractive/feature/create-hasura-service
Feature/create hasura service
- Loading branch information
Showing
14 changed files
with
1,047 additions
and
915 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,14 @@ | ||
{ | ||
"sonarlint.connectedMode.project": { | ||
"connectionId": "brewinteractive", | ||
"projectKey": "BrewInteractive_nestjs-hasura-module" | ||
}, | ||
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint", | ||
"editor.formatOnType": false, // required | ||
"editor.formatOnPaste": true, // optional | ||
"editor.formatOnSave": true, // optional | ||
"editor.formatOnSaveMode": "file", // required to format on save | ||
"files.autoSave": "onFocusChange", // optional but recommended | ||
"vs-code-prettier-eslint.prettierLast": false // set as "true" to run 'prettier' last not first | ||
} | ||
"sonarlint.connectedMode.project": { | ||
"connectionId": "brewinteractive", | ||
"projectKey": "BrewInteractive_nestjs-hasura-module" | ||
}, | ||
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint", | ||
"editor.formatOnType": false, // required | ||
"editor.formatOnPaste": true, // optional | ||
"editor.formatOnSave": true, // optional | ||
"editor.formatOnSaveMode": "file", // required to format on save | ||
"files.autoSave": "onFocusChange", // optional but recommended | ||
"vs-code-prettier-eslint.prettierLast": false, | ||
"cSpell.words": ["Hasura"] // set as "true" to run 'prettier' last not first | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,10 +21,12 @@ | |
"@nestjs/common": "^10.0.0", | ||
"@nestjs/core": "^10.0.0", | ||
"reflect-metadata": "^0.2.0", | ||
"rxjs": "^7.8.1" | ||
"rxjs": "^7.8.1", | ||
"graphql": "^16.8.1" | ||
}, | ||
"devDependencies": { | ||
"@nestjs/cli": "^10.0.0", | ||
"graphql": "^16.8.1", | ||
"@nestjs/common": "^10.0.0", | ||
"@nestjs/core": "^10.0.0", | ||
"@nestjs/schematics": "^10.0.0", | ||
|
@@ -82,7 +84,7 @@ | |
}, | ||
"homepage": "https://github.com/BrewInteractive/nestjs-hasura#readme-module", | ||
"dependencies": { | ||
"graphql-request": "^7.0.1" | ||
"graphql-request": "^6.1.0" | ||
}, | ||
"packageManager": "[email protected]+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,157 @@ | ||
import { AuthorizationOptions, HasuraConfig, RequestFlags } from './models'; | ||
import { Faker, MockFactory } from 'mockingbird'; | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
|
||
import { HasuraConfigFixture } from '../test/fixtures'; | ||
import { HasuraService } from './hasura.service'; | ||
import { gql } from 'graphql-request'; | ||
|
||
const graphqlClientSpy = jest.fn(); | ||
|
||
jest.mock('graphql-request', () => { | ||
return { | ||
GraphQLClient: jest.fn().mockImplementation(() => ({ | ||
request: graphqlClientSpy, | ||
})), | ||
gql: jest.requireActual('graphql-request').gql, | ||
}; | ||
}); | ||
|
||
describe('HasuraService', () => { | ||
let service: HasuraService; | ||
let hasuraService: HasuraService; | ||
const hasuraConfig = MockFactory(HasuraConfigFixture).one(); | ||
|
||
beforeEach(async () => { | ||
const module: TestingModule = await Test.createTestingModule({ | ||
providers: [HasuraService], | ||
providers: [ | ||
HasuraService, | ||
{ | ||
provide: HasuraConfig, | ||
useValue: hasuraConfig, | ||
}, | ||
], | ||
}).compile(); | ||
|
||
service = module.get<HasuraService>(HasuraService); | ||
hasuraService = module.get<HasuraService>(HasuraService); | ||
}); | ||
|
||
it('should be defined', () => { | ||
expect(service).toBeDefined(); | ||
it('should be defined', async () => { | ||
expect(hasuraService).toBeDefined(); | ||
}); | ||
|
||
it('should run the query without the variables.', async () => { | ||
const query = gql` | ||
query testQuery { | ||
books { | ||
id | ||
} | ||
} | ||
`; | ||
|
||
const expectedResult = [{ id: Faker.datatype.number() }]; | ||
graphqlClientSpy.mockResolvedValue(expectedResult); | ||
|
||
const actualResult = await hasuraService.requestAsync({ query }); | ||
|
||
expect(actualResult).toBe(expectedResult); | ||
expect(graphqlClientSpy).toHaveBeenCalledWith(query, undefined, {}); | ||
}); | ||
|
||
it('should run the query with the variables.', async () => { | ||
const variables = { | ||
id: Faker.datatype.number(), | ||
}; | ||
const query = gql` | ||
query testQuery($id: Int!) { | ||
books_by_id(id: $id) { | ||
id | ||
} | ||
} | ||
`; | ||
|
||
const expectedResult = { id: Faker.datatype.number() }; | ||
graphqlClientSpy.mockResolvedValue(expectedResult); | ||
|
||
const actualResult = await hasuraService.requestAsync({ | ||
query, | ||
variables, | ||
}); | ||
|
||
expect(actualResult).toBe(expectedResult); | ||
expect(graphqlClientSpy).toHaveBeenCalledWith(query, variables, {}); | ||
}); | ||
|
||
it('should run the query with the runQueryFlag.', async () => { | ||
const query = gql` | ||
query testQuery($id: Int!) { | ||
books_by_id(id: $id) { | ||
id | ||
} | ||
} | ||
`; | ||
const requestFlags: RequestFlags = RequestFlags.UseBackendOnlyPermissions; | ||
|
||
const expectedResult = { id: Faker.datatype.number() }; | ||
graphqlClientSpy.mockResolvedValue(expectedResult); | ||
|
||
const actualResult = await hasuraService.requestAsync({ | ||
query, | ||
requestFlags, | ||
}); | ||
|
||
expect(actualResult).toBe(expectedResult); | ||
expect(graphqlClientSpy).toHaveBeenCalledWith(query, undefined, { | ||
'x-hasura-use-backend-only-permissions': true, | ||
'x-hasura-admin-secret': hasuraConfig.adminSecret, | ||
}); | ||
}); | ||
|
||
it('should run the query with the runQueryOptions.', async () => { | ||
const query = gql` | ||
query testQuery($id: Int!) { | ||
books_by_id(id: $id) { | ||
id | ||
} | ||
} | ||
`; | ||
|
||
const authorizationOptions: AuthorizationOptions = { | ||
role: Faker.datatype.string(), | ||
authorizationToken: Faker.datatype.string(), | ||
}; | ||
|
||
const expectedResult = { id: Faker.datatype.number() }; | ||
graphqlClientSpy.mockResolvedValue(expectedResult); | ||
|
||
const actualResult = await hasuraService.requestAsync({ | ||
query, | ||
authorizationOptions, | ||
}); | ||
|
||
expect(actualResult).toBe(expectedResult); | ||
expect(graphqlClientSpy).toHaveBeenCalledWith(query, undefined, { | ||
'x-hasura-role': authorizationOptions.role, | ||
authorization: authorizationOptions.authorizationToken, | ||
}); | ||
}); | ||
|
||
it('should throw error if UseAdminSecret flag is set without setting admin secret in config.', async () => { | ||
Reflect.set(hasuraService, 'adminSecret', undefined); | ||
const query = gql` | ||
query testQuery { | ||
books { | ||
id | ||
} | ||
} | ||
`; | ||
|
||
const requestFlags: RequestFlags = RequestFlags.UseAdminSecret; | ||
|
||
expect(async () => { | ||
await hasuraService.requestAsync({ | ||
query, | ||
requestFlags, | ||
}); | ||
}).rejects.toThrow(Error); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,64 @@ | ||
import { | ||
AuthorizationOptions, | ||
HasuraConfig, | ||
HasuraHeaders, | ||
HasuraRequest, | ||
RequestFlags, | ||
} from './models'; | ||
import { GraphQLClient, Variables } from 'graphql-request'; | ||
|
||
import { Injectable } from '@nestjs/common'; | ||
|
||
@Injectable() | ||
export class HasuraService {} | ||
export class HasuraService { | ||
private readonly graphQLClient: GraphQLClient; | ||
private readonly adminSecret?: string; | ||
|
||
constructor(private readonly hasuraConfig: HasuraConfig) { | ||
this.graphQLClient = new GraphQLClient(this.hasuraConfig.graphqlEndpoint); | ||
this.adminSecret = this.hasuraConfig?.adminSecret; | ||
} | ||
|
||
requestAsync<T, V extends Variables = Variables>( | ||
hasuraRequest: HasuraRequest<V>, | ||
): Promise<T> { | ||
const headers = { | ||
...(hasuraRequest?.headers || {}), | ||
...this.createHeadersByRunQueryFlags(hasuraRequest?.requestFlags), | ||
...this.createHeadersByAuthorizationOptions( | ||
hasuraRequest?.authorizationOptions || {}, | ||
), | ||
}; | ||
|
||
return this.graphQLClient.request<T>( | ||
hasuraRequest.query, | ||
hasuraRequest.variables, | ||
headers, | ||
); | ||
} | ||
|
||
private getAdminSecret(): string { | ||
if (this.adminSecret) return this.adminSecret; | ||
|
||
throw new Error('Missing admin secret.'); | ||
} | ||
|
||
private createHeadersByRunQueryFlags(flags: RequestFlags) { | ||
const headers = {}; | ||
if ((RequestFlags.UseAdminSecret | flags) == flags) | ||
headers['x-hasura-admin-secret'] = this.getAdminSecret(); | ||
|
||
if ((RequestFlags.UseAdminSecret | flags) == flags) | ||
headers['x-hasura-use-backend-only-permissions'] = true; | ||
|
||
return headers; | ||
} | ||
|
||
private createHeadersByAuthorizationOptions(options: AuthorizationOptions) { | ||
return Object.entries(options).reduce((acc, [key, value]) => { | ||
const headerKey = HasuraHeaders[key]; | ||
if (headerKey) acc[headerKey] = value; | ||
return acc; | ||
}, {}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,11 @@ | ||
import { HasuraModule, HasuraService } from './index'; | ||
|
||
describe('HasuraTest', () => { | ||
it('Should export HasuraModule', () => { | ||
it('should export HasuraModule', () => { | ||
expect(HasuraModule).toBeDefined(); | ||
}); | ||
|
||
it('Should export HasuraService', () => { | ||
it('should export HasuraService', () => { | ||
expect(HasuraService).toBeDefined(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from './hasura.module'; | ||
export * from './hasura.service'; | ||
export * from './models'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export class AuthorizationOptions { | ||
authorizationToken?: string; | ||
role?: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
export class HasuraConfig { | ||
graphqlEndpoint: string; | ||
adminSecret: string; | ||
adminSecret?: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export const HasuraHeaders: Record<string, string> = { | ||
authorizationToken: 'authorization', | ||
role: 'x-hasura-role', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { AuthorizationOptions } from './authorization-options'; | ||
import { RequestFlags } from './request-flags'; | ||
|
||
export class HasuraRequest<V = unknown> { | ||
query: string; | ||
variables?: V; | ||
headers?: Record<string, string>; | ||
requestFlags?: RequestFlags; | ||
authorizationOptions?: AuthorizationOptions; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,6 @@ | ||
export * from './hasura-config'; | ||
export * from './hasura-async-config'; | ||
export * from './hasura-request'; | ||
export * from './request-flags'; | ||
export * from './authorization-options'; | ||
export * from './hasura-headers'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export enum RequestFlags { | ||
UseAdminSecret = 1, // 0001 | ||
UseBackendOnlyPermissions = 3, // 0011 | ||
} |
Oops, something went wrong.