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

[TM-1272] v3 FE integration #3

Merged
merged 5 commits into from
Sep 27, 2024
Merged
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
2 changes: 1 addition & 1 deletion .env.local.sample
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
NODE_ENV=development
PHP_PROXY_TARGET=http://host.docker.internal:8080/api
PHP_PROXY_TARGET=http://host.docker.internal:8080
USER_SERVICE_PROXY_TARGET=http://host.docker.internal:4010

DB_HOST=localhost
Expand Down
13 changes: 6 additions & 7 deletions apps/api-gateway/lambda/local-proxy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {APIGatewayProxyEventV2, APIGatewayProxyResultV2} from 'aws-lambda'

export async function main(event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResultV2> {
const query = event.rawQueryString === '' ? '' : `?${event.rawQueryString}`;
const url = `${process.env.PROXY_TARGET}/${event.pathParameters!.proxy}${query}`;
const url = `${process.env.PROXY_TARGET}${event.rawPath}${query}`;
const requestHeaders = new Headers();
for (const header in event.headers) {
requestHeaders.append(header, event.headers[header]!);
Expand All @@ -18,15 +18,14 @@ export async function main(event: APIGatewayProxyEventV2): Promise<APIGatewayPro

const result = await fetch(url, requestInit);

const responseHeaders: { [p: string]: string | number | boolean } = {};
for (const header in result.headers.keys) {
responseHeaders[header] = result.headers.get(header)!;
}

return {
body: await result.text(),
statusCode: result.status,
isBase64Encoded: false,
headers: responseHeaders,
headers: {
'Cache-Control': result.headers.get('Cache-Control') ?? 'no-cache',
'Content-Type': result.headers.get('Content-Type') ?? 'application/json',
},
cookies: result.headers.getSetCookie()
}
}
57 changes: 35 additions & 22 deletions apps/api-gateway/lib/api-gateway-stack.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import * as cdk from 'aws-cdk-lib';
import {Construct} from 'constructs';
import {CorsHttpMethod, HttpApi, HttpMethod} from 'aws-cdk-lib/aws-apigatewayv2'
import {HttpLambdaIntegration, HttpUrlIntegration} from 'aws-cdk-lib/aws-apigatewayv2-integrations'
import {NodejsFunction} from 'aws-cdk-lib/aws-lambda-nodejs'
import {Architecture, Runtime} from 'aws-cdk-lib/aws-lambda'
import {RetentionDays} from 'aws-cdk-lib/aws-logs'
import { Construct } from 'constructs';
import { CorsHttpMethod, HttpApi, HttpMethod } from 'aws-cdk-lib/aws-apigatewayv2';
import {
HttpLambdaIntegration,
HttpUrlIntegration
} from 'aws-cdk-lib/aws-apigatewayv2-integrations';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { Architecture, Runtime } from 'aws-cdk-lib/aws-lambda';
import { RetentionDays } from 'aws-cdk-lib/aws-logs';

// References the .env in the root of this repo, so building from this directory will not find
// the file correctly. Instead, use `nx build api-gateway` in the root directory.
require('dotenv').config();

const V3_SERVICES = {
'user-service': {
target: process.env.USER_SERVICE_PROXY_TARGET ?? '',
namespaces: ['auth']
}
}

export class ApiGatewayStack extends cdk.Stack {
protected httpApi: HttpApi;

Expand All @@ -32,25 +42,28 @@ export class ApiGatewayStack extends cdk.Stack {
}
});

this.addProxy(
'User Service',
'/user-service/{proxy+}',
process.env.USER_SERVICE_PROXY_TARGET ?? '',
'/{proxy}'
);
this.addProxy(
'PHP Monolith',
'/api/{proxy+}',
process.env.PHP_PROXY_TARGET ?? '',
'/api/{proxy}'
);
for (const [service, { target, namespaces }] of Object.entries(V3_SERVICES)) {
this.addProxy(`API Swagger Docs [${service}]`, `/${service}/documentation/`, target);

for (const namespace of namespaces) {
this.addProxy(`V3 Namespace [${service}/${namespace}]`, `/${namespace}/v3/`, target);
}
}

// The PHP Monolith proxy keeps `/api/` in its path to avoid conflict with the newer
// namespace-driven design of the v3 API space, and to minimize disruption with existing
// consumers (like Greenhouse and the web TM frontend) as we migrate to this API Gateway.
this.addProxy('PHP Monolith', '/api/', process.env.PHP_PROXY_TARGET ?? '');
this.addProxy('PHP OpenAPI Docs', '/documentation/', process.env.PHP_PROXY_TARGET ?? '')
}

protected addProxy (name: string, path: string, targetHost: string, targetPath: string) {
protected addProxy (name: string, path: string, targetHost: string) {
const sourcePath = `${path}{proxy+}`;
if (process.env.NODE_ENV === 'development') {
this.addLocalLambdaProxy(name, path, targetHost);
this.addLocalLambdaProxy(name, sourcePath, targetHost);
} else {
this.addHttpUrlProxy(name, path, `${targetHost}${targetPath}`);
const targetPath = `${path}{proxy}`;
this.addHttpUrlProxy(name, sourcePath, `${targetHost}${targetPath}`);
}
}

Expand Down Expand Up @@ -88,6 +101,6 @@ export class ApiGatewayStack extends cdk.Stack {
path: sourcePath,
methods: [HttpMethod.ANY],
integration: new HttpUrlIntegration(name, targetUrl),
})
});
}
}
6 changes: 3 additions & 3 deletions apps/user-service/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import { LoginResponse } from './dto/login-response.dto';
import { ApiException } from '@nanogiants/nestjs-swagger-api-exception-decorator';
import { ApiOperation } from '@nestjs/swagger';

@Controller('auth')
@Controller('auth/v3')
export class AuthController {
constructor (private readonly authService: AuthService) {}

@Post('login')
@ApiOperation({ summary: 'Receive a JWT Token in exchange for login credentials' })
@Post('logins')
@ApiOperation({ operationId: 'authLogin', description: 'Receive a JWT Token in exchange for login credentials' })
@JsonApiResponse({ status: HttpStatus.CREATED, dataType: LoginResponse })
@ApiException(
() => UnauthorizedException,
Expand Down
2 changes: 1 addition & 1 deletion apps/user-service/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ async function bootstrap() {
.addTag('user-service')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
SwaggerModule.setup('user-service/documentation/api', app, document);

app.useGlobalPipes(new ValidationPipe());
app.useGlobalInterceptors(new TransformInterceptor());
Expand Down