Skip to content

Commit 67ec6ef

Browse files
authored
Merge pull request #57 from DouglasNeuroInformatics/dev
fix: ignore unrepresentable types in json schema docs (temporary)
2 parents fd2231f + 4c9ce3f commit 67ec6ef

File tree

15 files changed

+220
-55
lines changed

15 files changed

+220
-55
lines changed

src/app/app.factory.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { GlobalExceptionFilter } from '../filters/global-exception.filter.js';
88
import { JSX_OPTIONS_TOKEN } from '../interceptors/render.interceptor.js';
99
import { ConfigModule } from '../modules/config/config.module.js';
1010
import { CryptoModule } from '../modules/crypto/crypto.module.js';
11+
import { JSXModule } from '../modules/jsx/jsx.module.js';
1112
import { LoggingModule } from '../modules/logging/logging.module.js';
1213
import { PrismaModule } from '../modules/prisma/prisma.module.js';
1314
import { ValidationPipe } from '../pipes/validation.pipe.js';
@@ -106,6 +107,10 @@ export class AppFactory {
106107
});
107108
}
108109

110+
if (jsx) {
111+
coreImports.push(JSXModule);
112+
}
113+
109114
if (envConfig.THROTTLER_ENABLED) {
110115
coreImports.push(
111116
ThrottlerModule.forRoot([

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export type {
2727
UserQueryResult
2828
} from './modules/auth/auth.config.js';
2929
export { AuthModule } from './modules/auth/auth.module.js';
30+
export { TokenService } from './modules/auth/token.service.js';
3031
export { ConfigService } from './modules/config/config.service.js';
3132
export type { CryptoOptions } from './modules/crypto/crypto.config.js';
3233
export { CryptoService } from './modules/crypto/crypto.service.js';

src/modules/auth/__tests__/auth.service.spec.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
import { UnauthorizedException } from '@nestjs/common';
2-
import { JwtService } from '@nestjs/jwt';
32
import { Test } from '@nestjs/testing';
43
import { beforeEach, describe, expect, it, vi } from 'vitest';
54
import type { Mock } from 'vitest';
65

76
import { MockFactory } from '../../../testing/index.js';
87
import { CryptoService } from '../../crypto/crypto.service.js';
9-
import { LoggingService } from '../../logging/logging.service.js';
108
import { AbilityFactory } from '../ability.factory.js';
119
import { USER_QUERY_TOKEN } from '../auth.config.js';
1210
import { AuthService } from '../auth.service.js';
11+
import { TokenService } from '../token.service.js';
1312

1413
import type { MockedInstance } from '../../../testing/index.js';
1514

1615
describe('AuthService', () => {
1716
let authService: AuthService;
1817
let abilityFactory: MockedInstance<AbilityFactory>;
1918
let cryptoService: MockedInstance<CryptoService>;
20-
let jwtService: MockedInstance<JwtService>;
19+
let tokenService: MockedInstance<TokenService>;
2120

2221
let userQuery: Mock;
2322

@@ -32,15 +31,13 @@ describe('AuthService', () => {
3231
AuthService,
3332
MockFactory.createForService(AbilityFactory),
3433
MockFactory.createForService(CryptoService),
35-
MockFactory.createForService(JwtService),
36-
MockFactory.createForService(LoggingService)
34+
MockFactory.createForService(TokenService)
3735
]
3836
}).compile();
3937
abilityFactory = moduleRef.get(AbilityFactory);
4038
authService = moduleRef.get(AuthService);
4139
cryptoService = moduleRef.get(CryptoService);
42-
jwtService = moduleRef.get(JwtService);
43-
40+
tokenService = moduleRef.get(TokenService);
4441
abilityFactory.createForPayload.mockReturnValue([]);
4542
});
4643

@@ -78,7 +75,7 @@ describe('AuthService', () => {
7875
cryptoService.comparePassword.mockImplementationOnce((password, hashedPassword) => {
7976
return password === loginRequest.password && hashedPassword === 'HASHED_PASSWORD';
8077
});
81-
jwtService.signAsync.mockResolvedValueOnce('ACCESS_TOKEN');
78+
tokenService.signToken.mockResolvedValueOnce('ACCESS_TOKEN');
8279
await expect(authService.login(loginRequest)).resolves.toStrictEqual({
8380
accessToken: 'ACCESS_TOKEN'
8481
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { JwtModule } from '@nestjs/jwt';
2+
import { Test } from '@nestjs/testing';
3+
import { beforeAll, describe, expect, it } from 'vitest';
4+
5+
import { MockFactory } from '../../../testing/index.js';
6+
import { LoggingService } from '../../logging/logging.service.js';
7+
import { TokenService } from '../token.service.js';
8+
9+
describe('TokenService', () => {
10+
let tokenService: TokenService;
11+
12+
beforeAll(async () => {
13+
const moduleRef = await Test.createTestingModule({
14+
imports: [
15+
JwtModule.register({
16+
secret: '123'
17+
})
18+
],
19+
providers: [MockFactory.createForService(LoggingService), TokenService]
20+
}).compile();
21+
tokenService = moduleRef.get(TokenService);
22+
});
23+
24+
it('should sign and verify tokens', async () => {
25+
const token = await tokenService.signToken({ isAdmin: true });
26+
const payload = await tokenService.verifyToken(token);
27+
expect(payload).toMatchObject({ isAdmin: true });
28+
});
29+
});

src/modules/auth/auth.module.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ import { AuthService } from './auth.service.js';
1717
import { LoginCredentialsDto } from './dto/login-credentials.dto.js';
1818
import { JwtAuthGuard } from './guards/jwt-auth.guard.js';
1919
import { JwtStrategy } from './strategies/jwt.strategy.js';
20+
import { TokenService } from './token.service.js';
2021

2122
import type { AuthModuleOptions, BaseLoginCredentialsSchema } from './auth.config.js';
2223

2324
@Module({
2425
controllers: [AuthController],
26+
exports: [TokenService],
2527
imports: [
2628
JwtModule.registerAsync({
2729
inject: [ConfigService],
@@ -34,6 +36,7 @@ import type { AuthModuleOptions, BaseLoginCredentialsSchema } from './auth.confi
3436
AbilityFactory,
3537
AuthService,
3638
JwtStrategy,
39+
TokenService,
3740
{
3841
provide: APP_GUARD,
3942
useClass: JwtAuthGuard

src/modules/auth/auth.service.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
2-
import { JwtService } from '@nestjs/jwt';
32

43
import { CryptoService } from '../crypto/crypto.service.js';
5-
import { LoggingService } from '../logging/logging.service.js';
64
import { AbilityFactory } from './ability.factory.js';
75
import { USER_QUERY_TOKEN } from './auth.config.js';
6+
import { TokenService } from './token.service.js';
87

98
import type { UserTypes } from '../../user-config.js';
109
import type { BaseLoginCredentials, LoginResponseBody, UserQuery } from './auth.config.js';
@@ -15,8 +14,7 @@ export class AuthService {
1514
@Inject(USER_QUERY_TOKEN) private readonly userQuery: UserQuery,
1615
private readonly abilityFactory: AbilityFactory,
1716
private readonly cryptoService: CryptoService,
18-
private readonly jwtService: JwtService,
19-
private readonly loggingService: LoggingService
17+
private readonly tokenService: TokenService
2018
) {}
2119

2220
async login(credentials: BaseLoginCredentials): Promise<LoginResponseBody> {
@@ -31,16 +29,16 @@ export class AuthService {
3129

3230
const ability = this.abilityFactory.createForPayload(user.tokenPayload, user.metadata);
3331

34-
return { accessToken: await this.signToken({ ...user.tokenPayload, permissions: ability.rules }) };
35-
}
36-
37-
private async signToken(payload: UserTypes.JwtPayload): Promise<string> {
38-
this.loggingService.verbose({
39-
message: 'Signing JWT',
40-
payload
41-
});
42-
return this.jwtService.signAsync(payload, {
43-
expiresIn: '1d'
44-
});
32+
return {
33+
accessToken: await this.tokenService.signToken(
34+
{
35+
...user.tokenPayload,
36+
permissions: ability.rules
37+
} satisfies UserTypes.JwtPayload,
38+
{
39+
expiresIn: '1d'
40+
}
41+
)
42+
};
4543
}
4644
}

src/modules/auth/token.service.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { JwtService } from '@nestjs/jwt';
3+
import type { JwtSignOptions, JwtVerifyOptions } from '@nestjs/jwt';
4+
5+
import { LoggingService } from '../logging/logging.service.js';
6+
7+
@Injectable()
8+
export class TokenService {
9+
constructor(
10+
private readonly jwtService: JwtService,
11+
private readonly loggingService: LoggingService
12+
) {}
13+
async signToken(payload: { [key: string]: unknown }, options?: JwtSignOptions): Promise<string> {
14+
this.loggingService.verbose({
15+
message: 'Signing JWT',
16+
payload
17+
});
18+
return this.jwtService.signAsync(payload, options);
19+
}
20+
21+
async verifyToken(token: string, options?: JwtVerifyOptions): Promise<unknown> {
22+
this.loggingService.verbose({
23+
message: 'Verifying JWT'
24+
});
25+
return this.jwtService.verifyAsync(token, options) as Promise<unknown>;
26+
}
27+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Test } from '@nestjs/testing';
2+
import { beforeAll, describe, expect, it } from 'vitest';
3+
4+
import { JSXModule } from '../jsx.module.js';
5+
import { JSXService } from '../jsx.service.js';
6+
7+
describe('JSXModule', () => {
8+
let jsxService: JSXService;
9+
10+
beforeAll(async () => {
11+
const moduleRef = await Test.createTestingModule({
12+
imports: [JSXModule]
13+
}).compile();
14+
jsxService = moduleRef.get(JSXService);
15+
});
16+
17+
describe('JSXService', () => {
18+
describe('renderToString', () => {
19+
it('should render JSX to a string', () => {
20+
expect(jsxService.renderToString(<h1>Hello World</h1>)).toBe('<h1>Hello World</h1>');
21+
});
22+
});
23+
});
24+
});

src/modules/jsx/jsx.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { defineToken } from '../../utils/token.utils.js';
2+
3+
export const { REACT_DOM_SERVER_TOKEN } = defineToken('REACT_DOM_SERVER_TOKEN');
4+
5+
export const { JSX_MODULE_OPTIONS_TOKEN } = defineToken('JSX_MODULE_OPTIONS_TOKEN');

src/modules/jsx/jsx.module.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Global, Module } from '@nestjs/common';
2+
3+
import { REACT_DOM_SERVER_TOKEN } from './jsx.config.js';
4+
import { JSXService } from './jsx.service.js';
5+
6+
@Global()
7+
@Module({
8+
exports: [JSXService],
9+
providers: [
10+
{
11+
provide: REACT_DOM_SERVER_TOKEN,
12+
useFactory: async (): Promise<typeof import('react-dom/server')> => {
13+
return import('react-dom/server');
14+
}
15+
},
16+
JSXService
17+
]
18+
})
19+
export class JSXModule {}

0 commit comments

Comments
 (0)