Skip to content

Commit cef950d

Browse files
committed
(WiP): fixing multi tenancy
1 parent c13b2b5 commit cef950d

File tree

23 files changed

+253
-285
lines changed

23 files changed

+253
-285
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License
2+
3+
Copyright (c) 2010-2019 Google, Inc. http://angularjs.org
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ ULTIMATE BACKEND
88
<p align="center">
99
</p>
1010

11+
<a href="https://img.shields.io/github/license/juicycleff/ultimate-backend?style=for-the-badge" target="_blank"><img src="https://img.shields.io/github/license/juicycleff/ultimate-backend?style=for-the-badge" alt="License"/></a>
12+
<a href="https://img.shields.io/snyk/vulnerabilities/github/juicycleff/ultimate-backend?style=for-the-badge" target="_blank"><img src="https://img.shields.io/snyk/vulnerabilities/github/juicycleff/ultimate-backend?style=for-the-badge" alt="Snyk"/></a>
13+
1114
## Description
1215
This should be the go to backend base for your next scalable project. It comes with Multi-Tenancy, following different multi-tenancy database strategy as well as different resolver patterns
1316
to identify your tenants. The goal is to give your next big project that extra leap to awesomeness.

apps/gateway-admin/src/app.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { Module } from '@nestjs/common';
1+
import { CacheModule, Module } from '@nestjs/common';
22
import { AppController } from './app.controller';
33
import { AppService } from './app.service';
44
import { GraphqlDistributedGatewayModule } from '@graphqlcqrs/graphql-gateway';
55
import { HeadersDatasource } from './headers.datasource';
66

77
@Module({
88
imports: [
9+
CacheModule.register(),
910
GraphqlDistributedGatewayModule.forRoot({
1011
subscriptions: false,
1112
path: '/graphql',

apps/gateway-admin/src/headers.datasource.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export class HeadersDatasource extends RemoteGraphQLDataSource {
55
willSendRequest({ request, context }) {
66

77
if (context.req) {
8-
if (context.req && context.req.headers) {
8+
if (context.req.headers) {
99
const ctxHeaders = context.req.headers;
1010

1111
for (const key in ctxHeaders) {
@@ -14,10 +14,16 @@ export class HeadersDatasource extends RemoteGraphQLDataSource {
1414
}
1515
}
1616
}
17-
if (context.req && context.req.cookies) {
17+
if (context.req.cookies) {
1818
// tslint:disable-next-line:no-console
1919
console.log('cookies', context.req.cookies);
2020
}
21+
if (context.req.tenantInfo) {
22+
// tslint:disable-next-line:no-console
23+
console.log('tenantInfo start', context.req.tenantInfo);
24+
request.tenantInfo = context.req.tenantInfo;
25+
request.http.headers.set('x-tenant-info', JSON.stringify(context.req.tenantInfo));
26+
}
2127
}
2228
}
2329

apps/gateway-admin/src/main.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ async function bootstrap() {
2121
}));
2222
AppUtils.killAppWithGrace(app);
2323
app.use(cookieParser());
24-
2524
await app.listen(parseInt(process.env.PORT, 10) || 4000);
2625
}
2726
bootstrap();

apps/service-auth/src/auth/auth.resolver.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Args, Context, Mutation, Resolver } from '@nestjs/graphql';
22
import { UseGuards } from '@nestjs/common';
33
import { GqlAuthGuard } from '@graphqlcqrs/common/guards';
44
import { AuthService } from './auth.service';
5+
import { AuthEntity } from '@graphqlcqrs/repository/entities';
56

67
@Resolver('AuthPayload')
78
export class AuthResolver {
@@ -13,10 +14,15 @@ export class AuthResolver {
1314
async login(@Args('input') {identifier, password}: any, @Context() context: any) {
1415
console.log('************************');
1516
console.log(context.req.session);
16-
const gt = await this.authService.create({
17-
name: 'catty',
18-
sound: 'meow',
19-
});
17+
let auth = new AuthEntity();
18+
// @ts-ignore
19+
auth = {
20+
local: {
21+
email: 'rex@gmail.com',
22+
hashedPassword: 'blaaaaab',
23+
},
24+
};
25+
const gt = await this.authService.create(auth);
2026
// console.log(gt);
2127
console.log('************************');
2228
const { user } = await context.authenticate('graphql-local', { email: identifier, password });

apps/service-auth/src/auth/auth.service.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
import { Injectable } from '@nestjs/common';
22
import { AuthRepository } from '@graphqlcqrs/repository/repositories/auth.repository';
3+
import { AuthEntity } from '@graphqlcqrs/repository/entities';
34

45
@Injectable()
56
export class AuthService {
67
constructor(private authRepository: AuthRepository) {}
78

8-
async create(cat: any) {
9-
// @ts-ignore
10-
return await this.authRepository.create({
11-
local: {
12-
email: 'juicycleff@gmail.com',
13-
hashedPassword: 'green',
14-
},
15-
}); // save(cat);
9+
async create(auth: Partial<AuthEntity> | AuthEntity) {
10+
try {
11+
return await this.authRepository.create(auth);
12+
} catch (e) {
13+
console.log(e);
14+
}
1615
}
1716

1817
async validateUser(email: string, pass: string): Promise<any> {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Validator } from 'class-validator';
2+
import * as stringify from 'json-stable-stringify';
3+
import { hash } from './hash.utils';
4+
5+
/**
6+
* @description: Utility functions to support caching operations
7+
* @notes:
8+
* - Uses json-stable-stringify (instead of JSON.Stringify) for determanistic string generation - regardless of parameter ordering
9+
* - Uses custom hash function as significantly faster than cryptogaphic hashes
10+
*/
11+
export class CachingUtils {
12+
private static validator = new Validator();
13+
14+
public static makeCacheKeyFromId(entityId: string): string {
15+
this.validator.isMongoId(entityId);
16+
return this.makeCacheKeyFromProperty(entityId, 'id');
17+
}
18+
19+
public static makeCacheKeyFromProperty(
20+
propertyName: string,
21+
propertyValue: string,
22+
): string {
23+
this.validator.isNotEmpty(propertyValue);
24+
this.validator.isNotEmpty(propertyName);
25+
return `CacheKey-${propertyName}-${propertyValue}`;
26+
}
27+
28+
public static makeCacheKeyFromObject(object: object): string {
29+
return hash(stringify(object)).toString();
30+
}
31+
}

libs/nest-multi-tenant/src/database/mongo/decorators/entity-repository.decorator.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { COLLECTION_KEY, CollectionProps } from '../interfaces';
1+
import { COLLECTION_KEY, CollectionProps, ENTITY_KEY, EntityProps } from '../interfaces';
22

33
/**
44
* Indicate the class represents a collection
@@ -10,3 +10,14 @@ import { COLLECTION_KEY, CollectionProps } from '../interfaces';
1010
export const EntityRepository = (props: CollectionProps) => (target: any) => {
1111
Reflect.defineMetadata(COLLECTION_KEY, props, target.prototype);
1212
};
13+
14+
/**
15+
* Indicate the class represents a collection
16+
*
17+
* @export
18+
* @param {CollectionProps} props
19+
* @returns
20+
*/
21+
export const Entity = (props?: EntityProps) => (target: any) => {
22+
Reflect.defineMetadata(ENTITY_KEY, props, target.prototype);
23+
};

libs/nest-multi-tenant/src/database/mongo/decorators/hooks.decorator.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { POST_KEY, PRE_KEY } from '../interfaces';
2+
import { DataEvents } from '@juicycleff/nest-multi-tenant/enums';
23

34
/**
45
* Run this function before an event occurs
@@ -17,7 +18,7 @@ import { POST_KEY, PRE_KEY } from '../interfaces';
1718
* @param {...string[]} events a list of events
1819
* @returns
1920
*/
20-
export const Before = (...events: string[]) => (target: any, name: string, descriptor: TypedPropertyDescriptor<any>) => {
21+
export const Before = (...events: DataEvents[]) => (target: any, name: string, descriptor: TypedPropertyDescriptor<any>) => {
2122
for (const event of events) {
2223
const fns = Reflect.getMetadata(`${PRE_KEY}_${event}`, target) || [];
2324
// you must create new array so you don't push fn into siblings
@@ -44,7 +45,7 @@ export const Before = (...events: string[]) => (target: any, name: string, descr
4445
* @param {...string[]} events a list of events
4546
* @returns
4647
*/
47-
export const After = (...events: string[]) => (target: any, name: string, descriptor: TypedPropertyDescriptor<any>) => {
48+
export const After = (...events: DataEvents[]) => (target: any, name: string, descriptor: TypedPropertyDescriptor<any>) => {
4849
for (const event of events) {
4950
const fns = Reflect.getMetadata(`${POST_KEY}_${event}`, target) || [];
5051
// you must create new array so you don't push fn into siblings

0 commit comments

Comments
 (0)