Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 24 additions & 2 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import express, {
Response,
RequestHandler,
ErrorRequestHandler,
NextFunction,
} from "express";
import morgan from "morgan";
import cors from "cors";
Expand All @@ -13,6 +14,7 @@ import sessionRouter from "./routes/session.routes";
import emailVerification from "./routes/emailVerification.routes";
import PaymentRoute from "./routes/PaymentLink.routes";
import authRoutes from "./routes/authRoutes";
import auditLogRoute from "./routes/auditLogRoutes";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Import added but route is commented out

You've imported auditLogRoute but the route registration on line 113 is commented out. Either uncomment the route registration or remove this import if it's not being used yet.

import userRoutes from "./routes/userRoutes";
import healthRouter from "./routes/health.routes";
import walletVerificationRoutes from "./routes/wallet-verification.routes";
Expand All @@ -30,6 +32,8 @@ import { requestLogger } from "./middlewares/requestLogger.middleware";
import RateLimitMonitoringService from "./services/rateLimitMonitoring.service";
import { startExpiredSessionCleanupCronJobs } from "./utils/schedular";
import logger from "./utils/logger";
import { auth } from "express-openid-connect";
import { auditMiddleware } from "./middlewares/audit.middleware";

// Initialize express app
const app = express();
Expand Down Expand Up @@ -77,6 +81,21 @@ app.use((req, res, next) => {
// Start scheduled jobs
startExpiredSessionCleanupCronJobs();

// Initialize the database connection
// AppDataSource.initialize()
// .then(() => {
// logger.info("Data Source has been initialized!");
// })
// .catch((err) => {
// logger.error("Error during Data Source initialization:", err);
// process.exit(1); // Exit if we can't connect to the database
// });
Comment on lines +84 to +92

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Verification agent

🧩 Analysis chain

Uncomment or remove database initialization code.

The database initialization code is commented out, which suggests it's handled elsewhere. If that's the case, remove this code entirely. If not, uncomment it to ensure the database is properly initialized before handling requests. Leaving commented code in production creates confusion about what's actually running.


🏁 Script executed:

#!/bin/bash
# Check where database initialization happens in the codebase
rg -A 5 "AppDataSource.initialize" --type=ts

Length of output: 2450


Remove redundant commented-out database initialization in src/app.ts

The database is already initialized in src/index.ts (and in health routes), so the commented-out block in src/app.ts can be deleted to avoid confusion.

β€’ File: src/app.ts
Lines 86–94 – remove the entire commented block:

- // Initialize the database connection
- // AppDataSource.initialize()
- //   .then(() => {
- //     logger.info("Data Source has been initialized!");
- //   })
- //   .catch((err) => {
- //     logger.error("Error during Data Source initialization:", err);
- //     process.exit(1); // Exit if we can't connect to the database
- //   });
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Initialize the database connection
// AppDataSource.initialize()
// .then(() => {
// logger.info("Data Source has been initialized!");
// })
// .catch((err) => {
// logger.error("Error during Data Source initialization:", err);
// process.exit(1); // Exit if we can't connect to the database
// });


app.use(((req: Request, res: Response, next: NextFunction) => {
req.dataSource = AppDataSource;
next();
}) as RequestHandler);
Comment on lines +94 to +97

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Missing import for AppDataSource

The middleware attaches AppDataSource to the request object, but AppDataSource is not imported. This will cause runtime errors.

Add the import at the top of the file:

import logger from "./utils/logger";
import { auth } from "express-openid-connect";
import { auditMiddleware } from "./middlewares/audit.middleware";
+import { AppDataSource } from "./config/db";
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
app.use(((req: Request, res: Response, next: NextFunction) => {
req.dataSource = AppDataSource;
next();
}) as RequestHandler);
import logger from "./utils/logger";
import { auth } from "express-openid-connect";
import { auditMiddleware } from "./middlewares/audit.middleware";
import { AppDataSource } from "./config/db";
🧰 Tools
πŸͺ› ESLint

[error] 95-95: 'AppDataSource' is not defined.

(no-undef)


// Log application startup
logger.info("Application started successfully");

Expand All @@ -91,7 +110,10 @@ app.use("/users", userRoutes);
app.use("/merchants", merchantRoutes);
app.use("/webhook-queue/merchant", merchantWebhookQueueRoutes);
app.use("/reports/transactions", transactionReportsRoutes);
//app.use("/api/v1/stellar", stellarContractRoutes);
//app.use("/audit-log", auditLogRoute);
app.use("/api/v1/stellar", stellarContractRoutes);
Comment on lines +113 to +114

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Inconsistent route configuration

There are two issues here:

  1. The audit log route is commented out (line 113), which is inconsistent with the purpose of this PR
  2. stellarContractRoutes is being used (line 114) but its import on line 24 is commented out

Fix these issues with the following changes:

-//app.use("/audit-log", auditLogRoute);
-app.use("/api/v1/stellar", stellarContractRoutes);
+app.use("/audit-log", auditLogRoute);
+//app.use("/api/v1/stellar", stellarContractRoutes);

Or uncomment the import on line 24:

-//import stellarContractRoutes from "./routes/stellar-contract.routes";
+import stellarContractRoutes from "./routes/stellar-contract.routes";
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//app.use("/audit-log", auditLogRoute);
app.use("/api/v1/stellar", stellarContractRoutes);
// In src/app.ts, replace lines 113–114:
app.use("/audit-log", auditLogRoute);
//app.use("/api/v1/stellar", stellarContractRoutes);
🧰 Tools
πŸͺ› ESLint

[error] 114-114: 'stellarContractRoutes' is not defined.

(no-undef)




// Error handling middleware
const customErrorHandler: ErrorRequestHandler = (err, req, res, _next) => {
Expand All @@ -117,4 +139,4 @@ app.use(((req: Request, res: Response) => {
}) as RequestHandler);

// Export app
export default app;
export { app };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Verification agent

🧩 Analysis chain

Verify that import changes are applied throughout the codebase.

Changing from default export to named export will require updates to all import statements that reference this module. Ensure all imports have been updated to use the new named export format.


🏁 Script executed:

#!/bin/bash
# Check for imports of app to ensure they're using named imports
rg "import .* from ['\"].*app['\"]" --type=ts

Length of output: 330


Update app imports to use named export

Tests are still using the default import (import app from "../app"), which will break now that src/app.ts exports app as a named export. Please update all import statements accordingly:

β€’ src/tests/health.test.ts
β€’ src/tests/auth.test.ts
β€’ src/tests/transactionReportsRoute.test.ts
β€’ src/tests/routes/merchantRoutes.test.ts

Example change:

-import app from "../app";
+import { app } from "../app";

Committable suggestion skipped: line range outside the PR's diff.

7 changes: 4 additions & 3 deletions src/config/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import { WalletVerification } from "../entities/WalletVerification";
import { MerchantEntity } from "../entities/Merchant.entity";
import { MerchantWebhookEntity } from "../entities/MerchantWebhook.entity";
import { MerchantWebhookEventEntity } from "../entities/MerchantWebhookEvent.entity";
import { AuditLog } from "../entities/AuditLog.entity";
import { PaymentLink } from "../entities/PaymentLink";


dotenv.config();

const AppDataSource = new DataSource({
export const AppDataSource = new DataSource({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Verification agent

🧩 Analysis chain

Export style change from default to named export.

The switch from a default export to a named export improves code consistency and makes imports more explicit throughout the codebase.


🏁 Script executed:

#!/bin/bash
# Check if all imports of AppDataSource have been updated to use named imports
grep -r "import AppDataSource from" --include="*.ts" .

Length of output: 601


Update test imports to use named export
Several test files still import AppDataSource as a default export. Please update these to use the named export syntax.

Files to update:

  • src/tests/PaymentService.test.ts
  • src/tests/auth.test.ts
  • src/tests/health.test.ts
  • src/tests/routes/merchantRoutes.test.ts
  • src/tests/services/UserService.test.ts
  • src/tests/sessionController.test.ts
  • src/tests/sessionService.test.ts

Example change:

- import AppDataSource from "../config/db";
+ import { AppDataSource } from "../config/db";

Ensure all occurrences are corrected so tests import with braces.

type: "postgres",
host: process.env.POSTGRES_HOST,
port: Number(process.env.POSTGRES_PORT),
Expand All @@ -32,10 +34,9 @@ const AppDataSource = new DataSource({
MerchantEntity,
MerchantWebhookEntity,
MerchantWebhookEventEntity,
AuditLog,
PaymentLink,
],
migrations: ["src/migrations/*.ts"],
migrationsTableName: "migrations",
});

export default AppDataSource;
36 changes: 36 additions & 0 deletions src/controllers/AuditLogController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Request, Response } from "express";

import { AuditLogActionsEnum } from "../enums/AuditLogAction";
import { AuditService } from "../services/Audit.service";

export class AuditLogController {
fetchAllAuditLogs = async (req: Request, res: Response) => {
const auditService = new AuditService(req.dataSource);
try {
const filters = {
entityType: req.query.entityType as string,
entityId: req.query.entityId as string,
userId: req.query.userId as string,
action: req.query.action as AuditLogActionsEnum,
startDate: req.query.startDate
? new Date(req.query.startDate as string)
: undefined,
endDate: req.query.endDate
? new Date(req.query.endDate as string)
: undefined,
page: req.query.page ? parseInt(req.query.page as string, 10) : 1,
limit: req.query.limit ? parseInt(req.query.limit as string, 10) : 10,
};
Comment on lines +10 to +23

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

Validate query params before passing them to the service
Raw casting/new Date() conversions accept invalid input and may yield undefined or Invalid Date, causing silent failures or unexpected SQL results. A minimal in-place fix keeps the footprint small:

-const filters = {
-  entityType: req.query.entityType as string,
-  entityId: req.query.entityId as string,
-  userId: req.query.userId as string,
-  action: req.query.action as AuditLogActionsEnum,
-  startDate: req.query.startDate ? new Date(req.query.startDate as string) : undefined,
-  endDate: req.query.endDate ? new Date(req.query.endDate as string) : undefined,
-  page: req.query.page ? parseInt(req.query.page as string, 10) : 1,
-  limit: req.query.limit ? parseInt(req.query.limit as string, 10) : 10,
-};
+const parseDate = (d?: string) => {
+  const dt = d ? new Date(d) : undefined;
+  if (dt && isNaN(dt.getTime())) throw new Error(`Invalid date: ${d}`);
+  return dt;
+};
+
+const toPositiveInt = (v: unknown, fallback: number) => {
+  const n = Number(v);
+  return Number.isInteger(n) && n > 0 ? n : fallback;
+};
+
+const filters = {
+  entityType: req.query.entityType as string | undefined,
+  entityId: req.query.entityId as string | undefined,
+  userId: req.query.userId as string | undefined,
+  action: req.query.action as AuditLogActionsEnum | undefined,
+  startDate: parseDate(req.query.startDate as string | undefined),
+  endDate: parseDate(req.query.endDate as string | undefined),
+  page: toPositiveInt(req.query.page, 1),
+  limit: toPositiveInt(req.query.limit, 10),
+};

This keeps invalid input from propagating to the DB layer and returns a clear 400 for bad requests.

πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const filters = {
entityType: req.query.entityType as string,
entityId: req.query.entityId as string,
userId: req.query.userId as string,
action: req.query.action as AuditLogActionsEnum,
startDate: req.query.startDate
? new Date(req.query.startDate as string)
: undefined,
endDate: req.query.endDate
? new Date(req.query.endDate as string)
: undefined,
page: req.query.page ? parseInt(req.query.page as string, 10) : 1,
limit: req.query.limit ? parseInt(req.query.limit as string, 10) : 10,
};
const parseDate = (d?: string) => {
const dt = d ? new Date(d) : undefined;
if (dt && isNaN(dt.getTime())) throw new Error(`Invalid date: ${d}`);
return dt;
};
const toPositiveInt = (v: unknown, fallback: number) => {
const n = Number(v);
return Number.isInteger(n) && n > 0 ? n : fallback;
};
const filters = {
entityType: req.query.entityType as string | undefined,
entityId: req.query.entityId as string | undefined,
userId: req.query.userId as string | undefined,
action: req.query.action as AuditLogActionsEnum | undefined,
startDate: parseDate(req.query.startDate as string | undefined),
endDate: parseDate(req.query.endDate as string | undefined),
page: toPositiveInt(req.query.page, 1),
limit: toPositiveInt(req.query.limit, 10),
};


const result = await auditService.findAuditLogs(filters);

res.status(200).json(result);
} catch (error) {
if (error instanceof Error) {
res.status(400).json({ message: error.message });
} else {
res.status(500).json({ message: "Internal server error" });
}
}
};
}
17 changes: 16 additions & 1 deletion src/controllers/AuthController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
import { Request, Response } from "express";
import { AuthService } from "../services/AuthService";
import { validateTwoFactorAuthentication } from "./validateTwoFactorAuthentication";
import AppDataSource from "../config/db";
import { User } from "../entities/User";
import { compare } from "bcryptjs";
import { Auth0Profile } from "src/interfaces/auth.interfaces";

import { AuditLogActionsEnum } from "../enums/AuditLogAction";
import { AuditService } from "../services/Audit.service";
import { AppDataSource } from "../config/db";

export class AuthController {
public authService: AuthService;

Expand All @@ -17,6 +20,18 @@ export class AuthController {
register = async (req: Request, res: Response): Promise<void> => {
try {
const user = await this.authService.register(req.body);
console.log("Done with registration", { user });
const auditService = new AuditService(req.dataSource);
await auditService.createAuditLogs({
entityType: "User",
entityId: String(user.id),
action: AuditLogActionsEnum.CREATE,
newValues: req.body,
userId: String(user.id),
userEmail: user.email,
ipAddress: req.auditContext?.ipAddress || "0.0.0.0",
userAgent: req.auditContext?.userAgent || "Unknown",
});
Comment on lines +23 to +34

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

Audit logging implementation for user registration.

The audit logging implementation captures comprehensive information about the registration event, including entity details, user information, and request metadata with appropriate fallbacks.

Consider wrapping the audit logging in a try-catch block to prevent audit logging failures from affecting the user experience:

      console.log("Done with registration", { user });
-      const auditService = new AuditService(req.dataSource);
-      await auditService.createAuditLogs({
-        entityType: "User",
-        entityId: String(user.id),
-        action: AuditLogActionsEnum.CREATE,
-        newValues: req.body,
-        userId: String(user.id),
-        userEmail: user.email,
-        ipAddress: req.auditContext?.ipAddress || "0.0.0.0",
-        userAgent: req.auditContext?.userAgent || "Unknown",
-      });
+      try {
+        const auditService = new AuditService(req.dataSource);
+        await auditService.createAuditLogs({
+          entityType: "User",
+          entityId: String(user.id),
+          action: AuditLogActionsEnum.CREATE,
+          newValues: req.body,
+          userId: String(user.id),
+          userEmail: user.email,
+          ipAddress: req.auditContext?.ipAddress || "0.0.0.0",
+          userAgent: req.auditContext?.userAgent || "Unknown",
+        });
+      } catch (auditError) {
+        console.error("Failed to create audit log for user registration:", auditError);
+        // Continue with the response, don't fail the registration if audit logging fails
+      }
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log("Done with registration", { user });
const auditService = new AuditService(req.dataSource);
await auditService.createAuditLogs({
entityType: "User",
entityId: String(user.id),
action: AuditLogActionsEnum.CREATE,
newValues: req.body,
userId: String(user.id),
userEmail: user.email,
ipAddress: req.auditContext?.ipAddress || "0.0.0.0",
userAgent: req.auditContext?.userAgent || "Unknown",
});
console.log("Done with registration", { user });
try {
const auditService = new AuditService(req.dataSource);
await auditService.createAuditLogs({
entityType: "User",
entityId: String(user.id),
action: AuditLogActionsEnum.CREATE,
newValues: req.body,
userId: String(user.id),
userEmail: user.email,
ipAddress: req.auditContext?.ipAddress || "0.0.0.0",
userAgent: req.auditContext?.userAgent || "Unknown",
});
} catch (auditError) {
console.error("Failed to create audit log for user registration:", auditError);
// Continue with the response, don't fail the registration if audit logging fails
}

res.status(201).json(user);
} catch (error) {
if (error instanceof Error) {
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/PaymentLink.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Request, Response } from "express";
import { PaymentLinkService } from "../services/PaymentLink.services";
import { Repository } from "typeorm";
import { PaymentLink } from "../entities/PaymentLink";
import AppDataSource from "../config/db";
import { AppDataSource } from "../config/db";
import {
CreatePaymentLinkDto,
UpdatePaymentLinkDto,
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/twoFactorAuthController.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { User } from "../entities/User";
import { TwoFactorAuth } from "../entities/TwoFactorAuth";
import { generateTwoFactorSecret } from "../services/generateTwoFactorSecret";
import AppDataSource from "../config/db";
import { AppDataSource } from "../config/db";
import { validateTwoFactorAuthentication } from "./validateTwoFactorAuthentication";

export const enableTwoFactorAuthentication = async (userId: number) => {
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/validateTwoFactorAuthentication.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { verifyTwoFactorCode } from "../services/verifyTwoFactorCode";
import { User } from "../entities/User";
import AppDataSource from "../config/db";
import { AppDataSource } from "../config/db";

export const validateTwoFactorAuthentication = async (
userId: number,
Expand Down
48 changes: 48 additions & 0 deletions src/entities/AuditLog.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
} from "typeorm";
import { AuditLogActionsEnum } from "../enums/AuditLogAction";

@Entity("audit_logs")
export class AuditLog {
@PrimaryGeneratedColumn("uuid")
id: string;

@Column()
entityType: string; // 'User', 'PaymentLink', 'BusinessProfile', etc.

@Column()
entityId: string;

@Column({
type: "enum",
enum: AuditLogActionsEnum,
default: AuditLogActionsEnum.CREATE,
})
action: AuditLogActionsEnum;

@Column("json", { nullable: true })
oldValues?: Record<string, any>; // used when there is an update, so we can see
// what value was there previously

@Column("json", { nullable: true })
newValues?: Record<string, any>;

@Column()
userId: string;

@Column()
userEmail: string;

@Column()
ipAddress: string;

@Column()
userAgent: string;

@CreateDateColumn()
createdAt: Date;
}
Loading