Skip to content

Commit 3a8a11a

Browse files
authored
feature: database transaction logging (#1716)
* Database transaction logging middleware * Review changes * comment change * fixing failed tests * updated .gitignore * write tests for dbLogger * Update logs destination for test logs
1 parent f5d762c commit 3a8a11a

File tree

11 files changed

+416
-16
lines changed

11 files changed

+416
-16
lines changed

.env.sample

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ SMTP_USERNAME=
4949
SMTP_PORT=
5050
SMTP_SSL_TLS=
5151

52+
# Enable or disable the storage of logs
53+
LOG=false
54+
55+
56+
# Path of file that will store logs
57+
LOG_PATH=./logs/transaction.log
5258

5359
# Email for the first user who will be super admin
5460
# The user with the email address set with this parameter will automatically be elevated to Super Admin status on registration.

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ images/**
2222
video/**
2323
videos/**
2424

25+
# Ignore log files
26+
logs/*
27+
!logs/README.md
28+
*.log
29+
2530
# Don't ignore any .gitignore files in any location
2631
!.gitignore
2732

logs/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Logs Directory
2+
3+
## Overview
4+
5+
This directory is the default location for the logs generated by the audit trailing mechanism of our application. The logging system is designed to provide a comprehensive and chronological record of events, actions, and operations performed within the application, offering transparency and accountability.
6+
7+
## Setup Instructions
8+
9+
### Initial Setup
10+
11+
To initialize the logging system and ensure that all necessary configurations are correctly established, please run the following command from the root of the project:
12+
13+
```bash
14+
15+
npm run setup
16+
17+
```

package-lock.json

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
"@types/express-rate-limit": "^6.0.0",
110110
"@types/graphql-depth-limit": "^1.1.6",
111111
"@types/i18n": "^0.13.10",
112+
"@types/inquirer": "^9.0.7",
112113
"@types/jsonwebtoken": "^9.0.5",
113114
"@types/lodash": "^4.14.202",
114115
"@types/mongoose-paginate-v2": "^1.6.5",

setup.ts

Lines changed: 108 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
const dotenv = require("dotenv");
2-
const fs = require("fs");
3-
const cryptolib = require("crypto");
4-
const inquirer = require("inquirer");
5-
const mongodb = require("mongodb");
6-
const redis = require("redis");
7-
const { exec } = require("child_process");
8-
const nodemailer = require("nodemailer");
1+
import dotenv from "dotenv";
2+
import fs from "fs";
3+
import path from "path";
4+
import * as cryptolib from "crypto";
5+
import inquirer from "inquirer";
6+
import mongodb from "mongodb";
7+
import * as redis from "redis";
8+
import { exec } from "child_process";
9+
import nodemailer from "nodemailer";
10+
import type { ExecException } from "child_process";
911

1012
dotenv.config();
1113

@@ -114,6 +116,70 @@ async function accessAndRefreshTokens(
114116
}
115117
}
116118

119+
function transactionLogPath(logPath: string | null): void {
120+
const config = dotenv.parse(fs.readFileSync(".env"));
121+
config.LOG = "true";
122+
if (!logPath) {
123+
// Check if the logs/transaction.log file exists, if not, create it
124+
const defaultLogPath = path.resolve(__dirname, "logs");
125+
const defaultLogFile = path.join(defaultLogPath, "transaction.log");
126+
if (!fs.existsSync(defaultLogPath)) {
127+
console.log("Creating logs/transaction.log file...");
128+
fs.mkdirSync(defaultLogPath);
129+
}
130+
131+
config.LOG_PATH = defaultLogFile;
132+
} else {
133+
// Remove the logs files, if exists
134+
const logsDirPath = path.resolve(__dirname, "logs");
135+
if (fs.existsSync(logsDirPath)) {
136+
fs.readdirSync(logsDirPath).forEach((file: string) => {
137+
if (file !== "README.md") {
138+
const curPath = path.join(logsDirPath, file);
139+
fs.unlinkSync(curPath);
140+
}
141+
});
142+
}
143+
config.LOG_PATH = logPath;
144+
}
145+
fs.writeFileSync(".env", "");
146+
for (const key in config) {
147+
fs.appendFileSync(".env", `${key}=${config[key]}\n`);
148+
}
149+
}
150+
151+
async function askForTransactionLogPath(): Promise<string> {
152+
let logPath: string | null;
153+
// Keep asking for path, until user gives a valid path
154+
// eslint-disable-next-line no-constant-condition
155+
while (true) {
156+
const response = await inquirer.prompt([
157+
{
158+
type: "input",
159+
name: "logPath",
160+
message: "Enter absolute path of log file:",
161+
default: null,
162+
},
163+
]);
164+
logPath = response.logPath;
165+
if (logPath && fs.existsSync(logPath)) {
166+
try {
167+
fs.accessSync(logPath, fs.constants.R_OK | fs.constants.W_OK);
168+
break;
169+
} catch {
170+
console.error(
171+
"The file is not readable/writable. Please enter a valid file path."
172+
);
173+
}
174+
} else {
175+
console.error(
176+
"Invalid path or file does not exist. Please enter a valid file path."
177+
);
178+
}
179+
}
180+
return logPath;
181+
}
182+
117183
// Check connection to Redis with the specified URL.
118184
/**
119185
* The function `checkRedisConnection` checks if a connection to Redis can be established using the
@@ -253,7 +319,7 @@ async function redisConfiguration(): Promise<void> {
253319
// Update the .env file
254320
const config = dotenv.parse(fs.readFileSync(".env"));
255321
config.REDIS_HOST = host;
256-
config.REDIS_PORT = port;
322+
config.REDIS_PORT = port.toString();
257323
config.REDIS_PASSWORD = password;
258324
updateEnvVariable(config);
259325
} catch (err) {
@@ -640,7 +706,7 @@ async function importData(): Promise<void> {
640706
console.log("Importing sample data...");
641707
await exec(
642708
"npm run import:sample-data",
643-
(error: { message: string }, stdout: string, stderr: string) => {
709+
(error: ExecException | null, stdout: string, stderr: string) => {
644710
if (error) {
645711
console.error(`Error: ${error.message}`);
646712
abort();
@@ -657,7 +723,7 @@ async function importData(): Promise<void> {
657723

658724
type VerifySmtpConnectionReturnType = {
659725
success: boolean;
660-
error: any;
726+
error: unknown;
661727
};
662728

663729
/**
@@ -687,7 +753,7 @@ async function verifySmtpConnection(
687753
await transporter.verify();
688754
console.log("SMTP connection verified successfully.");
689755
return { success: true, error: null };
690-
} catch (error: any) {
756+
} catch (error: unknown) {
691757
console.error("SMTP connection verification failed:");
692758
return { success: false, error };
693759
} finally {
@@ -749,7 +815,9 @@ async function configureSmtp(): Promise<void> {
749815
console.error(
750816
"SMTP configuration verification failed. Please check your SMTP settings."
751817
);
752-
console.log(error.message);
818+
if (error instanceof Error) {
819+
console.log(error.message);
820+
}
753821
return;
754822
}
755823

@@ -815,6 +883,33 @@ async function main(): Promise<void> {
815883

816884
accessAndRefreshTokens(accessToken, refreshToken);
817885

886+
const { shouldLog } = await inquirer.prompt({
887+
type: "confirm",
888+
name: "shouldLog",
889+
message: "Would you like to enable logging for the database transactions?",
890+
default: true,
891+
});
892+
893+
if (shouldLog) {
894+
if (process.env.LOG_PATH) {
895+
console.log(
896+
`\n Log path already exists with the value:\n${process.env.LOG_PATH}`
897+
);
898+
}
899+
let logPath: string | null = null;
900+
const { shouldUseCustomLogPath } = await inquirer.prompt({
901+
type: "confirm",
902+
name: "shouldUseCustomLogPath",
903+
message: "Would you like to provide a custom path for storing logs?",
904+
default: false,
905+
});
906+
907+
if (shouldUseCustomLogPath) {
908+
logPath = await askForTransactionLogPath();
909+
}
910+
transactionLogPath(logPath);
911+
}
912+
818913
const { isDockerInstallation } = await inquirer.prompt({
819914
type: "confirm",
820915
name: "isDockerInstallation",

src/constants.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,5 +474,15 @@ export const REDIS_HOST = process.env.REDIS_HOST || "";
474474
export const REDIS_PORT = Number(process.env.REDIS_PORT);
475475
export const REDIS_PASSWORD = process.env.REDIS_PASSWORD;
476476

477-
export const key = process.env.ENCRYPTION_KEY as string;
477+
export const key = ENV.ENCRYPTION_KEY as string;
478478
export const iv = crypto.randomBytes(16).toString("hex");
479+
480+
export const LOG = ENV.LOG === "true";
481+
482+
export const LOG_PATH = ENV.LOG_PATH;
483+
484+
export enum TransactionLogTypes {
485+
CREATE = "CREATE",
486+
UPDATE = "UPDATE",
487+
DELETE = "DELETE",
488+
}

0 commit comments

Comments
 (0)