Add comments

This commit is contained in:
Alexander Ungar 2023-04-01 12:40:30 +02:00
parent bba723e240
commit 8de0894deb
12 changed files with 76 additions and 2 deletions

View file

@ -143,7 +143,7 @@ components:
spent:
type: number
minimum: 0
description: The summed up spent amount of all transaction of that budget
description: The summed up spent amount of all expenses of that budget
- $ref: "#/components/schemas/NewBudget"
BudgetSummary:
allOf:

View file

@ -14,22 +14,32 @@ import * as path from "path";
const app = express();
// add a winston express logger middleware to log traffic and other events emitted by express
app.use(expressLogger);
// Enable JSON body parsing
app.use(express.json());
// allow parsing of URL encoded payloads
app.use(express.urlencoded({ extended: true }));
// add some additional security on PROD by setting some important HTTP headers automatically
if (environment.isProd) {
app.use(helmet());
}
// validate payloads based on our OpenAPI specification. Fails if something
// does not meet the contract that we have defined and returns a verbose error
// message specifying what exactly is the problem
app.use(
OpenApiValidator.middleware({
apiSpec: path.join(__dirname, "..", "api", "my-finance-pal.yml"),
// validate incoming requests
validateRequests: true,
// also validate our responses to the clients
validateResponses: true,
})
);
// Instantiate dependencies and pass them to the respective components needed for our use cases
const budgetUseCases = BudgetService(
BudgetSummaryMongoRepository(),
BudgetMongoRepository()
@ -37,6 +47,10 @@ const budgetUseCases = BudgetService(
const expenseUseCases = ExpenseService(ExpenseMongoRepository());
app.use(ApiRouter(budgetUseCases, expenseUseCases));
// IMPORTANT! Always add an error handler to avoid unexpected crashes of the app!
// If not caught, every exception will lead to Node.js terminating the process!
// Also place the error handler at the end of your app configuration as this should
// be the last middleware that is called, so we can handle any error that might occur before!
app.use(errorHandler);
export default app;

View file

@ -1,6 +1,9 @@
import { cleanEnv, port, str, url } from "envalid";
import * as process from "process";
// Use cleanenv to read all environment variables and validate them agains our
// typesafe specification. This allows us to fail early on app startup in case
// some of the variables are missing or in the wrong format.
const env = cleanEnv(process.env, {
PORT: port(),
LOG_LEVEL: str({ default: "info" }),

View file

@ -18,4 +18,15 @@ if (!environment.isProd) {
);
}
/*
For production we are still missing a log aggregation stack as well
as some monitoring solution! These two things are imperative when it
comes to creating a production ready state-of-the-art application!
Please have a look at solutions like:
Datadog, Elastic APM, Grafana, Prometheus, InfluxDB and many more.
All cloud providers also offer native solutions for the above, so also
check the pricing and features of these as they are often easily
integrable into cloud native applications.
*/
export default logger;

View file

@ -3,6 +3,8 @@ import logger from "../logging/logger";
import * as util from "util";
import { type NextFunction, type Request, type Response } from "express";
// This whole handling logic is copied from https://github.com/practicajs/practica was and modified to fit our application
let httpServerRef: Http.Server;
export class AppError extends Error {

View file

@ -1,5 +1,6 @@
import { type operations, type paths } from "../../generated/api";
// Typesafe constant holding all REST endpoint paths based on the OpenAPI specification
const apiPaths: Record<keyof operations, keyof paths> = {
createBudget: "/budgets",
getBudgets: "/budgets",

View file

@ -4,6 +4,7 @@ import type BudgetUseCases from "../usecase/budget/budgetUseCases";
import ExpenseRouter from "./expense/expenseRouter";
import type ExpenseUseCases from "../usecase/expense/expenseUseCases";
// Express router bundling all individual routes of our app
const ApiRouter = (
budgetUseCases: BudgetUseCases,
expenseUseCases: ExpenseUseCases

View file

@ -1,4 +1,4 @@
import "./preStart";
import "./preStart"; // always have this at the top of this file in order to execute these scripts first
import * as http from "http";
import app from "./app";
import { listenToErrorEvents } from "./middleware/errorHandler";
@ -10,6 +10,7 @@ void (async () => {
await mongoDb.connect();
})();
// callback function logging out server information
const onListening = (server: http.Server) => (): void => {
const addr = server.address();
const bind =
@ -17,7 +18,9 @@ const onListening = (server: http.Server) => (): void => {
logger.info(`Listening on ${bind}`);
};
// let's first create a server based on our Express application
const server = http.createServer(app);
// then add an error handler for anything uncaught by the app
listenToErrorEvents(server);
server.on("listening", onListening(server));
server.listen(environment.PORT);

View file

@ -6,9 +6,24 @@ import {
} from "../../domain/budget";
interface BudgetUseCases {
/**
* Creates a new budget
* @param newBudget The new budget to be created
*/
createBudget: (newBudget: NewBudget) => Promise<Budget>;
/**
* Gets the summary of a budget including all expenses
* @param budgetId The ID of the budget to fetch
*/
getBudgetSummary: (budgetId: BudgetId) => Promise<BudgetSummary | undefined>;
/**
* Gets a list of all budgets (without expenses)
*/
getBudgets: () => Promise<Budget[]>;
/**
* Deletes a budget including all related expenses
* @param budgetId The ID of the budget to delete
*/
deleteBudget: (budgetId: BudgetId) => Promise<{ deleted: boolean }>;
}

View file

@ -18,6 +18,16 @@ const addNewExpenseToBudget: (
id: new UUID(),
};
const updatedSpent = calculateSpent(existingExpenses, expense);
/*
Here we could also check if updatedSpent > budget.limit, but
we will omit this for now. It might also be a valid use case
that one spends more than they set as their limit
==> clarification with business required
The same goes for a transaction date, that is not within the
boundaries of the budget.
*/
return await insert(budgetId, updatedSpent, expense);
};

View file

@ -2,6 +2,11 @@ import { type BudgetId } from "../../domain/budget";
import { type NewExpense, type Expense } from "../../domain/expense";
interface ExpenseUseCases {
/**
* Adds a new expense to an existing budget
* @param budgetId The ID of the budget for which the expense should be added
* @param newExpense The new expense to be added
*/
addToBudget: (
budgetId: BudgetId,
newExpense: NewExpense

View file

@ -1,9 +1,18 @@
/**
* Parses an ISO-8601 date string into a JS Date object
* @param isoString The date string to be parsed
*/
export const toDate = (isoString?: string): Date | undefined => {
if (isoString !== null && isoString !== undefined) {
return new Date(isoString);
}
};
/**
* Translates a JS Date into an ISO date string
* E.g. 2023-04-01T10:01:57Z => 2023-04-01
* @param date The date to be written in ISO date format
*/
export const toIsoDate = (date?: Date): string | undefined => {
if (date !== null && date !== undefined) {
const isoDateTime = date.toISOString();