Add API validator middleware

This commit is contained in:
Alexander Ungar 2023-03-21 23:28:41 +01:00
parent 4d72d8035f
commit 06e3fafe15
7 changed files with 31 additions and 11 deletions

View file

@ -256,7 +256,7 @@ components:
example: 23.94 example: 23.94
date: date:
type: string type: string
format: date format: date-time
NewTransaction: NewTransaction:
type: object type: object
description: A new transaction to be created description: A new transaction to be created
@ -275,4 +275,4 @@ components:
example: 23.94 example: 23.94
date: date:
type: string type: string
format: date format: date-time

View file

@ -26,6 +26,7 @@
"express": "^4.18.2", "express": "^4.18.2",
"express-async-errors": "^3.1.1", "express-async-errors": "^3.1.1",
"express-async-handler": "^1.2.0", "express-async-handler": "^1.2.0",
"express-openapi-validator": "^5.0.3",
"express-winston": "^4.2.0", "express-winston": "^4.2.0",
"helmet": "^6.0.1", "helmet": "^6.0.1",
"http-status-codes": "^2.2.0", "http-status-codes": "^2.2.0",

View file

@ -9,9 +9,10 @@ import BudgetMongoRepository from "./repository/budget/mongo/budgetMongoReposito
import BudgetSummaryMongoRepository from "./repository/budget/mongo/budgetSummaryMongoRepository.js"; import BudgetSummaryMongoRepository from "./repository/budget/mongo/budgetSummaryMongoRepository.js";
import TransactionService from "./usecase/transaction/transactionService.js"; import TransactionService from "./usecase/transaction/transactionService.js";
import TransactionMongoRepository from "./repository/transaction/mongo/TransactionMongoRepository.js"; import TransactionMongoRepository from "./repository/transaction/mongo/TransactionMongoRepository.js";
import OpenApiValidator from "express-openapi-validator";
const app = express(); const app = express();
const API_ROOT = "/api"; const API_ROOT = "/";
app.use(expressLogger); app.use(expressLogger);
app.use(express.json()); app.use(express.json());
@ -21,6 +22,14 @@ if (environment.isProd) {
app.use(helmet()); app.use(helmet());
} }
app.use(
OpenApiValidator.middleware({
apiSpec: new URL(`../api/my-finance-pal.yml`, import.meta.url).pathname,
validateRequests: true,
validateResponses: true,
})
);
const budgetUseCases = BudgetService( const budgetUseCases = BudgetService(
BudgetSummaryMongoRepository(), BudgetSummaryMongoRepository(),
BudgetMongoRepository() BudgetMongoRepository()

View file

@ -1,7 +1,7 @@
import type * as Http from "http"; import type * as Http from "http";
import logger from "../logging/logger.js"; import logger from "../logging/logger.js";
import * as util from "util"; import * as util from "util";
import { NextFunction, type Request, type Response } from "express"; import { type NextFunction, type Request, type Response } from "express";
let httpServerRef: Http.Server; let httpServerRef: Http.Server;
@ -103,5 +103,8 @@ export const errorHandler = (
} }
// ✅ Best Practice: Pass all error to a centralized error handler so they get treated equally // ✅ Best Practice: Pass all error to a centralized error handler so they get treated equally
handleError(err); handleError(err);
res.status((err as AppError).HTTPStatus ?? 500).end(); res
.status((err as AppError).HTTPStatus ?? 500)
.json({ message: err.message, errors: (err as any).errors })
.end();
}; };

View file

@ -14,7 +14,7 @@ const budgetSummarySchema = new mongoose.Schema<BudgetSummaryEntity>(
endDate: Types.Date, endDate: Types.Date,
transactions: [transactionSchema], transactions: [transactionSchema],
}, },
{ strict: true, timestamps: true } { strict: true, timestamps: true, versionKey: false }
); );
export default budgetSummarySchema; export default budgetSummarySchema;

View file

@ -4,7 +4,7 @@ import {
type NewBudget, type NewBudget,
} from "../../../domain/budget.js"; } from "../../../domain/budget.js";
import Limit from "../../../domain/limit.js"; import Limit from "../../../domain/limit.js";
import { toDate } from "../../../util/date.js"; import { toDate, toIsoDate } from "../../../util/date.js";
import { import {
type BudgetDto, type BudgetDto,
type BudgetSummaryDto, type BudgetSummaryDto,
@ -27,8 +27,8 @@ export const BudgetDtoConverter = {
limit: domain.limit.amount, limit: domain.limit.amount,
spent: domain.spent, spent: domain.spent,
name: domain.name, name: domain.name,
startDate: domain.startDate?.toISOString(), startDate: toIsoDate(domain.startDate),
endDate: domain.endDate?.toISOString(), endDate: toIsoDate(domain.endDate),
}), }),
}; };
@ -38,8 +38,8 @@ export const BudgetSummaryDtoConverter = {
limit: domain.limit.amount, limit: domain.limit.amount,
spent: domain.spent, spent: domain.spent,
name: domain.name, name: domain.name,
startDate: domain.startDate?.toISOString(), startDate: toIsoDate(domain.startDate),
endDate: domain.endDate?.toISOString(), endDate: toIsoDate(domain.endDate),
transactions: domain.transactions.map(TransactionDtoConverter.toDto), transactions: domain.transactions.map(TransactionDtoConverter.toDto),
}), }),
}; };

View file

@ -3,3 +3,10 @@ export const toDate = (isoString?: string): Date | undefined => {
return new Date(isoString); return new Date(isoString);
} }
}; };
export const toIsoDate = (date?: Date): string | undefined => {
if (date !== null && date !== undefined) {
const isoDateTime = date.toISOString();
return isoDateTime.substring(0, isoDateTime.indexOf("T"));
}
};