Skip to content
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
Empty file added .bashrc
Empty file.
27 changes: 27 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# dependencies
node_modules

# build outputs
/dist
/apps/*/dist

# test/coverage
/coverage

# local env files (keep example.env)
.env
.env.*
!.env.example
!example.env

# logs
*.log

# VCS / editor
.git
.gitignore
.vscode

# OS
.DS_Store
Thumbs.db
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,5 @@ Thumbs.db
.vscode
.postman

infrastructure/params/*.json
infrastructure/params/*.json
.jest-tmp/
38 changes: 38 additions & 0 deletions apps/backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# syntax=docker/dockerfile:1

FROM node:20-bookworm-slim AS builder

WORKDIR /app

RUN corepack enable && corepack prepare yarn@1.22.22 --activate

COPY package.json yarn.lock nx.json tsconfig.base.json jest.preset.js ./

RUN yarn install --frozen-lockfile

COPY . .

RUN yarn nx build backend --configuration=production


FROM node:20-bookworm-slim AS runner

WORKDIR /app

ENV NODE_ENV=production

RUN corepack enable && corepack prepare yarn@1.22.22 --activate

RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl \
&& rm -rf /var/lib/apt/lists/*

COPY package.json yarn.lock ./

RUN yarn install --frozen-lockfile --production=true && yarn cache clean

COPY --from=builder /app/dist/apps/backend ./dist/apps/backend

EXPOSE 3000

CMD ["sh", "-c", "curl -fsSL -o /app/global-bundle.pem https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem && exec node dist/apps/backend/main.js"]
5 changes: 5 additions & 0 deletions apps/backend/src/app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,9 @@ export class AppController {
getData() {
return this.appService.getData();
}

@Get('health')
getHealth() {
return { status: 'ok' };
}
}
10 changes: 10 additions & 0 deletions apps/backend/src/data-source.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DataSource } from 'typeorm';
import fs from 'fs';
import { AdminInfo } from './admin-info/admin-info.entity';
import { User } from './users/user.entity';
import { PluralNamingStrategy } from './strategies/plural-naming.strategy';
Expand All @@ -10,13 +11,22 @@ import { CandidateInfo } from './candidate-info/candidate-info.entity';

dotenv.config();

const sslCaPath = process.env.NX_DB_SSL_CA_PATH;
const sslCa = sslCaPath ? fs.readFileSync(sslCaPath, 'utf8') : undefined;

const AppDataSource = new DataSource({
type: 'postgres',
host: process.env.NX_DB_HOST,
port: parseInt(process.env.NX_DB_PORT as string, 10),
username: process.env.NX_DB_USERNAME,
password: process.env.NX_DB_PASSWORD,
database: process.env.NX_DB_DATABASE,
ssl: sslCa
? {
rejectUnauthorized: true,
ca: sslCa,
}
: undefined,
entities: [
Application,
CandidateInfo,
Expand Down
54 changes: 17 additions & 37 deletions apps/backend/src/util/aws-exports.spec.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,25 @@
type AwsExports = {
AWSConfig: {
accessKeyId?: string;
secretAccessKey?: string;
bucketName?: string;
region?: string;
};
CognitoAuthConfig: {
userPoolId?: string;
clientId?: string;
region?: string;
clientSecret?: string;
};
};

const ORIGINAL_ENV = { ...process.env };

const ENV_KEYS = [
'AWS_ACCESS_KEY_ID',
'NX_AWS_ACCESS_KEY',
'AWS_SECRET_ACCESS_KEY',
'NX_AWS_SECRET_ACCESS_KEY',
'BHCHP_AWS_BUCKET_NAME',
'AWS_BUCKET_NAME',
'BHCHP_AWS_REGION',
'AWS_REGION',
'BHCHP_AWS_SES_SENDER_EMAIL',
'AWS_SES_SENDER_EMAIL',
'COGNITO_APP_CLIENT_ID',
'VITE_COGNITO_APP_CLIENT_ID',
'COGNITO_CLIENT_SECRET',
'COGNITO_USER_POOL_ID',
'VITE_COGNITO_USER_POOL_ID',
'COGNITO_REGION',
Expand Down Expand Up @@ -52,80 +49,63 @@ describe('aws-exports', () => {
});

it('loads config when required primary env vars are present', async () => {
process.env.AWS_ACCESS_KEY_ID = 'aws-key';
process.env.AWS_SECRET_ACCESS_KEY = 'aws-secret';
process.env.AWS_BUCKET_NAME = 'app-bucket';
process.env.BHCHP_AWS_BUCKET_NAME = 'app-bucket';
process.env.BHCHP_AWS_REGION = 'us-west-2';
process.env.COGNITO_APP_CLIENT_ID = 'cognito-client';
process.env.COGNITO_CLIENT_SECRET = 'cognito-secret';
process.env.COGNITO_USER_POOL_ID = 'pool-id';
process.env.COGNITO_REGION = 'us-west-2';

const config = await loadAwsExports();

expect(config.AWSConfig).toEqual({
accessKeyId: 'aws-key',
secretAccessKey: 'aws-secret',
region: 'us-west-2',
bucketName: 'app-bucket',
});
expect(config.CognitoAuthConfig).toEqual({
userPoolId: 'pool-id',
clientId: 'cognito-client',
clientSecret: 'cognito-secret',
});
});

it('uses NX and VITE fallback env vars when primary vars are absent', async () => {
process.env.NX_AWS_ACCESS_KEY = 'nx-aws-key';
process.env.NX_AWS_SECRET_ACCESS_KEY = 'nx-aws-secret';
process.env.AWS_BUCKET_NAME = 'fallback-bucket';
process.env.BHCHP_AWS_BUCKET_NAME = 'fallback-bucket';
process.env.VITE_COGNITO_APP_CLIENT_ID = 'vite-client';
process.env.COGNITO_CLIENT_SECRET = 'cognito-secret';
process.env.VITE_COGNITO_USER_POOL_ID = 'vite-pool';
process.env.VITE_COGNITO_REGION = 'eu-west-1';

const config = await loadAwsExports();

expect(config.AWSConfig).toEqual({
accessKeyId: 'nx-aws-key',
secretAccessKey: 'nx-aws-secret',
region: 'eu-west-1',
bucketName: 'fallback-bucket',
});
expect(config.CognitoAuthConfig).toEqual({
userPoolId: 'vite-pool',
clientId: 'vite-client',
clientSecret: 'cognito-secret',
});
});

it('throws when required AWS env vars are missing', async () => {
await expect(loadAwsExports()).rejects.toThrow(
'The following environmental variables are missing:AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_BUCKET_NAME,COGNITO_REGION',
'The following environmental variables are missing:BHCHP_AWS_BUCKET_NAME,AWS_REGION',
);
});

it('throws when Cognito region vars are missing', async () => {
process.env.AWS_ACCESS_KEY_ID = 'aws-key';
process.env.AWS_SECRET_ACCESS_KEY = 'aws-secret';
process.env.AWS_BUCKET_NAME = 'app-bucket';
it('throws when AWS and Cognito region vars are missing', async () => {
process.env.BHCHP_AWS_BUCKET_NAME = 'app-bucket';
process.env.COGNITO_APP_CLIENT_ID = 'cognito-client';
process.env.COGNITO_CLIENT_SECRET = 'cognito-secret';
process.env.COGNITO_USER_POOL_ID = 'pool-id';

await expect(loadAwsExports()).rejects.toThrow(
'The following environmental variables are missing:COGNITO_REGION',
'The following environmental variables are missing:AWS_REGION',
);
});

it('throws when Cognito app client and client secret are missing', async () => {
process.env.AWS_ACCESS_KEY_ID = 'aws-key';
process.env.AWS_SECRET_ACCESS_KEY = 'aws-secret';
process.env.AWS_BUCKET_NAME = 'app-bucket';
process.env.COGNITO_REGION = 'us-west-2';
it('throws when Cognito app client is missing', async () => {
process.env.BHCHP_AWS_BUCKET_NAME = 'app-bucket';
process.env.AWS_REGION = 'us-west-2';

await expect(loadAwsExports()).rejects.toThrow(
'The following environmental variables are missing:COGNITO_APP_CLIENT_ID,COGNITO_CLIENT_SECRET',
'The following environmental variables are missing:COGNITO_APP_CLIENT_ID',
);
});
});
38 changes: 10 additions & 28 deletions apps/backend/src/util/aws-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,16 @@
*/
function checkAWSSecrets(): void {
const missingVars = [];
if (!process.env.AWS_ACCESS_KEY_ID && !process.env.NX_AWS_ACCESS_KEY) {
missingVars.push('AWS_ACCESS_KEY_ID');
if (!process.env.BHCHP_AWS_BUCKET_NAME && !process.env.AWS_BUCKET_NAME) {
missingVars.push('BHCHP_AWS_BUCKET_NAME');
}
if (
!process.env.AWS_SECRET_ACCESS_KEY &&
!process.env.NX_AWS_SECRET_ACCESS_KEY
!process.env.BHCHP_AWS_REGION &&
!process.env.AWS_REGION &&
!process.env.COGNITO_REGION &&
!process.env.VITE_COGNITO_REGION
) {
missingVars.push('AWS_SECRET_ACCESS_KEY');
}
if (!process.env.AWS_BUCKET_NAME) {
missingVars.push('AWS_BUCKET_NAME');
}
if (!process.env.COGNITO_REGION && !process.env.VITE_COGNITO_REGION) {
missingVars.push('COGNITO_REGION');
missingVars.push('AWS_REGION');
}
if (missingVars.length > 0) {
throw new Error(
Expand All @@ -30,11 +26,10 @@ function checkAWSSecrets(): void {
checkAWSSecrets();

const AWSConfig = {
accessKeyId: process.env.AWS_ACCESS_KEY_ID || process.env.NX_AWS_ACCESS_KEY,
secretAccessKey:
process.env.AWS_SECRET_ACCESS_KEY || process.env.NX_AWS_SECRET_ACCESS_KEY,
bucketName: process.env.AWS_BUCKET_NAME,
bucketName: process.env.BHCHP_AWS_BUCKET_NAME || process.env.AWS_BUCKET_NAME,
region:
process.env.BHCHP_AWS_REGION ||
process.env.AWS_REGION ||
process.env.COGNITO_REGION ||
process.env.VITE_COGNITO_REGION ||
'us-east-2',
Expand All @@ -46,24 +41,12 @@ const AWSConfig = {
*/
function checkAuthSecrets(): void {
const missingVars = [];
if (!process.env.AWS_ACCESS_KEY_ID && !process.env.NX_AWS_ACCESS_KEY) {
missingVars.push('AWS_ACCESS_KEY_ID');
}
if (
!process.env.AWS_SECRET_ACCESS_KEY &&
!process.env.NX_AWS_SECRET_ACCESS_KEY
) {
missingVars.push('AWS_SECRET_ACCESS_KEY');
}
if (
!process.env.COGNITO_APP_CLIENT_ID &&
!process.env.VITE_COGNITO_APP_CLIENT_ID
) {
missingVars.push('COGNITO_APP_CLIENT_ID');
}
if (!process.env.COGNITO_CLIENT_SECRET) {
missingVars.push('COGNITO_CLIENT_SECRET');
}
if (missingVars.length > 0) {
throw new Error(
'The following environmental variables are missing:' +
Expand All @@ -79,7 +62,6 @@ const CognitoAuthConfig = {
process.env.COGNITO_USER_POOL_ID || process.env.VITE_COGNITO_USER_POOL_ID,
clientId:
process.env.COGNITO_APP_CLIENT_ID || process.env.VITE_COGNITO_APP_CLIENT_ID,
clientSecret: process.env.COGNITO_CLIENT_SECRET,
};

const PublicFrontendUrl =
Expand Down
14 changes: 5 additions & 9 deletions apps/backend/src/util/aws-s3/aws-s3.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ jest.mock('../aws-exports', () => ({
__esModule: true,
default: {
AWSConfig: {
accessKeyId: 'test-access-key',
secretAccessKey: 'test-secret-key',
region: 'us-east-2',
bucketName: 'bucket',
},
Expand Down Expand Up @@ -107,7 +105,8 @@ describe('AWSS3Service', () => {
// cleanup uploaded object(s) from S3 using helper
try {
await deleteObjects({
bucketName: process.env.AWS_BUCKET_NAME,
bucketName:
process.env.BHCHP_AWS_BUCKET_NAME || process.env.AWS_BUCKET_NAME,
keys: [uploadedFileName],
});
} catch (cleanupErr) {
Expand All @@ -120,7 +119,8 @@ describe('AWSS3Service', () => {

try {
await getObjectFromS3({
bucketName: process.env.AWS_BUCKET_NAME,
bucketName:
process.env.BHCHP_AWS_BUCKET_NAME || process.env.AWS_BUCKET_NAME,
key: uploadedFileName,
});
} catch (err) {
Expand Down Expand Up @@ -151,11 +151,7 @@ describe('AWSS3Service', () => {
*/
const deleteObjects = async ({ bucketName, keys }) => {
const client = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
region: process.env.BHCHP_AWS_REGION || process.env.AWS_REGION,
});

try {
Expand Down
7 changes: 0 additions & 7 deletions apps/backend/src/util/aws-s3/aws-s3.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,8 @@ export class AWSS3Service {
}

constructor() {
const accessKeyId = envVars.AWSConfig.accessKeyId ?? '';
const secretAccessKey = envVars.AWSConfig.secretAccessKey ?? '';

this.client = new S3Client({
region: this.region,
credentials: {
accessKeyId,
secretAccessKey,
},
});
}

Expand Down
Loading
Loading