Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: api gateway setup #1442

Draft
wants to merge 7 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
.firebase/
artifacts/
.env
.local/

# See http://help.github.com/ignore-files/ for more about ignoring files.

Expand Down
18 changes: 18 additions & 0 deletions apps/api-gateway/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
16 changes: 16 additions & 0 deletions apps/api-gateway/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* eslint-disable */
export default {
displayName: 'api-gateway',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/apps/api-gateway',
};
77 changes: 77 additions & 0 deletions apps/api-gateway/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"name": "api-gateway",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/api-gateway/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",
"outputs": ["{options.outputPath}"],
"options": {
"target": "node",
"compiler": "tsc",
"outputPath": "dist/apps/api-gateway",
"main": "apps/api-gateway/src/main.ts",
"tsConfig": "apps/api-gateway/tsconfig.app.json",
"assets": ["apps/api-gateway/src/assets"]
},
"configurations": {
"production": {
"optimization": true,
"extractLicenses": true,
"inspect": false,
"fileReplacements": [
{
"replace": "apps/api-gateway/src/environments/environment.ts",
"with": "apps/api-gateway/src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"executor": "@nrwl/js:node",
"options": {
"buildTarget": "api-gateway:build"
},
"configurations": {
"production": {
"buildTarget": "api-gateway:build:production"
}
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/api-gateway/**/*.ts"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["coverage/apps/api-gateway"],
"options": {
"jestConfig": "apps/api-gateway/jest.config.ts",
"passWithNoTests": true
}
},
"dockerize": {
"executor": "nx:run-commands",
"options": {
"command": "docker build -f ./apps/api-gateway/server/Dockerfile . -t krew-api-gateway"
},
"parallel": false
},
"docker-push": {
"executor": "nx:run-commands",
"options": {
"commands": [
"if [ {args.projectId} = \"undefined\" ]; then echo \"provide project arg\"; else docker tag krew-api-gateway gcr.io/{args.projectId}/api-gateway; fi ",
"if [ {args.projectId} = \"undefined\" ]; then echo \"provide project arg\"; else docker push gcr.io/{args.projectId}/api-gateway; fi "
]
},
"parallel": false
}
},
"tags": []
}
13 changes: 13 additions & 0 deletions apps/api-gateway/server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM node:16-slim

ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

WORKDIR /app
COPY package.json ./

RUN npm install -only=production --ignore-scripts

COPY ./dist/apps/api-gateway ./

CMD node ./main.js
23 changes: 23 additions & 0 deletions apps/api-gateway/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { HttpModule } from '@nestjs/axios';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

import { FirebaseAuthStrategy } from './core/strategies/firebase-auth.strategy';
import { AuthController, NewsController } from './controllers';
import { configuration } from './core/config/configuration';
import { validationSchema } from './core/config/validation';
import { AuthService, DataService, GatewaySettingsService, KrewApiService } from './core/services';

@Module({
imports: [
HttpModule,
ConfigModule.forRoot({
isGlobal: true,
load: [configuration],
validationSchema,
}),
],
controllers: [AuthController, NewsController],
providers: [FirebaseAuthStrategy, AuthService, DataService, GatewaySettingsService, KrewApiService],
})
export class AppModule {}
37 changes: 37 additions & 0 deletions apps/api-gateway/src/app/controllers/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Controller, Get, UseGuards } from '@nestjs/common';
import { User } from '../core/decorators';
import { FirebaseAuthGuard } from '../core/guards';
import { AuthService, GatewaySettingsService } from '../core/services';

@Controller('')
export class AuthController {
constructor(
private readonly gatewaySettingsService: GatewaySettingsService,
private readonly authService: AuthService
) {}

@UseGuards(FirebaseAuthGuard)
@Get('me')
async getData(@User() loggedInUser) {
const config = await this.gatewaySettingsService.getRemoteConfig();
console.log('configuration', config);

const USER_ID = null;
const user = await this.authService.getUserByFirebaseUserId(USER_ID);
console.log(user);

return {
...loggedInUser,
...user,
};
}

@Get('account')
async getAccount(@User() loggedInUser) {
const config = await this.gatewaySettingsService.getRemoteConfig();
console.log('configuration', config);

const account = await this.authService.getUserByFirebaseUserId(null);
return account;
}
}
2 changes: 2 additions & 0 deletions apps/api-gateway/src/app/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './auth.controller';
export * from './news.controller';
12 changes: 12 additions & 0 deletions apps/api-gateway/src/app/controllers/news.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Controller, Get } from '@nestjs/common';
import { KrewApiService } from '../core/services';

@Controller('news')
export class NewsController {
constructor(private readonly krewApiService: KrewApiService) {}

@Get('')
async getAll() {
return this.krewApiService.getNews();
}
}
5 changes: 5 additions & 0 deletions apps/api-gateway/src/app/core/config/configuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const configuration = () => ({
environment: process.env.NODE_ENV,
port: parseInt(process.env.PORT || '3000', 10),
apiBaseUrl: process.env.API_URL,
});
7 changes: 7 additions & 0 deletions apps/api-gateway/src/app/core/config/validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as Joi from 'joi';

export const validationSchema = Joi.object({
NODE_ENV: Joi.string().valid('development', 'production', 'test').required(),
PORT: Joi.number().default(3000),
API_URL: Joi.string().required(),
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const User = createParamDecorator((data: unknown, context: ExecutionContext) => {
return context.switchToHttp().getRequest().user;
});
1 change: 1 addition & 0 deletions apps/api-gateway/src/app/core/decorators/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './firebase-user.decorator';
5 changes: 5 additions & 0 deletions apps/api-gateway/src/app/core/guards/firebase-auth.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class FirebaseAuthGuard extends AuthGuard('firebase-auth') {}
1 change: 1 addition & 0 deletions apps/api-gateway/src/app/core/guards/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './firebase-auth.guard';
11 changes: 11 additions & 0 deletions apps/api-gateway/src/app/core/models/user-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// import { Field, ObjectType } from '@nestjs/graphql';
// import { AuthUser } from './user';

// @ObjectType()
// export class UserToken {
// @Field()
// token: string;

// @Field()
// user: AuthUser;
// }
21 changes: 21 additions & 0 deletions apps/api-gateway/src/app/core/models/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// import { Field, ObjectType } from '@nestjs/graphql';

// @ObjectType()
// export class AuthUser {
// @Field({ nullable: true })
// id?: number;

// @Field({ nullable: true })
// email?: string;

// @Field({ nullable: true })
// firstName?: string;

// @Field({ nullable: true })
// lastName?: string;

// @Field({ nullable: true })
// picture?: string;

// password?: string;
// }
39 changes: 39 additions & 0 deletions apps/api-gateway/src/app/core/services/auth.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { DataService } from '.';

const ACCOUNTS_COLLECTION = 'accounts';

@Injectable()
export class AuthService {
constructor(private readonly data: DataService) {}

async getUserByFirebaseUserId(firebaseUid: string): Promise<any> {
const account = this.getAccountByFirebaseUserId(firebaseUid);

// const employee = await this.krewApiService.getMe();

return {
// ...employee,
account,
};
}

private async getAccountByFirebaseUserId(uid: string): Promise<any> {
const docRef = this.data.collection(ACCOUNTS_COLLECTION);

const querySnapshot = await docRef.where('firebaseUserId', '==', uid).get();

if (querySnapshot.docs.length === 1) {
return this.firestoreToUser(querySnapshot.docs[0]);
}

throw new NotFoundException();
}

private firestoreToUser(doc: FirebaseFirestore.QueryDocumentSnapshot) {
return {
_id: doc.id,
...doc.data(),
};
}
}
13 changes: 13 additions & 0 deletions apps/api-gateway/src/app/core/services/data.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import * as firebase from 'firebase-admin';

@Injectable()
export class DataService extends firebase.firestore.Firestore implements OnModuleInit {
constructor() {
super();
}

public async onModuleInit() {
Logger.debug('[DataService] Firestore client connected');
}
}
23 changes: 23 additions & 0 deletions apps/api-gateway/src/app/core/services/gateway-settings.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Injectable } from '@nestjs/common';
import { DataService } from './data.service';

const SETTINGS_COLLECTION = 'settings';

interface IRemoteConfig {
apiKey: string;
}

@Injectable()
export class GatewaySettingsService {
constructor(private readonly data: DataService) {}

async getRemoteConfig(): Promise<IRemoteConfig> {
const docRef = this.data.collection(SETTINGS_COLLECTION).doc('config');
const snapshot = await docRef.get();
if (snapshot === null) {
return null;
}

return <IRemoteConfig>snapshot.data();
}
}
4 changes: 4 additions & 0 deletions apps/api-gateway/src/app/core/services/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './auth.service';
export * from './data.service';
export * from './gateway-settings.service';
export * from './krew-api.service';
29 changes: 29 additions & 0 deletions apps/api-gateway/src/app/core/services/krew-api.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Injectable } from '@nestjs/common';
import { GatewaySettingsService } from '.';
import { createKrewClient, Employee, IKrewClient, NewsItem } from '@krew/api-client';

@Injectable()
export class KrewApiService {
private krewClient: IKrewClient;

constructor(private readonly gatewaySettingsService: GatewaySettingsService) {
this.krewClient = createKrewClient({ sandbox: false, storageMode: 'MEMORY' });
}

async getMe(): Promise<Employee> {
this.krewClient.me().setAccessToken(await this.getApiKey());

return this.krewClient.me().get();
}

async getNews(): Promise<NewsItem[]> {
this.krewClient.news().setAccessToken(await this.getApiKey());

return this.krewClient.news().all();
}

private async getApiKey() {
const config = await this.gatewaySettingsService.getRemoteConfig();
return config.apiKey;
}
}
Loading