mirror of
https://github.com/netlight/my-finance-pal-backend.git
synced 2024-11-09 16:41:56 +01:00
Add comments
This commit is contained in:
parent
bba723e240
commit
8de0894deb
12 changed files with 76 additions and 2 deletions
|
@ -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:
|
||||
|
|
14
src/app.ts
14
src/app.ts
|
@ -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;
|
||||
|
|
|
@ -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" }),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 }>;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue