From a032c86493b4a6d9e03b0fcc1e4620ffdf090b06 Mon Sep 17 00:00:00 2001 From: Ndifreke Ekanem <111875002+Ndifreke000@users.noreply.github.com> Date: Fri, 25 Apr 2025 12:39:52 +0000 Subject: [PATCH 1/5] ufs --- package-lock.json | 203 +++++++++++++---- package.json | 5 +- src/config/configuration.ts | 30 ++- src/config/data-source.ts | 16 ++ .../controllers/transaction.controller.ts | 92 ++++++++ .../treasury/entities/account.entity.ts | 13 ++ .../treasury/entities/audit-log.entity.ts | 26 +++ .../treasury/entities/ledger-entry.entity.ts | 21 ++ .../treasury/entities/transaction.entity.ts | 34 +++ .../treasury/services/transaction.service.ts | 213 ++++++++++++++++++ src/modules/treasury/treasury.module.ts | 15 +- 11 files changed, 613 insertions(+), 55 deletions(-) create mode 100644 src/config/data-source.ts create mode 100644 src/modules/treasury/controllers/transaction.controller.ts create mode 100644 src/modules/treasury/entities/account.entity.ts create mode 100644 src/modules/treasury/entities/audit-log.entity.ts create mode 100644 src/modules/treasury/entities/ledger-entry.entity.ts create mode 100644 src/modules/treasury/entities/transaction.entity.ts create mode 100644 src/modules/treasury/services/transaction.service.ts diff --git a/package-lock.json b/package-lock.json index d527343..959b37c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,11 +28,11 @@ "passport-custom": "^1.1.1", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", - "pg": "^8.13.3", + "pg": "^8.15.5", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", "starknet": "^6.24.1", - "typeorm": "^0.3.21", + "typeorm": "^0.3.22", "winston": "^3.17.0" }, "devDependencies": { @@ -62,6 +62,7 @@ "ts-loader": "^9.5.2", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", + "typeorm-ts-node-commonjs": "^0.3.20", "typescript": "^5.7.3", "typescript-eslint": "^8.20.0" } @@ -1930,11 +1931,14 @@ } }, "node_modules/@nestjs/common": { - "version": "11.0.12", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.0.12.tgz", - "integrity": "sha512-6PXxmDe2iYmb57xacnxzpW1NAxRZ7Gf+acMT7/hmRB/4KpZiFU/cNvLWwgbM2BL5QSzQulOwY6ny5bbKnPpB+A==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.0.tgz", + "integrity": "sha512-8MrajltjtIN6eW9cTpv+1IZogqz2Zsrc8YDt0LwQPUq8cSq0j50DETdQpPsNMeib+p9avkV41+NrzGk1z2o5Wg==", + "license": "MIT", "dependencies": { + "file-type": "20.4.1", "iterare": "1.2.1", + "load-esm": "1.0.2", "tslib": "2.8.1", "uid": "2.0.2" }, @@ -1957,10 +1961,59 @@ } } }, + "node_modules/@nestjs/common/node_modules/file-type": { + "version": "20.4.1", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.4.1.tgz", + "integrity": "sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==", + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.2.6", + "strtok3": "^10.2.0", + "token-types": "^6.0.0", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/@nestjs/common/node_modules/peek-readable": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-7.0.0.tgz", + "integrity": "sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@nestjs/common/node_modules/strtok3": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.2.2.tgz", + "integrity": "sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/@nestjs/config": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.2.tgz", "integrity": "sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==", + "license": "MIT", "dependencies": { "dotenv": "16.4.7", "dotenv-expand": "12.0.1", @@ -2158,6 +2211,7 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz", "integrity": "sha512-SOeUQl70Lb2OfhGkvnh4KXWlsd+zA08RuuQgT7kKbzivngxzSo1Oc7Usu5VxCxACQC9wc2l9esOHILSJeK7rJA==", + "license": "MIT", "peerDependencies": { "@nestjs/common": "^10.0.0 || ^11.0.0", "@nestjs/core": "^10.0.0 || ^11.0.0", @@ -2467,11 +2521,28 @@ "node": ">=14.16" } }, + "node_modules/@tokenizer/inflate": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", + "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "fflate": "^0.8.2", + "token-types": "^6.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "dev": true + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" }, "node_modules/@tsconfig/node10": { "version": "1.0.11", @@ -3533,6 +3604,7 @@ "version": "3.15.0", "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.15.0.tgz", "integrity": "sha512-zIcWDJ+Kwqxfdnogx66Gxzr0kVmCcRAdat9nlY2IHsshqTN4fBH6tMeRMPA/2w0rpBayIJvjQAaa2/4RDrNqwg==", + "dev": true, "engines": { "node": ">=14" } @@ -5600,6 +5672,12 @@ "tough-cookie": "^4.0.0" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -7628,6 +7706,25 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/load-esm": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.2.tgz", + "integrity": "sha512-nVAvWk/jeyrWyXEAs84mpQCYccxRqgKY4OznLuJhJCa0XsPSfdOIr2zvBZEj3IHEHbX97jjscKRRV539bW0Gpw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + }, + { + "type": "buymeacoffee", + "url": "https://buymeacoffee.com/borewit" + } + ], + "license": "MIT", + "engines": { + "node": ">=13.2.0" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -8616,13 +8713,14 @@ "dev": true }, "node_modules/pg": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.14.1.tgz", - "integrity": "sha512-0TdbqfjwIun9Fm/r89oB7RFQ0bLgduAhiIqIXOsyKoiC/L54DbuAAzIEN/9Op0f1Po9X7iCPXGoa/Ah+2aI8Xw==", + "version": "8.15.5", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.15.5.tgz", + "integrity": "sha512-EpAhHFQc+aH9VfeffWIVC+XXk6lmAhS9W1FxtxcPXs94yxhrI1I6w/zkWfIOII/OkBv3Be04X3xMOj0kQ78l6w==", + "license": "MIT", "dependencies": { - "pg-connection-string": "^2.7.0", - "pg-pool": "^3.8.0", - "pg-protocol": "^1.8.0", + "pg-connection-string": "^2.8.5", + "pg-pool": "^3.9.5", + "pg-protocol": "^1.9.5", "pg-types": "^2.1.0", "pgpass": "1.x" }, @@ -8630,7 +8728,7 @@ "node": ">= 8.0.0" }, "optionalDependencies": { - "pg-cloudflare": "^1.1.1" + "pg-cloudflare": "^1.2.5" }, "peerDependencies": { "pg-native": ">=3.0.1" @@ -8642,15 +8740,17 @@ } }, "node_modules/pg-cloudflare": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", - "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.5.tgz", + "integrity": "sha512-OOX22Vt0vOSRrdoUPKJ8Wi2OpE/o/h9T8X1s4qSkCedbNah9ei2W2765be8iMVxQUsvgT7zIAT2eIa9fs5+vtg==", + "license": "MIT", "optional": true }, "node_modules/pg-connection-string": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz", - "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==" + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.8.5.tgz", + "integrity": "sha512-Ni8FuZ8yAF+sWZzojvtLE2b03cqjO5jNULcHFfM9ZZ0/JXrgom5pBREbtnAw7oxsxJqHw9Nz/XWORUEL3/IFow==", + "license": "MIT" }, "node_modules/pg-int8": { "version": "1.0.1", @@ -8661,17 +8761,19 @@ } }, "node_modules/pg-pool": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.8.0.tgz", - "integrity": "sha512-VBw3jiVm6ZOdLBTIcXLNdSotb6Iy3uOCwDGFAksZCXmi10nyRvnP2v3jl4d+IsLYRyXf6o9hIm/ZtUzlByNUdw==", + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.9.5.tgz", + "integrity": "sha512-DxyAlOgvUzRFpFAZjbCc8fUfG7BcETDHgepFPf724B0i08k9PAiZV1tkGGgQIL0jbMEuR9jW1YN7eX+WgXxCsQ==", + "license": "MIT", "peerDependencies": { "pg": ">=8.0" } }, "node_modules/pg-protocol": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.8.0.tgz", - "integrity": "sha512-jvuYlEkL03NRvOoyoRktBK7+qU5kOvlAwvmrH8sr3wbLrOdVWsRxQfz8mMy9sZFsqJ1hEWNfdWKI4SAmoL+j7g==" + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.9.5.tgz", + "integrity": "sha512-DYTWtWpfd5FOro3UnAfwvhD8jh59r2ig8bPtc9H8Ds7MscE/9NYruUQWFAOuraRl29jwcT2kyMFQ3MxeaVjUhg==", + "license": "MIT" }, "node_modules/pg-types": { "version": "2.2.0", @@ -10436,7 +10538,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.0.tgz", "integrity": "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==", - "dev": true, "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" @@ -10583,6 +10684,7 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "devOptional": true, + "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -10716,23 +10818,24 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "node_modules/typeorm": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.21.tgz", - "integrity": "sha512-lh4rUWl1liZGjyPTWpwcK8RNI5x4ekln+/JJOox1wCd7xbucYDOXWD+1cSzTN3L0wbTGxxOtloM5JlxbOxEufA==", + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.22.tgz", + "integrity": "sha512-P/Tsz3UpJ9+K0oryC0twK5PO27zejLYYwMsE8SISfZc1lVHX+ajigiOyWsKbuXpEFMjD9z7UjLzY3+ElVOMMDA==", + "license": "MIT", "dependencies": { "@sqltools/formatter": "^1.2.5", - "ansis": "^3.9.0", + "ansis": "^3.17.0", "app-root-path": "^3.1.0", "buffer": "^6.0.3", - "dayjs": "^1.11.9", - "debug": "^4.3.4", - "dotenv": "^16.0.3", + "dayjs": "^1.11.13", + "debug": "^4.4.0", + "dotenv": "^16.4.7", "glob": "^10.4.5", "sha.js": "^2.4.11", "sql-highlight": "^6.0.0", - "tslib": "^2.5.0", - "uuid": "^11.0.5", - "yargs": "^17.6.2" + "tslib": "^2.8.1", + "uuid": "^11.1.0", + "yargs": "^17.7.2" }, "bin": { "typeorm": "cli.js", @@ -10746,12 +10849,12 @@ "url": "https://opencollective.com/typeorm" }, "peerDependencies": { - "@google-cloud/spanner": "^5.18.0", + "@google-cloud/spanner": "^5.18.0 || ^6.0.0 || ^7.0.0", "@sap/hana-client": "^2.12.25", "better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", "hdb-pool": "^0.1.6", "ioredis": "^5.0.4", - "mongodb": "^5.8.0", + "mongodb": "^5.8.0 || ^6.0.0", "mssql": "^9.1.1 || ^10.0.1 || ^11.0.1", "mysql2": "^2.2.5 || ^3.0.1", "oracledb": "^6.3.0", @@ -10763,7 +10866,7 @@ "sql.js": "^1.4.0", "sqlite3": "^5.0.3", "ts-node": "^10.7.0", - "typeorm-aurora-data-api-driver": "^2.0.0" + "typeorm-aurora-data-api-driver": "^2.0.0 || ^3.0.0" }, "peerDependenciesMeta": { "@google-cloud/spanner": { @@ -10819,6 +10922,25 @@ } } }, + "node_modules/typeorm-ts-node-commonjs": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/typeorm-ts-node-commonjs/-/typeorm-ts-node-commonjs-0.3.20.tgz", + "integrity": "sha512-lXjve7w7OcF3s5+dHnCsrBjUTukpVeiS0bDe5KDXWcDx8TyRW0GTTg9kjWgHzFgHgBIBBu4WGXM0iuGpEgaV9g==", + "dev": true, + "license": "UNLICENSED", + "bin": { + "typeorm-ts-node-commonjs": "wrapper.sh" + } + }, + "node_modules/typeorm/node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, "node_modules/typeorm/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -10967,7 +11089,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz", "integrity": "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==", - "dev": true, "engines": { "node": ">=18" }, diff --git a/package.json b/package.json index 85cb0d6..16c95ad 100644 --- a/package.json +++ b/package.json @@ -39,11 +39,11 @@ "passport-custom": "^1.1.1", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", - "pg": "^8.13.3", + "pg": "^8.15.5", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", "starknet": "^6.24.1", - "typeorm": "^0.3.21", + "typeorm": "^0.3.22", "winston": "^3.17.0" }, "devDependencies": { @@ -73,6 +73,7 @@ "ts-loader": "^9.5.2", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", + "typeorm-ts-node-commonjs": "^0.3.20", "typescript": "^5.7.3", "typescript-eslint": "^8.20.0" }, diff --git a/src/config/configuration.ts b/src/config/configuration.ts index 1240415..22e3e11 100644 --- a/src/config/configuration.ts +++ b/src/config/configuration.ts @@ -1,18 +1,28 @@ -import { registerAs } from '@nestjs/config'; +import dotenv from 'dotenv'; +dotenv.config(); -export default registerAs('app', () => ({ +// Helper to parse integers robustly +const toInt = (value, fallback) => { + const num = parseInt(value, 10); + return Number.isNaN(num) ? fallback : num; +}; + +const config = { env: process.env.NODE_ENV || 'development', - name: process.env.APP_NAME || 'MyApp', - port: parseInt(process.env.APP_PORT || '3000', 10), + name: process.env.APP_NAME || 'budget-chain-backend', + port: toInt(process.env.PORT, 3000), database: { host: process.env.DATABASE_HOST || 'localhost', - port: parseInt(process.env.DATABASE_PORT || '5432', 10), + port: toInt(process.env.DATABASE_PORT, 5432), user: process.env.DATABASE_USER || 'postgres', - password: process.env.DATABASE_PASSWORD || 'secret', - name: process.env.DATABASE_NAME || 'mydatabase', + password: process.env.DATABASE_PASSWORD || 'password', + name: process.env.DATABASE_NAME || 'budgetchain', }, jwt: { - secret: process.env.JWT_SECRET || 'default_jwt_secret', - expiresIn: process.env.JWT_EXPIRES_IN || '3600s', + // Throw error if secret isn't set for security + secret: process.env.JWT_SECRET || (() => { throw new Error('JWT_SECRET is not defined'); })(), + expiresIn: process.env.JWT_EXPIRES_IN || '1h', }, -})); +}; + +export default config; diff --git a/src/config/data-source.ts b/src/config/data-source.ts new file mode 100644 index 0000000..be53dfa --- /dev/null +++ b/src/config/data-source.ts @@ -0,0 +1,16 @@ +import { DataSource } from 'typeorm'; +import { ConfigService } from '@nestjs/config'; + +const configService = new ConfigService(); + +export default new DataSource({ + type: 'postgres', + host: configService.get('database').host, + port: configService.get('database').port, + username: configService.get('database').user, + password: configService.get('database').password, + database: configService.get('database').name, + entities: ['src/modules/**/*.entity.ts'], + migrations: ['src/migrations/*.ts'], + synchronize: false // Use migrations instead of auto-sync +}); \ No newline at end of file diff --git a/src/modules/treasury/controllers/transaction.controller.ts b/src/modules/treasury/controllers/transaction.controller.ts new file mode 100644 index 0000000..04e3df7 --- /dev/null +++ b/src/modules/treasury/controllers/transaction.controller.ts @@ -0,0 +1,92 @@ +import { Controller, Post, Get, Put, Delete, Param, Body, Query, UseGuards, Request } from '@nestjs/common'; +import { TransactionService } from '../services/transaction.service'; +import { RolesGuard } from '../../../shared/guards/roles.guard'; +import { Roles } from '../../../shared/decorators/roles.decorator'; +import { UserRole } from '../../user/entities/user.entity'; + +@Controller('transactions') +@UseGuards(RolesGuard) +export class TransactionController { + constructor(private readonly transactionService: TransactionService) {} + + @Post() + @Roles(UserRole.ADMIN) + async createTransaction( + @Request() req, + @Body() body: { + date: Date; + description: string; + category: string; + ledgerEntries: { accountId: number; type: 'debit' | 'credit'; amount: number }[]; + metadata?: any; + }, + ) { + return this.transactionService.createTransaction( + req.user, + body.date, + body.description, + body.category, + body.ledgerEntries, + body.metadata, + ); + } + + @Put(':id') + @Roles(UserRole.ADMIN) + async updateTransaction( + @Param('id') id: number, + @Request() req, + @Body() body: { + date?: Date; + description?: string; + category?: string; + ledgerEntries?: { accountId: number; type: 'debit' | 'credit'; amount: number }[]; + metadata?: any; + }, + ) { + return this.transactionService.updateTransaction( + id, + req.user, + body.date, + body.description, + body.category, + body.ledgerEntries, + body.metadata, + ); + } + + @Delete(':id') + @Roles(UserRole.ADMIN) + async deleteTransaction(@Param('id') id: number, @Request() req) { + await this.transactionService.deleteTransaction(id, req.user); + return { message: 'Transaction deleted successfully' }; + } + + @Get() + @Roles(UserRole.USER, UserRole.ADMIN) + async getTransactions( + @Query('startDate') startDate?: Date, + @Query('endDate') endDate?: Date, + @Query('category') category?: string, + @Query('accountId') accountId?: number, + @Query('description') description?: string, + @Query('page') page = 1, + @Query('limit') limit = 10, + ) { + const filters = { startDate, endDate, category, accountId, description }; + return this.transactionService.findTransactions(filters, page, limit); + } + + @Post('reconcile') + @Roles(UserRole.ADMIN) + async reconcileTransactions() { + await this.transactionService.reconcileTransactions(); + return { message: 'Reconciliation completed' }; + } + + @Get('report') + @Roles(UserRole.USER, UserRole.ADMIN) + async generateReport(@Query('startDate') startDate: Date, @Query('endDate') endDate: Date) { + return this.transactionService.generateReport(startDate, endDate); + } +} \ No newline at end of file diff --git a/src/modules/treasury/entities/account.entity.ts b/src/modules/treasury/entities/account.entity.ts new file mode 100644 index 0000000..7b1df89 --- /dev/null +++ b/src/modules/treasury/entities/account.entity.ts @@ -0,0 +1,13 @@ +import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; + +@Entity('accounts') +export class Account { + @PrimaryGeneratedColumn() + id: number; + + @Column() + name: string; + + @Column('decimal', { precision: 15, scale: 2, default: 0 }) + balance: number; +} \ No newline at end of file diff --git a/src/modules/treasury/entities/audit-log.entity.ts b/src/modules/treasury/entities/audit-log.entity.ts new file mode 100644 index 0000000..d844392 --- /dev/null +++ b/src/modules/treasury/entities/audit-log.entity.ts @@ -0,0 +1,26 @@ +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn } from 'typeorm'; +import { User } from '../../user/entities/user.entity'; + +@Entity('audit_logs') +export class AuditLog { + @PrimaryGeneratedColumn() + id: number; + + @Column() + entityName: string; // e.g., 'Transaction' + + @Column() + entityId: number; + + @Column({ type: 'enum', enum: ['create', 'update', 'delete'] }) + action: 'create' | 'update' | 'delete'; + + @Column({ type: 'json' }) + changes: any; // Stores before/after values + + @ManyToOne(() => User) + user: User; + + @CreateDateColumn() + timestamp: Date; +} \ No newline at end of file diff --git a/src/modules/treasury/entities/ledger-entry.entity.ts b/src/modules/treasury/entities/ledger-entry.entity.ts new file mode 100644 index 0000000..cd11b1a --- /dev/null +++ b/src/modules/treasury/entities/ledger-entry.entity.ts @@ -0,0 +1,21 @@ +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm'; +import { Transaction } from './transaction.entity'; +import { Account } from './account.entity'; + +@Entity('ledger_entries') +export class LedgerEntry { + @PrimaryGeneratedColumn() + id: number; + + @ManyToOne(() => Transaction, transaction => transaction.ledgerEntries) + transaction: Transaction; + + @ManyToOne(() => Account) + account: Account; + + @Column({ type: 'enum', enum: ['debit', 'credit'] }) + type: 'debit' | 'credit'; + + @Column('decimal', { precision: 15, scale: 2 }) + amount: number; +} \ No newline at end of file diff --git a/src/modules/treasury/entities/transaction.entity.ts b/src/modules/treasury/entities/transaction.entity.ts new file mode 100644 index 0000000..f1fe51c --- /dev/null +++ b/src/modules/treasury/entities/transaction.entity.ts @@ -0,0 +1,34 @@ +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, CreateDateColumn, Index } from 'typeorm'; +import { User } from '../../user/entities/user.entity'; +import { LedgerEntry } from './ledger-entry.entity'; + +@Entity('transactions') +@Index(['date', 'category']) // Optimize search and filtering +export class Transaction { + @PrimaryGeneratedColumn() + id: number; + + @Column() + date: Date; + + @Column() + description: string; + + @Column() + category: string; // e.g., 'income', 'expense', 'transfer' + + @ManyToOne(() => User) + createdBy: User; + + @CreateDateColumn() + createdAt: Date; + + @OneToMany(() => LedgerEntry, ledgerEntry => ledgerEntry.transaction, { cascade: true }) + ledgerEntries: LedgerEntry[]; + + @Column({ default: false }) + reconciled: boolean; + + @Column({ type: 'json', nullable: true }) + metadata: any; // Flexible key-value pairs +} \ No newline at end of file diff --git a/src/modules/treasury/services/transaction.service.ts b/src/modules/treasury/services/transaction.service.ts new file mode 100644 index 0000000..3bff7c2 --- /dev/null +++ b/src/modules/treasury/services/transaction.service.ts @@ -0,0 +1,213 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository, In, Between, Like } from 'typeorm'; +import { Transaction } from '../entities/transaction.entity'; +import { LedgerEntry } from '../entities/ledger-entry.entity'; +import { AuditLog } from '../entities/audit-log.entity'; +import { Account } from '../entities/account.entity'; +import { User } from '../../user/entities/user.entity'; + +@Injectable() +export class TransactionService { + constructor( + @InjectRepository(Transaction) + private transactionRepository: Repository, + @InjectRepository(LedgerEntry) + private ledgerEntryRepository: Repository, + @InjectRepository(AuditLog) + private auditLogRepository: Repository, + @InjectRepository(Account) + private accountRepository: Repository, + ) {} + + async createTransaction( + user: User, + date: Date, + description: string, + category: string, + ledgerEntriesData: { accountId: number; type: 'debit' | 'credit'; amount: number }[], + metadata?: any, + ): Promise { + const debitTotal = ledgerEntriesData + .filter(e => e.type === 'debit') + .reduce((sum, e) => sum + e.amount, 0); + const creditTotal = ledgerEntriesData + .filter(e => e.type === 'credit') + .reduce((sum, e) => sum + e.amount, 0); + if (debitTotal !== creditTotal) { + throw new Error('Debits must equal credits for double-entry bookkeeping'); + } + + const transaction = this.transactionRepository.create({ + date, + description, + category, + createdBy: user, + metadata, + ledgerEntries: [], + }); + + for (const entryData of ledgerEntriesData) { + const account = await this.accountRepository.findOneOrFail({ where: { id: entryData.accountId } }); + const ledgerEntry = this.ledgerEntryRepository.create({ + account, + type: entryData.type, + amount: entryData.amount, + }); + transaction.ledgerEntries.push(ledgerEntry); + } + + const savedTransaction = await this.transactionRepository.save(transaction); + await this.logAudit(user, 'Transaction', savedTransaction.id, 'create', { metadata }); + return savedTransaction; + } + + async updateTransaction( + id: number, + user: User, + date?: Date, + description?: string, + category?: string, + ledgerEntriesData?: { accountId: number; type: 'debit' | 'credit'; amount: number }[], + metadata?: any, + ): Promise { + const transaction = await this.transactionRepository.findOneOrFail({ + where: { id }, + relations: ['ledgerEntries', 'ledgerEntries.account'], + }); + const oldData = { ...transaction }; + + if (date) transaction.date = date; + if (description) transaction.description = description; + if (category) transaction.category = category; + if (metadata) transaction.metadata = metadata; + + if (ledgerEntriesData) { + const debitTotal = ledgerEntriesData + .filter(e => e.type === 'debit') + .reduce((sum, e) => sum + e.amount, 0); + const creditTotal = ledgerEntriesData + .filter(e => e.type === 'credit') + .reduce((sum, e) => sum + e.amount, 0); + if (debitTotal !== creditTotal) { + throw new Error('Debits must equal credits for double-entry bookkeeping'); + } + + await this.ledgerEntryRepository.remove(transaction.ledgerEntries); + transaction.ledgerEntries = []; + for (const entryData of ledgerEntriesData) { + const account = await this.accountRepository.findOneOrFail({ where: { id: entryData.accountId } }); + const ledgerEntry = this.ledgerEntryRepository.create({ + account, + type: entryData.type, + amount: entryData.amount, + }); + transaction.ledgerEntries.push(ledgerEntry); + } + } + + const updatedTransaction = await this.transactionRepository.save(transaction); + await this.logAudit(user, 'Transaction', id, 'update', { old: oldData, new: updatedTransaction }); + return updatedTransaction; + } + + async deleteTransaction(id: number, user: User): Promise { + const transaction = await this.transactionRepository.findOneOrFail({ where: { id } }); + await this.transactionRepository.remove(transaction); + await this.logAudit(user, 'Transaction', id, 'delete', { deleted: transaction }); + } + + async findTransactions( + filters: { + startDate?: Date; + endDate?: Date; + category?: string; + accountId?: number; + description?: string; + }, + page: number = 1, + limit: number = 10, + ): Promise<{ transactions: Transaction[]; total: number }> { + const query = this.transactionRepository.createQueryBuilder('transaction') + .leftJoinAndSelect('transaction.ledgerEntries', 'ledgerEntry') + .leftJoinAndSelect('ledgerEntry.account', 'account'); + + if (filters.startDate || filters.endDate) { + query.andWhere('transaction.date BETWEEN :startDate AND :endDate', { + startDate: filters.startDate || new Date(0), + endDate: filters.endDate || new Date(), + }); + } + if (filters.category) { + query.andWhere('transaction.category = :category', { category: filters.category }); + } + if (filters.accountId) { + query.andWhere('ledgerEntry.accountId = :accountId', { accountId: filters.accountId }); + } + if (filters.description) { + query.andWhere('transaction.description LIKE :description', { description: `%${filters.description}%` }); + } + + const [transactions, total] = await query + .skip((page - 1) * limit) + .take(limit) + .getManyAndCount(); + + return { transactions, total }; + } + + async reconcileTransactions(): Promise { + const accounts = await this.accountRepository.find(); + for (const account of accounts) { + const ledgerEntries = await this.ledgerEntryRepository.find({ + where: { account: { id: account.id } }, + relations: ['transaction'], + }); + const debitSum = ledgerEntries + .filter(e => e.type === 'debit') + .reduce((sum, e) => sum + Number(e.amount), 0); + const creditSum = ledgerEntries + .filter(e => e.type === 'credit') + .reduce((sum, e) => sum + Number(e.amount), 0); + const calculatedBalance = creditSum - debitSum; + + if (calculatedBalance !== Number(account.balance)) { + const transactionIds = ledgerEntries.map(e => e.transaction.id); + await this.transactionRepository.update({ id: In(transactionIds) }, { reconciled: false }); + account.balance = calculatedBalance; + await this.accountRepository.save(account); // Update balance per Issue #19 integration + } else { + const transactionIds = ledgerEntries.map(e => e.transaction.id); + await this.transactionRepository.update({ id: In(transactionIds) }, { reconciled: true }); + } + } + } + + async generateReport(startDate: Date, endDate: Date): Promise<{ category: string; totalAmount: number }[]> { + const results = await this.transactionRepository + .createQueryBuilder('transaction') + .select('transaction.category', 'category') + .addSelect('SUM(CASE WHEN ledgerEntry.type = \'credit\' THEN ledgerEntry.amount ELSE -ledgerEntry.amount END)', 'totalAmount') + .leftJoin('transaction.ledgerEntries', 'ledgerEntry') + .where('transaction.date BETWEEN :startDate AND :endDate', { startDate, endDate }) + .groupBy('transaction.category') + .getRawMany(); + + return results.map(row => ({ + category: row.category, + totalAmount: parseFloat(row.totalAmount), + })); + } + + private async logAudit(user: User, entityName: string, entityId: number, action: string, changes: any): Promise { + const auditLog = this.auditLogRepository.create({ + entityName, + entityId, + action, + changes, + user, + timestamp: new Date(), + } as AuditLog); + await this.auditLogRepository.save(auditLog); + } +} \ No newline at end of file diff --git a/src/modules/treasury/treasury.module.ts b/src/modules/treasury/treasury.module.ts index 95f87e3..fa43efe 100644 --- a/src/modules/treasury/treasury.module.ts +++ b/src/modules/treasury/treasury.module.ts @@ -1,4 +1,15 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Transaction } from './entities/transaction.entity'; +import { LedgerEntry } from './entities/ledger-entry.entity'; +import { AuditLog } from './entities/audit-log.entity'; +import { Account } from './entities/account.entity'; +import { TransactionService } from './services/transaction.service'; +import { TransactionController } from './controllers/transaction.controller'; -@Module({}) -export class TreasuryModule {} +@Module({ + imports: [TypeOrmModule.forFeature([Transaction, LedgerEntry, AuditLog, Account])], + providers: [TransactionService], + controllers: [TransactionController], +}) +export class TreasuryModule {} \ No newline at end of file From 4847148e9b4ddae9521e6e1efe924635e6b2ac47 Mon Sep 17 00:00:00 2001 From: Ndifreke Ekanem <111875002+Ndifreke000@users.noreply.github.com> Date: Mon, 28 Apr 2025 10:47:35 +0000 Subject: [PATCH 2/5] No more errors --- src/config/configuration.ts | 10 +- src/config/data-source.ts | 43 +++++- src/config/logging.service.ts | 49 +++---- .../controllers/transaction.controller.ts | 58 ++++++-- .../treasury/entities/account.entity.ts | 2 +- .../treasury/entities/audit-log.entity.ts | 10 +- .../treasury/entities/ledger-entry.entity.ts | 4 +- .../treasury/entities/transaction.entity.ts | 16 ++- .../treasury/services/transaction.service.ts | 131 +++++++++++++----- src/modules/treasury/treasury.module.ts | 22 +-- 10 files changed, 235 insertions(+), 110 deletions(-) diff --git a/src/config/configuration.ts b/src/config/configuration.ts index 22e3e11..ee520b4 100644 --- a/src/config/configuration.ts +++ b/src/config/configuration.ts @@ -2,7 +2,8 @@ import dotenv from 'dotenv'; dotenv.config(); // Helper to parse integers robustly -const toInt = (value, fallback) => { +const toInt = (value: string | undefined, fallback: number): number => { + if (value === undefined) return fallback; const num = parseInt(value, 10); return Number.isNaN(num) ? fallback : num; }; @@ -19,8 +20,11 @@ const config = { name: process.env.DATABASE_NAME || 'budgetchain', }, jwt: { - // Throw error if secret isn't set for security - secret: process.env.JWT_SECRET || (() => { throw new Error('JWT_SECRET is not defined'); })(), + secret: + process.env.JWT_SECRET || + (() => { + throw new Error('JWT_SECRET is not defined'); + })(), expiresIn: process.env.JWT_EXPIRES_IN || '1h', }, }; diff --git a/src/config/data-source.ts b/src/config/data-source.ts index be53dfa..1d7eb74 100644 --- a/src/config/data-source.ts +++ b/src/config/data-source.ts @@ -1,16 +1,45 @@ +// import { DataSource } from 'typeorm'; +// import { ConfigService } from '@nestjs/config'; + +// const configService = new ConfigService(); + +// export default new DataSource({ +// type: 'postgres', +// host: configService.get('database').host as string, +// port: configService.get('database').port as number, +// username: configService.get('database').user as string, +// password: configService.get('database').password as string, +// database: configService.get('database').name as string, +// entities: ['src/modules/**/*.entity.ts'], +// migrations: ['src/migrations/*.ts'], +// synchronize: false, // Use migrations instead of auto-sync +// }); + import { DataSource } from 'typeorm'; import { ConfigService } from '@nestjs/config'; +// Define the structure of the database configuration +interface DatabaseConfig { + host: string; + port: number; + user: string; + password: string; + name: string; +} + const configService = new ConfigService(); +// Retrieve the database configuration with a type assertion +const database = configService.get('database') as DatabaseConfig; + export default new DataSource({ type: 'postgres', - host: configService.get('database').host, - port: configService.get('database').port, - username: configService.get('database').user, - password: configService.get('database').password, - database: configService.get('database').name, + host: database.host, + port: database.port, + username: database.user, + password: database.password, + database: database.name, entities: ['src/modules/**/*.entity.ts'], migrations: ['src/migrations/*.ts'], - synchronize: false // Use migrations instead of auto-sync -}); \ No newline at end of file + synchronize: false, // Use migrations instead of auto-sync +}); diff --git a/src/config/logging.service.ts b/src/config/logging.service.ts index 63badb3..eb7edc5 100644 --- a/src/config/logging.service.ts +++ b/src/config/logging.service.ts @@ -8,47 +8,44 @@ export class LoggingService implements LoggerService { this.context = context; } - log(message: any, ...optionalParams: any[]) { + log(message: unknown, ...optionalParams: unknown[]) { this.printMessage('log', message, ...optionalParams); } - error(message: any, ...optionalParams: any[]) { + error(message: unknown, ...optionalParams: unknown[]) { this.printMessage('error', message, ...optionalParams); } - warn(message: any, ...optionalParams: any[]) { + warn(message: unknown, ...optionalParams: unknown[]) { this.printMessage('warn', message, ...optionalParams); } - debug(message: any, ...optionalParams: any[]) { + debug(message: unknown, ...optionalParams: unknown[]) { this.printMessage('debug', message, ...optionalParams); } - verbose(message: any, ...optionalParams: any[]) { + verbose(message: unknown, ...optionalParams: unknown[]) { this.printMessage('verbose', message, ...optionalParams); } - private printMessage(level: string, message: any, ...optionalParams: any[]) { + private printMessage( + level: 'log' | 'error' | 'warn' | 'debug' | 'verbose', + message: unknown, + ...optionalParams: unknown[] + ) { const timestamp = new Date().toISOString(); - let logMessage = `[${timestamp}] ${level.toUpperCase()} `; - - if (this.context) { - logMessage += `[${this.context}] `; - } - - logMessage += message; - - if (optionalParams.length > 0) { - logMessage += ` ${optionalParams.join(' ')}`; - } - - if (process.env.NODE_ENV === 'production') { - // In production, you might want to send logs to a centralized logging system - // (e.g., using Winston, Morgan, or a cloud logging service) - console.log(logMessage); // Or send to your logging service - } else { - // In development, you can simply print to the console - console.log(logMessage); - } + const contextTag = this.context ? `[${this.context}] ` : ''; + // ensure message is a string + const main = + typeof message === 'string' ? message : JSON.stringify(message, null, 2); + // turn params into strings too + const rest = optionalParams + .map((p) => (typeof p === 'string' ? p : JSON.stringify(p, null, 2))) + .join(' '); + const payload = rest ? `${main} ${rest}` : main; + const output = `[${timestamp}] ${level.toUpperCase()} ${contextTag}${payload}`; + + // console.log only sees a single string, so no spread of any[] + console.log(output); } } diff --git a/src/modules/treasury/controllers/transaction.controller.ts b/src/modules/treasury/controllers/transaction.controller.ts index 04e3df7..39ab1ba 100644 --- a/src/modules/treasury/controllers/transaction.controller.ts +++ b/src/modules/treasury/controllers/transaction.controller.ts @@ -1,8 +1,24 @@ -import { Controller, Post, Get, Put, Delete, Param, Body, Query, UseGuards, Request } from '@nestjs/common'; +import { + Controller, + Post, + Get, + Put, + Delete, + Param, + Body, + Query, + UseGuards, + Request, +} from '@nestjs/common'; import { TransactionService } from '../services/transaction.service'; import { RolesGuard } from '../../../shared/guards/roles.guard'; import { Roles } from '../../../shared/decorators/roles.decorator'; -import { UserRole } from '../../user/entities/user.entity'; +import { UserRole, User } from '../../user/entities/user.entity'; + +// Import Metadata from transaction.service.ts +interface Metadata { + [key: string]: unknown; +} @Controller('transactions') @UseGuards(RolesGuard) @@ -12,13 +28,18 @@ export class TransactionController { @Post() @Roles(UserRole.ADMIN) async createTransaction( - @Request() req, - @Body() body: { + @Request() req: { user: User }, + @Body() + body: { date: Date; description: string; category: string; - ledgerEntries: { accountId: number; type: 'debit' | 'credit'; amount: number }[]; - metadata?: any; + ledgerEntries: { + accountId: number; + type: 'debit' | 'credit'; + amount: number; + }[]; + metadata?: Metadata; }, ) { return this.transactionService.createTransaction( @@ -35,13 +56,18 @@ export class TransactionController { @Roles(UserRole.ADMIN) async updateTransaction( @Param('id') id: number, - @Request() req, - @Body() body: { + @Request() req: { user: User }, + @Body() + body: { date?: Date; description?: string; category?: string; - ledgerEntries?: { accountId: number; type: 'debit' | 'credit'; amount: number }[]; - metadata?: any; + ledgerEntries?: { + accountId: number; + type: 'debit' | 'credit'; + amount: number; + }[]; + metadata?: Metadata; }, ) { return this.transactionService.updateTransaction( @@ -57,7 +83,10 @@ export class TransactionController { @Delete(':id') @Roles(UserRole.ADMIN) - async deleteTransaction(@Param('id') id: number, @Request() req) { + async deleteTransaction( + @Param('id') id: number, + @Request() req: { user: User }, + ) { await this.transactionService.deleteTransaction(id, req.user); return { message: 'Transaction deleted successfully' }; } @@ -86,7 +115,10 @@ export class TransactionController { @Get('report') @Roles(UserRole.USER, UserRole.ADMIN) - async generateReport(@Query('startDate') startDate: Date, @Query('endDate') endDate: Date) { + async generateReport( + @Query('startDate') startDate: Date, + @Query('endDate') endDate: Date, + ) { return this.transactionService.generateReport(startDate, endDate); } -} \ No newline at end of file +} diff --git a/src/modules/treasury/entities/account.entity.ts b/src/modules/treasury/entities/account.entity.ts index 7b1df89..e6232ed 100644 --- a/src/modules/treasury/entities/account.entity.ts +++ b/src/modules/treasury/entities/account.entity.ts @@ -10,4 +10,4 @@ export class Account { @Column('decimal', { precision: 15, scale: 2, default: 0 }) balance: number; -} \ No newline at end of file +} diff --git a/src/modules/treasury/entities/audit-log.entity.ts b/src/modules/treasury/entities/audit-log.entity.ts index d844392..900e505 100644 --- a/src/modules/treasury/entities/audit-log.entity.ts +++ b/src/modules/treasury/entities/audit-log.entity.ts @@ -1,4 +1,10 @@ -import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn } from 'typeorm'; +import { + Entity, + PrimaryGeneratedColumn, + Column, + ManyToOne, + CreateDateColumn, +} from 'typeorm'; import { User } from '../../user/entities/user.entity'; @Entity('audit_logs') @@ -23,4 +29,4 @@ export class AuditLog { @CreateDateColumn() timestamp: Date; -} \ No newline at end of file +} diff --git a/src/modules/treasury/entities/ledger-entry.entity.ts b/src/modules/treasury/entities/ledger-entry.entity.ts index cd11b1a..1b0339a 100644 --- a/src/modules/treasury/entities/ledger-entry.entity.ts +++ b/src/modules/treasury/entities/ledger-entry.entity.ts @@ -7,7 +7,7 @@ export class LedgerEntry { @PrimaryGeneratedColumn() id: number; - @ManyToOne(() => Transaction, transaction => transaction.ledgerEntries) + @ManyToOne(() => Transaction, (transaction) => transaction.ledgerEntries) transaction: Transaction; @ManyToOne(() => Account) @@ -18,4 +18,4 @@ export class LedgerEntry { @Column('decimal', { precision: 15, scale: 2 }) amount: number; -} \ No newline at end of file +} diff --git a/src/modules/treasury/entities/transaction.entity.ts b/src/modules/treasury/entities/transaction.entity.ts index f1fe51c..f20dd95 100644 --- a/src/modules/treasury/entities/transaction.entity.ts +++ b/src/modules/treasury/entities/transaction.entity.ts @@ -1,4 +1,12 @@ -import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, CreateDateColumn, Index } from 'typeorm'; +import { + Entity, + PrimaryGeneratedColumn, + Column, + ManyToOne, + OneToMany, + CreateDateColumn, + Index, +} from 'typeorm'; import { User } from '../../user/entities/user.entity'; import { LedgerEntry } from './ledger-entry.entity'; @@ -23,7 +31,9 @@ export class Transaction { @CreateDateColumn() createdAt: Date; - @OneToMany(() => LedgerEntry, ledgerEntry => ledgerEntry.transaction, { cascade: true }) + @OneToMany(() => LedgerEntry, (ledgerEntry) => ledgerEntry.transaction, { + cascade: true, + }) ledgerEntries: LedgerEntry[]; @Column({ default: false }) @@ -31,4 +41,4 @@ export class Transaction { @Column({ type: 'json', nullable: true }) metadata: any; // Flexible key-value pairs -} \ No newline at end of file +} diff --git a/src/modules/treasury/services/transaction.service.ts b/src/modules/treasury/services/transaction.service.ts index 3bff7c2..71ad0ff 100644 --- a/src/modules/treasury/services/transaction.service.ts +++ b/src/modules/treasury/services/transaction.service.ts @@ -1,12 +1,23 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository, In, Between, Like } from 'typeorm'; +import { Repository, In } from 'typeorm'; import { Transaction } from '../entities/transaction.entity'; import { LedgerEntry } from '../entities/ledger-entry.entity'; import { AuditLog } from '../entities/audit-log.entity'; import { Account } from '../entities/account.entity'; import { User } from '../../user/entities/user.entity'; +// Interface for metadata to replace `any` +interface Metadata { + [key: string]: unknown; +} + +// Interface for raw query results in generateReport +interface ReportRow { + category: string; + totalAmount: string; // Raw query returns string for numeric aggregates +} + @Injectable() export class TransactionService { constructor( @@ -25,14 +36,18 @@ export class TransactionService { date: Date, description: string, category: string, - ledgerEntriesData: { accountId: number; type: 'debit' | 'credit'; amount: number }[], - metadata?: any, + ledgerEntriesData: { + accountId: number; + type: 'debit' | 'credit'; + amount: number; + }[], + metadata?: Metadata, ): Promise { const debitTotal = ledgerEntriesData - .filter(e => e.type === 'debit') + .filter((e) => e.type === 'debit') .reduce((sum, e) => sum + e.amount, 0); const creditTotal = ledgerEntriesData - .filter(e => e.type === 'credit') + .filter((e) => e.type === 'credit') .reduce((sum, e) => sum + e.amount, 0); if (debitTotal !== creditTotal) { throw new Error('Debits must equal credits for double-entry bookkeeping'); @@ -48,7 +63,9 @@ export class TransactionService { }); for (const entryData of ledgerEntriesData) { - const account = await this.accountRepository.findOneOrFail({ where: { id: entryData.accountId } }); + const account = await this.accountRepository.findOneOrFail({ + where: { id: entryData.accountId }, + }); const ledgerEntry = this.ledgerEntryRepository.create({ account, type: entryData.type, @@ -58,7 +75,9 @@ export class TransactionService { } const savedTransaction = await this.transactionRepository.save(transaction); - await this.logAudit(user, 'Transaction', savedTransaction.id, 'create', { metadata }); + await this.logAudit(user, 'Transaction', savedTransaction.id, 'create', { + metadata, + }); return savedTransaction; } @@ -68,8 +87,12 @@ export class TransactionService { date?: Date, description?: string, category?: string, - ledgerEntriesData?: { accountId: number; type: 'debit' | 'credit'; amount: number }[], - metadata?: any, + ledgerEntriesData?: { + accountId: number; + type: 'debit' | 'credit'; + amount: number; + }[], + metadata?: Metadata, ): Promise { const transaction = await this.transactionRepository.findOneOrFail({ where: { id }, @@ -84,19 +107,23 @@ export class TransactionService { if (ledgerEntriesData) { const debitTotal = ledgerEntriesData - .filter(e => e.type === 'debit') + .filter((e) => e.type === 'debit') .reduce((sum, e) => sum + e.amount, 0); const creditTotal = ledgerEntriesData - .filter(e => e.type === 'credit') + .filter((e) => e.type === 'credit') .reduce((sum, e) => sum + e.amount, 0); if (debitTotal !== creditTotal) { - throw new Error('Debits must equal credits for double-entry bookkeeping'); + throw new Error( + 'Debits must equal credits for double-entry bookkeeping', + ); } await this.ledgerEntryRepository.remove(transaction.ledgerEntries); transaction.ledgerEntries = []; for (const entryData of ledgerEntriesData) { - const account = await this.accountRepository.findOneOrFail({ where: { id: entryData.accountId } }); + const account = await this.accountRepository.findOneOrFail({ + where: { id: entryData.accountId }, + }); const ledgerEntry = this.ledgerEntryRepository.create({ account, type: entryData.type, @@ -106,15 +133,23 @@ export class TransactionService { } } - const updatedTransaction = await this.transactionRepository.save(transaction); - await this.logAudit(user, 'Transaction', id, 'update', { old: oldData, new: updatedTransaction }); + const updatedTransaction = + await this.transactionRepository.save(transaction); + await this.logAudit(user, 'Transaction', id, 'update', { + old: oldData, + new: updatedTransaction, + }); return updatedTransaction; } async deleteTransaction(id: number, user: User): Promise { - const transaction = await this.transactionRepository.findOneOrFail({ where: { id } }); + const transaction = await this.transactionRepository.findOneOrFail({ + where: { id }, + }); await this.transactionRepository.remove(transaction); - await this.logAudit(user, 'Transaction', id, 'delete', { deleted: transaction }); + await this.logAudit(user, 'Transaction', id, 'delete', { + deleted: transaction, + }); } async findTransactions( @@ -128,7 +163,8 @@ export class TransactionService { page: number = 1, limit: number = 10, ): Promise<{ transactions: Transaction[]; total: number }> { - const query = this.transactionRepository.createQueryBuilder('transaction') + const query = this.transactionRepository + .createQueryBuilder('transaction') .leftJoinAndSelect('transaction.ledgerEntries', 'ledgerEntry') .leftJoinAndSelect('ledgerEntry.account', 'account'); @@ -139,13 +175,19 @@ export class TransactionService { }); } if (filters.category) { - query.andWhere('transaction.category = :category', { category: filters.category }); + query.andWhere('transaction.category = :category', { + category: filters.category, + }); } if (filters.accountId) { - query.andWhere('ledgerEntry.accountId = :accountId', { accountId: filters.accountId }); + query.andWhere('ledgerEntry.accountId = :accountId', { + accountId: filters.accountId, + }); } if (filters.description) { - query.andWhere('transaction.description LIKE :description', { description: `%${filters.description}%` }); + query.andWhere('transaction.description LIKE :description', { + description: `%${filters.description}%`, + }); } const [transactions, total] = await query @@ -164,42 +206,63 @@ export class TransactionService { relations: ['transaction'], }); const debitSum = ledgerEntries - .filter(e => e.type === 'debit') + .filter((e) => e.type === 'debit') .reduce((sum, e) => sum + Number(e.amount), 0); const creditSum = ledgerEntries - .filter(e => e.type === 'credit') + .filter((e) => e.type === 'credit') .reduce((sum, e) => sum + Number(e.amount), 0); const calculatedBalance = creditSum - debitSum; if (calculatedBalance !== Number(account.balance)) { - const transactionIds = ledgerEntries.map(e => e.transaction.id); - await this.transactionRepository.update({ id: In(transactionIds) }, { reconciled: false }); + const transactionIds = ledgerEntries.map((e) => e.transaction.id); + await this.transactionRepository.update( + { id: In(transactionIds) }, + { reconciled: false }, + ); account.balance = calculatedBalance; await this.accountRepository.save(account); // Update balance per Issue #19 integration } else { - const transactionIds = ledgerEntries.map(e => e.transaction.id); - await this.transactionRepository.update({ id: In(transactionIds) }, { reconciled: true }); + const transactionIds = ledgerEntries.map((e) => e.transaction.id); + await this.transactionRepository.update( + { id: In(transactionIds) }, + { reconciled: true }, + ); } } } - async generateReport(startDate: Date, endDate: Date): Promise<{ category: string; totalAmount: number }[]> { + async generateReport( + startDate: Date, + endDate: Date, + ): Promise<{ category: string; totalAmount: number }[]> { const results = await this.transactionRepository .createQueryBuilder('transaction') .select('transaction.category', 'category') - .addSelect('SUM(CASE WHEN ledgerEntry.type = \'credit\' THEN ledgerEntry.amount ELSE -ledgerEntry.amount END)', 'totalAmount') + .addSelect( + "SUM(CASE WHEN ledgerEntry.type = 'credit' THEN ledgerEntry.amount ELSE -ledgerEntry.amount END)", + 'totalAmount', + ) .leftJoin('transaction.ledgerEntries', 'ledgerEntry') - .where('transaction.date BETWEEN :startDate AND :endDate', { startDate, endDate }) + .where('transaction.date BETWEEN :startDate AND :endDate', { + startDate, + endDate, + }) .groupBy('transaction.category') - .getRawMany(); + .getRawMany(); - return results.map(row => ({ + return results.map((row) => ({ category: row.category, totalAmount: parseFloat(row.totalAmount), })); } - private async logAudit(user: User, entityName: string, entityId: number, action: string, changes: any): Promise { + private async logAudit( + user: User, + entityName: string, + entityId: number, + action: string, + changes: Record, + ): Promise { const auditLog = this.auditLogRepository.create({ entityName, entityId, @@ -210,4 +273,4 @@ export class TransactionService { } as AuditLog); await this.auditLogRepository.save(auditLog); } -} \ No newline at end of file +} diff --git a/src/modules/treasury/treasury.module.ts b/src/modules/treasury/treasury.module.ts index b4958cf..3ea344c 100644 --- a/src/modules/treasury/treasury.module.ts +++ b/src/modules/treasury/treasury.module.ts @@ -19,26 +19,9 @@ import { TransactionController } from './controllers/transaction.controller'; @Module({ imports: [ - TypeOrmModule.forFeature([ - Treasury, - Asset, - Transaction, - Budget, - Allocation, - RiskAssessment, - AuditLog, - ]), - ], - providers: [ - TreasuryRepositoryImpl, - AssetRepositoryImpl, - TransactionRepositoryImpl, - BudgetRepositoryImpl, - AllocationRepositoryImpl, - RiskAssessmentRepositoryImpl, - AuditLogRepositoryImpl, - TransactionService, + TypeOrmModule.forFeature([Transaction, LedgerEntry, AuditLog, Account]), ], + providers: [TransactionService], controllers: [TransactionController], exports: [ TreasuryRepositoryImpl, @@ -51,3 +34,4 @@ import { TransactionController } from './controllers/transaction.controller'; ], }) export class TreasuryModule {} + From da2f6e09ffd64114a3e4f826ab81bbc390d917d0 Mon Sep 17 00:00:00 2001 From: Ndifreke Ekanem <111875002+Ndifreke000@users.noreply.github.com> Date: Mon, 28 Apr 2025 13:29:40 +0000 Subject: [PATCH 3/5] efb --- src/config/configuration.ts | 7 +- src/config/logging.service.ts | 2 +- .../controllers/transaction.controller.ts | 14 ++-- .../treasury/entities/ledger-entry.entity.ts | 2 +- .../treasury/entities/transaction.entity.ts | 2 +- .../treasury/services/transaction.service.ts | 38 +++++----- src/modules/treasury/treasury.module.ts | 73 ++----------------- 7 files changed, 39 insertions(+), 99 deletions(-) diff --git a/src/config/configuration.ts b/src/config/configuration.ts index ee520b4..c6ce7ad 100644 --- a/src/config/configuration.ts +++ b/src/config/configuration.ts @@ -8,7 +8,8 @@ const toInt = (value: string | undefined, fallback: number): number => { return Number.isNaN(num) ? fallback : num; }; -const config = { +// Export a factory function +export default () => ({ env: process.env.NODE_ENV || 'development', name: process.env.APP_NAME || 'budget-chain-backend', port: toInt(process.env.PORT, 3000), @@ -27,6 +28,4 @@ const config = { })(), expiresIn: process.env.JWT_EXPIRES_IN || '1h', }, -}; - -export default config; +}); diff --git a/src/config/logging.service.ts b/src/config/logging.service.ts index eb7edc5..1799dd6 100644 --- a/src/config/logging.service.ts +++ b/src/config/logging.service.ts @@ -40,7 +40,7 @@ export class LoggingService implements LoggerService { typeof message === 'string' ? message : JSON.stringify(message, null, 2); // turn params into strings too const rest = optionalParams - .map((p) => (typeof p === 'string' ? p : JSON.stringify(p, null, 2))) + .map(p => (typeof p === 'string' ? p : JSON.stringify(p, null, 2))) .join(' '); const payload = rest ? `${main} ${rest}` : main; const output = `[${timestamp}] ${level.toUpperCase()} ${contextTag}${payload}`; diff --git a/src/modules/treasury/controllers/transaction.controller.ts b/src/modules/treasury/controllers/transaction.controller.ts index 39ab1ba..d00ce48 100644 --- a/src/modules/treasury/controllers/transaction.controller.ts +++ b/src/modules/treasury/controllers/transaction.controller.ts @@ -40,7 +40,7 @@ export class TransactionController { amount: number; }[]; metadata?: Metadata; - }, + } ) { return this.transactionService.createTransaction( req.user, @@ -48,7 +48,7 @@ export class TransactionController { body.description, body.category, body.ledgerEntries, - body.metadata, + body.metadata ); } @@ -68,7 +68,7 @@ export class TransactionController { amount: number; }[]; metadata?: Metadata; - }, + } ) { return this.transactionService.updateTransaction( id, @@ -77,7 +77,7 @@ export class TransactionController { body.description, body.category, body.ledgerEntries, - body.metadata, + body.metadata ); } @@ -85,7 +85,7 @@ export class TransactionController { @Roles(UserRole.ADMIN) async deleteTransaction( @Param('id') id: number, - @Request() req: { user: User }, + @Request() req: { user: User } ) { await this.transactionService.deleteTransaction(id, req.user); return { message: 'Transaction deleted successfully' }; @@ -100,7 +100,7 @@ export class TransactionController { @Query('accountId') accountId?: number, @Query('description') description?: string, @Query('page') page = 1, - @Query('limit') limit = 10, + @Query('limit') limit = 10 ) { const filters = { startDate, endDate, category, accountId, description }; return this.transactionService.findTransactions(filters, page, limit); @@ -117,7 +117,7 @@ export class TransactionController { @Roles(UserRole.USER, UserRole.ADMIN) async generateReport( @Query('startDate') startDate: Date, - @Query('endDate') endDate: Date, + @Query('endDate') endDate: Date ) { return this.transactionService.generateReport(startDate, endDate); } diff --git a/src/modules/treasury/entities/ledger-entry.entity.ts b/src/modules/treasury/entities/ledger-entry.entity.ts index 1b0339a..aeb664d 100644 --- a/src/modules/treasury/entities/ledger-entry.entity.ts +++ b/src/modules/treasury/entities/ledger-entry.entity.ts @@ -7,7 +7,7 @@ export class LedgerEntry { @PrimaryGeneratedColumn() id: number; - @ManyToOne(() => Transaction, (transaction) => transaction.ledgerEntries) + @ManyToOne(() => Transaction, transaction => transaction.ledgerEntries) transaction: Transaction; @ManyToOne(() => Account) diff --git a/src/modules/treasury/entities/transaction.entity.ts b/src/modules/treasury/entities/transaction.entity.ts index f20dd95..f5e8f38 100644 --- a/src/modules/treasury/entities/transaction.entity.ts +++ b/src/modules/treasury/entities/transaction.entity.ts @@ -31,7 +31,7 @@ export class Transaction { @CreateDateColumn() createdAt: Date; - @OneToMany(() => LedgerEntry, (ledgerEntry) => ledgerEntry.transaction, { + @OneToMany(() => LedgerEntry, ledgerEntry => ledgerEntry.transaction, { cascade: true, }) ledgerEntries: LedgerEntry[]; diff --git a/src/modules/treasury/services/transaction.service.ts b/src/modules/treasury/services/transaction.service.ts index 71ad0ff..21ca050 100644 --- a/src/modules/treasury/services/transaction.service.ts +++ b/src/modules/treasury/services/transaction.service.ts @@ -28,7 +28,7 @@ export class TransactionService { @InjectRepository(AuditLog) private auditLogRepository: Repository, @InjectRepository(Account) - private accountRepository: Repository, + private accountRepository: Repository ) {} async createTransaction( @@ -41,13 +41,13 @@ export class TransactionService { type: 'debit' | 'credit'; amount: number; }[], - metadata?: Metadata, + metadata?: Metadata ): Promise { const debitTotal = ledgerEntriesData - .filter((e) => e.type === 'debit') + .filter(e => e.type === 'debit') .reduce((sum, e) => sum + e.amount, 0); const creditTotal = ledgerEntriesData - .filter((e) => e.type === 'credit') + .filter(e => e.type === 'credit') .reduce((sum, e) => sum + e.amount, 0); if (debitTotal !== creditTotal) { throw new Error('Debits must equal credits for double-entry bookkeeping'); @@ -92,7 +92,7 @@ export class TransactionService { type: 'debit' | 'credit'; amount: number; }[], - metadata?: Metadata, + metadata?: Metadata ): Promise { const transaction = await this.transactionRepository.findOneOrFail({ where: { id }, @@ -107,14 +107,14 @@ export class TransactionService { if (ledgerEntriesData) { const debitTotal = ledgerEntriesData - .filter((e) => e.type === 'debit') + .filter(e => e.type === 'debit') .reduce((sum, e) => sum + e.amount, 0); const creditTotal = ledgerEntriesData - .filter((e) => e.type === 'credit') + .filter(e => e.type === 'credit') .reduce((sum, e) => sum + e.amount, 0); if (debitTotal !== creditTotal) { throw new Error( - 'Debits must equal credits for double-entry bookkeeping', + 'Debits must equal credits for double-entry bookkeeping' ); } @@ -161,7 +161,7 @@ export class TransactionService { description?: string; }, page: number = 1, - limit: number = 10, + limit: number = 10 ): Promise<{ transactions: Transaction[]; total: number }> { const query = this.transactionRepository .createQueryBuilder('transaction') @@ -206,26 +206,26 @@ export class TransactionService { relations: ['transaction'], }); const debitSum = ledgerEntries - .filter((e) => e.type === 'debit') + .filter(e => e.type === 'debit') .reduce((sum, e) => sum + Number(e.amount), 0); const creditSum = ledgerEntries - .filter((e) => e.type === 'credit') + .filter(e => e.type === 'credit') .reduce((sum, e) => sum + Number(e.amount), 0); const calculatedBalance = creditSum - debitSum; if (calculatedBalance !== Number(account.balance)) { - const transactionIds = ledgerEntries.map((e) => e.transaction.id); + const transactionIds = ledgerEntries.map(e => e.transaction.id); await this.transactionRepository.update( { id: In(transactionIds) }, - { reconciled: false }, + { reconciled: false } ); account.balance = calculatedBalance; await this.accountRepository.save(account); // Update balance per Issue #19 integration } else { - const transactionIds = ledgerEntries.map((e) => e.transaction.id); + const transactionIds = ledgerEntries.map(e => e.transaction.id); await this.transactionRepository.update( { id: In(transactionIds) }, - { reconciled: true }, + { reconciled: true } ); } } @@ -233,14 +233,14 @@ export class TransactionService { async generateReport( startDate: Date, - endDate: Date, + endDate: Date ): Promise<{ category: string; totalAmount: number }[]> { const results = await this.transactionRepository .createQueryBuilder('transaction') .select('transaction.category', 'category') .addSelect( "SUM(CASE WHEN ledgerEntry.type = 'credit' THEN ledgerEntry.amount ELSE -ledgerEntry.amount END)", - 'totalAmount', + 'totalAmount' ) .leftJoin('transaction.ledgerEntries', 'ledgerEntry') .where('transaction.date BETWEEN :startDate AND :endDate', { @@ -250,7 +250,7 @@ export class TransactionService { .groupBy('transaction.category') .getRawMany(); - return results.map((row) => ({ + return results.map(row => ({ category: row.category, totalAmount: parseFloat(row.totalAmount), })); @@ -261,7 +261,7 @@ export class TransactionService { entityName: string, entityId: number, action: string, - changes: Record, + changes: Record ): Promise { const auditLog = this.auditLogRepository.create({ entityName, diff --git a/src/modules/treasury/treasury.module.ts b/src/modules/treasury/treasury.module.ts index 45fdf9c..e469a30 100644 --- a/src/modules/treasury/treasury.module.ts +++ b/src/modules/treasury/treasury.module.ts @@ -1,42 +1,11 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { ConfigModule } from '../../config/config.module'; -import { BlockchainModule } from '../blockchain/blockchain.module'; - -// Entities -import { Asset } from './entities/asset.entity'; -import { AssetTransaction } from './entities/asset-transaction.entity'; -import { Budget } from './entities/budget.entity'; -import { Allocation } from './entities/allocation.entity'; -import { AllocationTransaction } from './entities/allocation-transaction.entity'; -import { Treasury } from '../user/entities/treasury.entity'; -import { Transaction } from '../user/entities/transaction.entity'; -import { RiskAssessment } from '../user/entities/risk_assessment.entity'; -import { AuditLog } from '../user/entities/audit_log.entity'; - -// Controllers -import { TreasuryController } from './controllers/treasury.controller'; -import { TreasuryAssetController } from './controllers/treasury-asset.controller'; -import { TreasuryTransactionController } from './controllers/treasury-transaction.controller'; -import { TreasuryBudgetController } from './controllers/treasury-budget.controller'; -import { TreasuryAllocationController } from './controllers/treasury-allocation.controller'; -import { TransactionController } from './controllers/transaction.controller'; - -// Services & Repositories -import { TreasuryService } from './services/treasury.service'; -import { TreasuryAssetService } from './services/treasury-asset.service'; -import { TreasuryTransactionService } from './services/treasury-transaction.service'; -import { TreasuryBudgetService } from './services/treasury-budget.service'; -import { TreasuryAllocationService } from './services/treasury-allocation.service'; +import { Transaction } from './entities/transaction.entity'; // Updated path +import { LedgerEntry } from './entities/ledger-entry.entity'; // Added import +import { AuditLog } from './entities/audit-log.entity'; // Updated path +import { Account } from './entities/account.entity'; // Added import import { TransactionService } from './services/transaction.service'; - -import { TreasuryRepositoryImpl } from '../repository/treasury.repository'; -import { AssetRepositoryImpl } from '../repository/asset.repository'; -import { TransactionRepositoryImpl } from '../repository/transaction.repository'; -import { BudgetRepositoryImpl } from '../repository/budget.repository'; -import { AllocationRepositoryImpl } from '../repository/allocation.repository'; -import { RiskAssessmentRepositoryImpl } from '../repository/risk_assessment.repository'; -import { AuditLogRepositoryImpl } from '../repository/audit_log.repository'; +import { TransactionController } from './controllers/transaction.controller'; @Module({ imports: [ @@ -62,35 +31,7 @@ import { AuditLogRepositoryImpl } from '../repository/audit_log.repository'; TreasuryAllocationController, TransactionController, ], - providers: [ - TreasuryService, - TreasuryAssetService, - TreasuryTransactionService, - TreasuryBudgetService, - TreasuryAllocationService, - TransactionService, - TreasuryRepositoryImpl, - AssetRepositoryImpl, - TransactionRepositoryImpl, - BudgetRepositoryImpl, - AllocationRepositoryImpl, - RiskAssessmentRepositoryImpl, - AuditLogRepositoryImpl, - ], - exports: [ - TreasuryService, - TreasuryAssetService, - TreasuryTransactionService, - TreasuryBudgetService, - TreasuryAllocationService, - TransactionService, - TreasuryRepositoryImpl, - AssetRepositoryImpl, - TransactionRepositoryImpl, - BudgetRepositoryImpl, - AllocationRepositoryImpl, - RiskAssessmentRepositoryImpl, - AuditLogRepositoryImpl, - ], + providers: [TransactionService], + controllers: [TransactionController], }) export class TreasuryModule {} From 85736fccf6f47abdadaf0bd3f967a95c6ab36a19 Mon Sep 17 00:00:00 2001 From: Ndifreke Ekanem <111875002+Ndifreke000@users.noreply.github.com> Date: Mon, 28 Apr 2025 13:38:30 +0000 Subject: [PATCH 4/5] jt --- src/modules/treasury/treasury.module.ts | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/src/modules/treasury/treasury.module.ts b/src/modules/treasury/treasury.module.ts index e469a30..6388fa8 100644 --- a/src/modules/treasury/treasury.module.ts +++ b/src/modules/treasury/treasury.module.ts @@ -9,29 +9,9 @@ import { TransactionController } from './controllers/transaction.controller'; @Module({ imports: [ - TypeOrmModule.forFeature([ - Asset, - AssetTransaction, - Budget, - Allocation, - AllocationTransaction, - Treasury, - Transaction, - RiskAssessment, - AuditLog, - ]), - ConfigModule, - BlockchainModule, - ], - controllers: [ - TreasuryController, - TreasuryAssetController, - TreasuryTransactionController, - TreasuryBudgetController, - TreasuryAllocationController, - TransactionController, + TypeOrmModule.forFeature([Transaction, LedgerEntry, AuditLog, Account]), ], providers: [TransactionService], controllers: [TransactionController], }) -export class TreasuryModule {} +export class TreasuryModule {} \ No newline at end of file From d95192780910205801c9c2b91a8fddd21c69c9b1 Mon Sep 17 00:00:00 2001 From: Ndifreke Ekanem <111875002+Ndifreke000@users.noreply.github.com> Date: Mon, 28 Apr 2025 14:54:46 +0000 Subject: [PATCH 5/5] Done --- package-lock.json | 21 ++++++++++----------- package.json | 2 +- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 771bcba..df8716f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "passport-custom": "^1.1.1", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", - "pg": "^8.15.5", + "pg": "^8.15.6", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", "starknet": "^6.24.1", @@ -8737,13 +8737,13 @@ "dev": true }, "node_modules/pg": { - "version": "8.15.5", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.15.5.tgz", - "integrity": "sha512-EpAhHFQc+aH9VfeffWIVC+XXk6lmAhS9W1FxtxcPXs94yxhrI1I6w/zkWfIOII/OkBv3Be04X3xMOj0kQ78l6w==", + "version": "8.15.6", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.15.6.tgz", + "integrity": "sha512-yvao7YI3GdmmrslNVsZgx9PfntfWrnXwtR+K/DjI0I/sTKif4Z623um+sjVZ1hk5670B+ODjvHDAckKdjmPTsg==", "license": "MIT", "dependencies": { "pg-connection-string": "^2.8.5", - "pg-pool": "^3.9.5", + "pg-pool": "^3.9.6", "pg-protocol": "^1.9.5", "pg-types": "^2.1.0", "pgpass": "1.x" @@ -8785,9 +8785,9 @@ } }, "node_modules/pg-pool": { - "version": "3.9.5", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.9.5.tgz", - "integrity": "sha512-DxyAlOgvUzRFpFAZjbCc8fUfG7BcETDHgepFPf724B0i08k9PAiZV1tkGGgQIL0jbMEuR9jW1YN7eX+WgXxCsQ==", + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.9.6.tgz", + "integrity": "sha512-rFen0G7adh1YmgvrmE5IPIqbb+IgEzENUm+tzm6MLLDSlPRoZVhzU1WdML9PV2W5GOdRA9qBKURlbt1OsXOsPw==", "license": "MIT", "peerDependencies": { "pg": ">=8.0" @@ -10948,17 +10948,16 @@ } } }, - "node_modules/typeorm-ts-node-commonjs": { + "node_modules/typeorm-ts-node-commonjs": { "version": "0.3.20", "resolved": "https://registry.npmjs.org/typeorm-ts-node-commonjs/-/typeorm-ts-node-commonjs-0.3.20.tgz", "integrity": "sha512-lXjve7w7OcF3s5+dHnCsrBjUTukpVeiS0bDe5KDXWcDx8TyRW0GTTg9kjWgHzFgHgBIBBu4WGXM0iuGpEgaV9g==", "dev": true, "license": "UNLICENSED", "bin": { - "typeorm-ts-node-commonjs": "wrapper.sh" + "typeorm-ts-node-commonjs": "wrapper.sh" } }, - "node_modules/typeorm/node_modules/ansis": { "version": "3.17.0", "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", diff --git a/package.json b/package.json index b1ce390..ace989f 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "passport-custom": "^1.1.1", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", - "pg": "^8.15.5", + "pg": "^8.15.6", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", "starknet": "^6.24.1",