Skip to content

Commit

Permalink
Initial project setup for v2
Browse files Browse the repository at this point in the history
  • Loading branch information
BradyMitch committed Oct 9, 2024
1 parent 81a2eda commit 470e45f
Show file tree
Hide file tree
Showing 48 changed files with 1,208 additions and 14 deletions.
15 changes: 15 additions & 0 deletions .env-template
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ENVIRONMENT=dev

NODE_IMAGE_TAG=22-bullseye-slim
MONGO_IMAGE_TAG=8.0
RABBITMQ_IMAGE_TAG=4.0-alpine

BACKEND_PORT=3200
MONGO_EXTERNAL_PORT=3300
RABBITMQ_EXTERNAL_PORT=3400

RABBITMQ_DEFAULT_USER=admin
RABBITMQ_DEFAULT_PASS=pass
MONGO_USER=admin
MONGO_PASSWORD=password
MONGO_DATABASE_NAME=dats
18 changes: 18 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## 🎯 Summary

<!-- COMPLETE JIRA LINK BELOW -->
[DAP-](https://citz-cirmo.atlassian.net/browse/DAP-)

<!-- PROVIDE BELOW an explanation of your changes and any supporting images -->


## 🔰 Checklist

- [ ] I have read and agree with the following checklist.

> - I have performed a self-review of my code.
> - I have commented my code, particularly in hard-to-understand areas.
> - I have made corresponding changes to the documentation where required.
> - I have tested my changes to the best of my ability.
> - I have consulted with the team if introducing a new dependency.
> - My changes generate no new warnings.
4 changes: 2 additions & 2 deletions .github/labeler.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
- .github/workflows/*

'API':
- server/**/*
- backend/**/*

'APP':
- client/**/*
- desktop/**/*
3 changes: 3 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["biomejs.biome"]
}
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true
}
36 changes: 34 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,38 @@

[![Lifecycle:Experimental](https://img.shields.io/badge/Lifecycle-Experimental-339999)](Redirect-URL)

The DATS project will be transfering inactive Full Retention (FR) government digital records and metadata to be archived, according to approved Information Schedules.
The DATS project will be transferring inactive Full Retention (FR) government digital records and metadata to be archived, according to approved Information Schedules.

TBD
<br />

## Quick Start

1. Set up the `.env` based on the `.env.template` file.

2. Run `npm run up` to start the API, MongoDB, and RabbitMQ services.

3. Change directory to `desktop` and run `npm run dev` to launch the desktop app.

<br />

## Architecture Diagram

```mermaid
graph TD
%% Define the architecture components
subgraph Client["Client"]
style Client fill:#D0E8F2,stroke:#3B82F6,stroke-width:2px
A1[Electron React App]
end
subgraph OpenShift["OpenShift Environment"]
style OpenShift fill:#F3F4F6,stroke:#6B7280,stroke-width:2px
direction TB
A1 -.->|Must be on VPN or BC Gov Network| B[Express API]
B -->|Queue Requests| C[RabbitMQ]
B -->|Store Metadata| D[MongoDB]
B -->|Store Files| E[S3 Bucket]
end
%% Define external connections
C -->|Process Queue| B
6 changes: 6 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package-lock.json
node_modules/
build/
dist/
coverage/
tests/
1 change: 1 addition & 0 deletions backend/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
save-exact=true
46 changes: 46 additions & 0 deletions backend/biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.3/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false,
"ignore": ["node_modules", "build", "coverage"]
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
"indentWidth": 2,
"lineWidth": 100,
"lineEnding": "lf"
},
"organizeImports": {
"enabled": false
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": {
"noConsole": "off"
}
}
},
"javascript": {
"formatter": {
"arrowParentheses": "always",
"bracketSameLine": false,
"bracketSpacing": true,
"jsxQuoteStyle": "double",
"semicolons": "always",
"trailingCommas": "all"
}
},
"json": {
"formatter": {
"trailingCommas": "none"
}
}
}
16 changes: 16 additions & 0 deletions backend/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
ARG NODE_IMAGE_TAG=22-bullseye-slim
ARG BACKEND_PORT=3200

FROM node:${NODE_IMAGE_TAG}

WORKDIR /app

ENV NODE_ENV=development

COPY . .

RUN npm i

EXPOSE ${BACKEND_PORT}

CMD ["npm", "run", "dev"]
33 changes: 33 additions & 0 deletions backend/docker/Dockerfile.prod
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
ARG NODE_IMAGE_TAG=22-bullseye-slim
ARG BACKEND_PORT=3200

# STAGE 1: Build
FROM node:${NODE_IMAGE_TAG} AS build

WORKDIR /app
COPY . .

RUN apt-get update && \
apt-get install -y curl && \
npm i && \
npm run build

# Stage 2: Prod
FROM node:${NODE_IMAGE_TAG} AS prod

WORKDIR /app
ENV NODE_ENV=production

# Copy only the package.json to install production dependencies
COPY package.json .
RUN npm i

# Copy only the necessary files from the previous stage
COPY --from=build /app/build ./build

RUN mkdir /.npm
RUN chgrp -R 0 /.npm && chmod -R g=u /.npm

EXPOSE ${BACKEND_PORT}

CMD ["node", "./build/index.js"]
20 changes: 20 additions & 0 deletions backend/healthcheck.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import https from "node:https";

const { BACKEND_PORT } = process.env;
const healthUrl = `http://localhost${BACKEND_PORT}/health`;

/**
* Make a request to the health endpoint.
* If it returns a 200 status, exit the script with exitCode 0 (terminated with success).
* If it returns any other status, exit the script with exitCode 1 (terminated with error).
*/
const req = https.request(healthUrl, (res) => {
process.exitCode = res.statusCode === 200 ? 0 : 1;
});

req.on("error", (error) => {
console.error(`Healthcheck failed with error: ${error}`);
process.exit(1);
});

req.end();
6 changes: 6 additions & 0 deletions backend/nodemon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"watch": ["src"],
"ext": "ts",
"ignore": ["tests/**"],
"exec": "tsx ./src/index.ts"
}
29 changes: 29 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "backend",
"license": "Apache-2.0",
"main": "src/index.ts",
"scripts": {
"dev": "nodemon",
"build": "tsc"
},
"dependencies": {
"@bcgov/citz-imb-express-utilities": "1.0.0-beta4",
"cors": "2.8.5",
"express": "4.21.0",
"express-rate-limit": "7.4.1",
"mongoose": "8.7.0",
"zod": "3.23.8"
},
"devDependencies": {
"@biomejs/biome": "1.9.3",
"@types/cors": "2.8.17",
"@types/express": "5.0.0",
"@types/node": "22.7.4",
"@types/supertest": "6.0.2",
"nodemon": "3.1.7",
"supertest": "7.0.0",
"ts-jest": "29.2.5",
"tsx": "4.19.1",
"typescript": "5.6.2"
}
}
10 changes: 10 additions & 0 deletions backend/src/config/cors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Middleware for enabling Cross-Origin Resource Sharing (CORS) on the server.
* @module cors
* @property {string|string[]} origin - The allowed origins for CORS requests.
* @property {boolean} credentials - Whether to allow credentials to be included in CORS requests.
*/
export const CORS_OPTIONS = {
origin: "*",
credentials: true,
};
24 changes: 24 additions & 0 deletions backend/src/config/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Environment variables set in the .env file.
const {
NODE_ENV,
ENVIRONMENT,
BACKEND_URL,
BACKEND_PORT,
MONGO_USER,
MONGO_PASSWORD,
MONGO_DATABASE_NAME,
MONGO_HOST,
} = process.env;

// Exported configuration values.
export default {
PORT: BACKEND_PORT ? Number(BACKEND_PORT) : 3200,
NODE_VERSION: process.version,
NODE_ENV,
ENVIRONMENT,
BACKEND_URL,
MONGO_USER,
MONGO_PASSWORD,
MONGO_DATABASE_NAME,
MONGO_HOST,
};
3 changes: 3 additions & 0 deletions backend/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default as ENV } from "./env";
export * from "./cors";
export * from "./rateLimit";
14 changes: 14 additions & 0 deletions backend/src/config/rateLimit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Middleware for rate-limiting requests on the server.
* @module express-rate-limit
* @property {number} windowMs - The length of the rate-limiting window in milliseconds.
* @property {number} max - The maximum number of requests allowed per window per IP address.
* @property {boolean} headers - Whether to include rate limit info in the `RateLimit-*` headers.
* @property {boolean} legacy - Whether to include rate limit info in the `X-RateLimit-*` headers (deprecated).
*/
export const RATE_LIMIT_OPTIONS = {
windowMs: 2 * 1000, // 2 seconds
max: 100,
standardHeaders: true,
legacyHeaders: false,
};
33 changes: 33 additions & 0 deletions backend/src/express.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
expressUtilitiesMiddleware,
healthModule,
configModule,
} from "@bcgov/citz-imb-express-utilities";
import cors from "cors";
import express from "express";
import rateLimit from "express-rate-limit";
import { CORS_OPTIONS, RATE_LIMIT_OPTIONS } from "./config";
import { ENV } from "./config";

const { ENVIRONMENT } = ENV;

// Define Express App
const app = express();

// Middleware
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
app.use(cors(CORS_OPTIONS));
app.use(rateLimit(RATE_LIMIT_OPTIONS));

// Disabled because it exposes information about the used framework to potential attackers.
app.disable("x-powered-by");

// Add express utils middleware.
app.use(expressUtilitiesMiddleware);

// Routing
healthModule(app); // Route (/health)
configModule(app, { ENVIRONMENT }); // Route (/config)

export default app;
36 changes: 36 additions & 0 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import mongoose from "mongoose";
import app from "./express";
import { serverStartupLogs } from "@bcgov/citz-imb-express-utilities";
import { ENV } from "./config";
import { logs } from "./utils";

const { PORT, MONGO_USER, MONGO_PASSWORD, MONGO_DATABASE_NAME, MONGO_HOST } =
ENV;
const { DATABASE_CONNECTION_SUCCESS, DATABASE_CONNECTION_ERROR } = logs;

if (!(MONGO_HOST && MONGO_DATABASE_NAME && MONGO_USER && MONGO_PASSWORD))
throw new Error(
"One of MONGO_USER, MONGO_PASSWORD, MONGO_DATABASE_NAME, MONGO_HOST env vars is undefined.",
);

// Create the MongoDB connection URI
const mongoUri = `mongodb://${MONGO_USER}:${encodeURIComponent(MONGO_PASSWORD)}@${MONGO_HOST}/${MONGO_DATABASE_NAME}?authSource=admin`;

mongoose
.connect(mongoUri)
.then(() => {
console.log(DATABASE_CONNECTION_SUCCESS);
})
.catch((error) => {
console.error(`${DATABASE_CONNECTION_ERROR}:`, error);
});

app.listen(PORT, () => {
try {
// Log server start information.
serverStartupLogs(PORT);
} catch (error) {
// Log any error that occurs during the server start.
console.error(error);
}
});
1 change: 1 addition & 0 deletions backend/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as logs from "./logs";
4 changes: 4 additions & 0 deletions backend/src/utils/logs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { ANSI_CODES } from "@bcgov/citz-imb-express-utilities";

export const DATABASE_CONNECTION_SUCCESS = `${ANSI_CODES.FOREGROUND.LIME}Database connection and initialization successful.${ANSI_CODES.FORMATTING.RESET}`;
export const DATABASE_CONNECTION_ERROR = `${ANSI_CODES.FOREGROUND.PINK}[ERROR] ${ANSI_CODES.FORMATTING.RESET}${ANSI_CODES.FOREGROUND.RED}Connecting to the database:${ANSI_CODES.FORMATTING.RESET}`;
Loading

0 comments on commit 470e45f

Please sign in to comment.