From c928e08aef75bb851efd272217d4b56f79a79525 Mon Sep 17 00:00:00 2001 From: Arise -D Nexus Date: Fri, 1 May 2026 18:52:19 +0100 Subject: [PATCH 1/3] chore: remove unused transactions.dto.ts after conflict resolution --- src/transactions/dto/transactions.dto.ts | 73 ------------------------ 1 file changed, 73 deletions(-) delete mode 100644 src/transactions/dto/transactions.dto.ts diff --git a/src/transactions/dto/transactions.dto.ts b/src/transactions/dto/transactions.dto.ts deleted file mode 100644 index 2e48d0a..0000000 --- a/src/transactions/dto/transactions.dto.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Type } from 'class-transformer'; -import { - IsDate, - IsEnum, - IsInt, - IsOptional, - IsString, - IsUUID, - Max, - Min, -} from 'class-validator'; -import { TransactionStatus, TransactionType } from '../../types/prisma.types'; - -export enum TransactionSortField { - CREATED_AT = 'createdAt', - AMOUNT = 'amount', - STATUS = 'status', - TYPE = 'type', -} - -export enum SortOrder { - ASC = 'asc', - DESC = 'desc', -} - -export class TransactionHistoryQueryDto { - @IsOptional() - @IsEnum(TransactionStatus) - status?: TransactionStatus; - - @IsOptional() - @IsEnum(TransactionType) - type?: TransactionType; - - @IsOptional() - @Type(() => Date) - @IsDate() - startDate?: Date; - - @IsOptional() - @Type(() => Date) - @IsDate() - endDate?: Date; - - @IsOptional() - @IsUUID('4') - propertyId?: string; - - @IsOptional() - @IsUUID('4') - userId?: string; - - @IsOptional() - @Type(() => Number) - @IsInt() - @Min(1) - page: number = 1; - - @IsOptional() - @Type(() => Number) - @IsInt() - @Min(1) - @Max(100) - limit: number = 20; - - @IsOptional() - @IsEnum(TransactionSortField) - sortBy: TransactionSortField = TransactionSortField.CREATED_AT; - - @IsOptional() - @IsEnum(SortOrder) - sortOrder: SortOrder = SortOrder.DESC; -} From cae49efcab302296a35cd10881acdb264d9fb789 Mon Sep 17 00:00:00 2001 From: Arise -D Nexus Date: Sun, 3 May 2026 09:42:23 +0100 Subject: [PATCH 2/3] #352 Add Transaction History --- nest | 0 package-lock.json | 420 ++++++++++-------- prisma/schema.prisma | 262 +++++------ propchain-backend@1.0.0 | 0 src/admin/admin.controller.ts | 3 +- src/admin/admin.service.ts | 8 +- src/notifications/notifications.service.ts | 2 +- src/transactions/dto/timeline.dto.ts | 4 + src/transactions/transactions.controller.ts | 15 + src/transactions/transactions.service.spec.ts | 39 ++ src/transactions/transactions.service.ts | 78 +++- src/types/prisma.types.ts | 10 + 12 files changed, 515 insertions(+), 326 deletions(-) create mode 100644 nest create mode 100644 propchain-backend@1.0.0 diff --git a/nest b/nest new file mode 100644 index 0000000..e69de29 diff --git a/package-lock.json b/package-lock.json index bcb640a..0baa7e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -750,7 +750,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -3351,7 +3350,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.22.tgz", "integrity": "sha512-fxJ4v85nDHaqT1PmfNCQ37b/jcv2OojtXTaK1P2uAXhzLf9qq6WNUOFvxBrV4fhQek1EQoT1o9oj5xAZmv3NRw==", "license": "MIT", - "peer": true, "dependencies": { "file-type": "20.4.1", "iterare": "1.2.1", @@ -3410,7 +3408,6 @@ "integrity": "sha512-6IX9+VwjiKtCjx+mXVPncpkQ5ZjKfmssOZPFexmT+6T9H9wZ3svpYACAo7+9e7Nr9DZSoRZw3pffkJP7Z0UjaA==", "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@nuxtjs/opencollective": "0.3.2", "fast-safe-stringify": "2.1.1", @@ -3448,7 +3445,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/graphql/-/graphql-12.2.2.tgz", "integrity": "sha512-lUDy/1uqbRA1kBKpXcmY0aHhcPbfeG52Wg5+9Jzd1d57dwSjCAmuO+mWy5jz9ugopVCZeK0S/kdAMvA+r9fNdA==", "license": "MIT", - "peer": true, "dependencies": { "@graphql-tools/merge": "9.0.11", "@graphql-tools/schema": "10.0.10", @@ -3618,7 +3614,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.22.tgz", "integrity": "sha512-ySSq7Py/DFozzZdNDH67m/vHoeVdphDniWBnl6q5QVoXldDdrZIHLXLRMPayTDh5A95nt7jjJzmD4qpTbNQ6tA==", "license": "MIT", - "peer": true, "dependencies": { "body-parser": "1.20.4", "cors": "2.8.5", @@ -4071,7 +4066,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.4.22.tgz", "integrity": "sha512-OLd4i0Faq7vgdtB5vVUrJ54hWEtcXy9poJ6n7kbbh/5ms+KffUl+wwGsbe7uSXLrkoyI8xXU6fZPkFArI+XiRg==", "license": "MIT", - "peer": true, "dependencies": { "iterare": "1.2.1", "object-hash": "3.0.0", @@ -4332,7 +4326,6 @@ "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.12.1.tgz", "integrity": "sha512-7aPGWeqA3uFm43o19umzdl16CEjK/JQGtSXVPevplTaOU3VJA/rseBC1QvYUz9lLDIMBimc4SW/zrW4S89BaCA==", "license": "MIT", - "peer": true, "dependencies": { "cluster-key-slot": "1.1.2" }, @@ -4600,7 +4593,6 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -4767,7 +4759,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -5009,7 +5000,6 @@ "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", @@ -5410,6 +5400,7 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", + "peer": true, "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" @@ -5423,6 +5414,7 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -5433,7 +5425,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "devOptional": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5447,6 +5438,7 @@ "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.13.0" }, @@ -5483,7 +5475,6 @@ "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -5950,7 +5941,6 @@ "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", "license": "Apache-2.0", - "peer": true, "peerDependencies": { "bare-abort-controller": "*" }, @@ -6227,7 +6217,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -6315,7 +6304,6 @@ "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.76.3.tgz", "integrity": "sha512-UBICMeWLYa+Dz7IGBNebXApQ1OIxNd4t6nX+AFPQ5gFA3sosW34PENe8Q1cvbjcbMTaU3xrKPorb6tM1czRSsw==", "license": "MIT", - "peer": true, "dependencies": { "cron-parser": "4.9.0", "ioredis": "5.10.1", @@ -6412,7 +6400,6 @@ "resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-7.2.8.tgz", "integrity": "sha512-0HDaDLBBY/maa/LmUVAr70XUOwsiQD+jyzCBjmUErYZUKdMS9dT59PqW59PpVqfGM7ve6H0J6307JTpkCYefHQ==", "license": "MIT", - "peer": true, "dependencies": { "@cacheable/utils": "^2.3.3", "keyv": "^5.5.5" @@ -6444,7 +6431,6 @@ "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", "license": "MIT", - "peer": true, "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -6819,15 +6805,13 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/class-validator": { "version": "0.14.4", "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.4.tgz", "integrity": "sha512-AwNusCCam51q703dW82x95tOqQp6oC9HNUl724KxJJOfnKscI8dOloXFgyez7LbTTKWuRBA37FScqVbJEoq8Yw==", "license": "MIT", - "peer": true, "dependencies": { "@types/validator": "^13.15.3", "libphonenumber-js": "^1.11.1", @@ -7094,6 +7078,7 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -7132,6 +7117,7 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.6.0" } @@ -7371,49 +7357,70 @@ "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==", "license": "MIT" }, + "node_modules/cssnano": { + "version": "7.1.8", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.8.tgz", + "integrity": "sha512-OGXtXqXmwEoIGfXM2QoD35vweUAtx+J8ZvLSZHOEV0Jv9Hs9ScTdGGjRzZXun5J4PEZhEoytKig2O2NR8NXxKw==", + "license": "MIT", + "optional": true, + "dependencies": { + "cssnano-preset-default": "^7.0.16", + "lilconfig": "^3.1.3" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.5.13" + } + }, "node_modules/cssnano-preset-default": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.15.tgz", - "integrity": "sha512-60kx7lJ40//HA85cIfQXSOJFby2D2V1pOMNHVCxue3KFWCjRzmiQyL9OvI+NAhwUlaojOfF9eK3nGvrJLCBUfQ==", + "version": "7.0.16", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.16.tgz", + "integrity": "sha512-W0hiFi/ca/u2OTptL11OdApaz1vh9jyfd2ku9dMjou6KdpdgbMTagaXHKNl5kaEyRSCu9GIIaPRp5YLdqRAZMw==", "license": "MIT", "optional": true, "dependencies": { "browserslist": "^4.28.2", "css-declaration-sorter": "^7.2.0", - "cssnano-utils": "^5.0.2", + "cssnano-utils": "^5.0.3", "postcss-calc": "^10.1.1", - "postcss-colormin": "^7.0.9", - "postcss-convert-values": "^7.0.11", - "postcss-discard-comments": "^7.0.7", - "postcss-discard-duplicates": "^7.0.3", - "postcss-discard-empty": "^7.0.2", - "postcss-discard-overridden": "^7.0.2", - "postcss-merge-longhand": "^7.0.6", - "postcss-merge-rules": "^7.0.10", - "postcss-minify-font-values": "^7.0.2", - "postcss-minify-gradients": "^7.0.4", - "postcss-minify-params": "^7.0.8", - "postcss-minify-selectors": "^7.1.0", - "postcss-normalize-charset": "^7.0.2", - "postcss-normalize-display-values": "^7.0.2", - "postcss-normalize-positions": "^7.0.3", - "postcss-normalize-repeat-style": "^7.0.3", - "postcss-normalize-string": "^7.0.2", - "postcss-normalize-timing-functions": "^7.0.2", - "postcss-normalize-unicode": "^7.0.8", - "postcss-normalize-url": "^7.0.2", - "postcss-normalize-whitespace": "^7.0.2", - "postcss-ordered-values": "^7.0.3", - "postcss-reduce-initial": "^7.0.8", - "postcss-reduce-transforms": "^7.0.2", - "postcss-svgo": "^7.1.2", - "postcss-unique-selectors": "^7.0.6" + "postcss-colormin": "^7.0.10", + "postcss-convert-values": "^7.0.12", + "postcss-discard-comments": "^7.0.8", + "postcss-discard-duplicates": "^7.0.4", + "postcss-discard-empty": "^7.0.3", + "postcss-discard-overridden": "^7.0.3", + "postcss-merge-longhand": "^7.0.7", + "postcss-merge-rules": "^7.0.11", + "postcss-minify-font-values": "^7.0.3", + "postcss-minify-gradients": "^7.0.5", + "postcss-minify-params": "^7.0.9", + "postcss-minify-selectors": "^7.1.1", + "postcss-normalize-charset": "^7.0.3", + "postcss-normalize-display-values": "^7.0.3", + "postcss-normalize-positions": "^7.0.4", + "postcss-normalize-repeat-style": "^7.0.4", + "postcss-normalize-string": "^7.0.3", + "postcss-normalize-timing-functions": "^7.0.3", + "postcss-normalize-unicode": "^7.0.9", + "postcss-normalize-url": "^7.0.3", + "postcss-normalize-whitespace": "^7.0.3", + "postcss-ordered-values": "^7.0.4", + "postcss-reduce-initial": "^7.0.9", + "postcss-reduce-transforms": "^7.0.3", + "postcss-svgo": "^7.1.3", + "postcss-unique-selectors": "^7.0.7" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/cssnano-preset-lite": { @@ -7436,16 +7443,16 @@ } }, "node_modules/cssnano-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.2.tgz", - "integrity": "sha512-kt41WLK7FLKfePzPi645Y+/NtW/nNM7Su6nlNUfJyRNW3JcuU3JU7+cWJc+JexTeZ8dRBvFufefdG2XpXkIo0A==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.3.tgz", + "integrity": "sha512-ynIREMICLxkxm7e9bCR9sh75s4Q5drICi0ua1yxo5jH2XPBqSKkl4dOh4EbFqtUmnTMhRffHgYL0EKKkMjtJTg==", "license": "MIT", "optional": true, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/csso": { @@ -8162,7 +8169,8 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/es-object-atoms": { "version": "1.1.1", @@ -8253,7 +8261,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -8310,7 +8317,6 @@ "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -8631,6 +8637,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -8674,6 +8681,7 @@ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", + "peer": true, "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", @@ -8698,6 +8706,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -8714,6 +8723,7 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -8723,6 +8733,7 @@ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", + "peer": true, "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", @@ -8738,6 +8749,7 @@ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", + "peer": true, "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", @@ -8954,6 +8966,7 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", @@ -9249,6 +9262,7 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -9292,7 +9306,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -9570,7 +9583,6 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.2.tgz", "integrity": "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==", "license": "MIT", - "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -10268,7 +10280,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/is-regex": { "version": "1.2.1", @@ -10471,7 +10484,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -11395,7 +11407,6 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", "license": "MIT", - "peer": true, "dependencies": { "@keyv/serialize": "^1.1.1" } @@ -11923,6 +11934,7 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -11997,6 +12009,7 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -12006,6 +12019,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", + "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -12759,7 +12773,6 @@ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.7.tgz", "integrity": "sha512-pkjE4mkBzQjdJT4/UmlKl3pX0rC9fZmjh7c6C9o7lv66Ac6w9WCnzPzhbPNxwZAzlF4mdq4CSWB5+FbK6FWCow==", "license": "MIT-0", - "peer": true, "engines": { "node": ">=6.0.0" } @@ -13225,7 +13238,6 @@ "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", "license": "MIT", - "peer": true, "dependencies": { "passport-strategy": "1.x.x", "pause": "0.0.1", @@ -13403,7 +13415,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.12.0", "pg-pool": "^3.13.0", @@ -13618,6 +13629,35 @@ "node": ">= 0.4" } }, + "node_modules/postcss": { + "version": "8.5.13", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz", + "integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/postcss-calc": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz", @@ -13636,13 +13676,13 @@ } }, "node_modules/postcss-colormin": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.9.tgz", - "integrity": "sha512-EZpoUlmbXQUpe+g4ZaGM2kjGlHrQ7Bjzb3xHcNrC9ysI1tGoib6DAYvxg6aB7MGxsjgLF+Qx/jwZQkJ5cKDvXA==", + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.10.tgz", + "integrity": "sha512-yFr6JezOolHLta/buLE71VKPh2mXursp4saVe98/ol8ZnEWhL+racShqPKlvd/DKWLre/39B6HhcMXf7RZ3hxg==", "license": "MIT", "optional": true, "dependencies": { - "@colordx/core": "^5.2.0", + "@colordx/core": "^5.4.3", "browserslist": "^4.28.2", "caniuse-api": "^3.0.0", "postcss-value-parser": "^4.2.0" @@ -13651,13 +13691,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-convert-values": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.11.tgz", - "integrity": "sha512-H+s7P0f9jJylSysAHs3/5MhAx7GthDO05uw1h56L2xyEqpiLTFLEqBNw3PUYzD5p/AKwWaigCXf6FGELpOw9lw==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.12.tgz", + "integrity": "sha512-xurKu5qqk4viR3Cp3p4xBR4KfnZm4w4ys6+UBwBmeuBSNkH7+DtLnYOYnOffgtE4yx8sH9S1VZ6RAAvROXzP2Q==", "license": "MIT", "optional": true, "dependencies": { @@ -13668,13 +13708,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-discard-comments": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.7.tgz", - "integrity": "sha512-FJhE3fSte7HaRNL4iwD8LTG9vWqj3puxXIdig6LfrFqc1TJRUhY4kXOkeTXZZfTXYny+k+SO7fd2fymj1wduJg==", + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.8.tgz", + "integrity": "sha512-CvvS5S9WrXblFXCEJ9nVo+4z+eA7zSC7Z88V1HEJuwlQhlFnYTIjg1xJY+BCUiG2bvICap2tXii4mP22BD108Q==", "license": "MIT", "optional": true, "dependencies": { @@ -13684,88 +13724,88 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-discard-duplicates": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.3.tgz", - "integrity": "sha512-9cRxXwhEM/aNZon1qZyToX4NmjbFbxOGbww+0CnbYFDbbPRGZ8jg4IbM8UlA+CzkXxM35itxyaHKNqBBg/RTDg==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.4.tgz", + "integrity": "sha512-VBNn1+EuMZkeGVVtz0gRfbNGtx9IFgAsAV+E2pHtXPrp4qfGBkhTIiAuE/wrb+Y6Pakg9NewAlfTpYIFAWODtw==", "license": "MIT", "optional": true, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-discard-empty": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.2.tgz", - "integrity": "sha512-NZFouOmOwtngJVgkNeI1LtkzFdYqIurxgy4wq3qNvIiXFURTZ3b/K7q3dP3QitlWQ5imHDQL0qSorItQhoxb1g==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.3.tgz", + "integrity": "sha512-M2pyjQCU+/7cMHVtL6bKTHjv0lZnPLMpicgr67Dlth7AbuV9gjVTtUqaRwn6Pp6BwSDspUzhz8SaUrRykJU5Dw==", "license": "MIT", "optional": true, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-discard-overridden": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.2.tgz", - "integrity": "sha512-Ym01X4v6U3sY8X0P1J9P+RTar+7JyLTOzDrxKSeaArFsLmkVu4KcAKPBWDYRIyZ/q4jwpSPnOnekeSSqXSXKUw==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.3.tgz", + "integrity": "sha512-aNovXo9UsZuRNLzHJtp13lHIvinDPfiXBPePpXkSjCbgp++iU2FqE+YxvjIsg6EdyPZsASFbfu+JcBFVsErXIQ==", "license": "MIT", "optional": true, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-merge-longhand": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.6.tgz", - "integrity": "sha512-lDsWeKRsssX/9vKFpingoRiuvGajtOGCJhs1kyaTJ5fzaVzs0aPPYe38UZ/ukMFEA5iuRIjQJHIkH2niYO3ubQ==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.7.tgz", + "integrity": "sha512-b3mfYUxR388u5Pt0HPcVIUtUDn/k15UfTY9M+ORW+meCR6JLNxoZffiYvXyOYQoRYQNZyX/UFkMCM/mNHxe1qA==", "license": "MIT", "optional": true, "dependencies": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^7.0.10" + "stylehacks": "^7.0.11" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-merge-rules": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.10.tgz", - "integrity": "sha512-UXYKxkg8Cy1so/evF7AE/25PNXZb3E0SrvjdbtbGf+MW+doLenKqRLQzz6YZW469ktiXK2MVLFWtel/DftCV0Q==", + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.11.tgz", + "integrity": "sha512-SJUPM18g2BmPhf8BVlbwqWz4aK3pLu6u6xjfwEzra7xL6IBR10sUaiB++EzqcVfadPHrKBSMlNdP+XieykhI+Q==", "license": "MIT", "optional": true, "dependencies": { "browserslist": "^4.28.2", "caniuse-api": "^3.0.0", - "cssnano-utils": "^5.0.2", + "cssnano-utils": "^5.0.3", "postcss-selector-parser": "^7.1.1" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-minify-font-values": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.2.tgz", - "integrity": "sha512-Z82NUmnvhPrvMUaHfkaAVBmWQq9F8Dox4Dy0LiwbaTxfmDUWLQtS+0WCgKViwdWCPPajiY9YzoQftgqKdXkM5g==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.3.tgz", + "integrity": "sha512-yilG/VOaNI74IylQvAQQxm3/wZVBkXyYUqNUAdxqwtbWUXPsbK1q8Ms0mL83v+f8YicgcyfYCRZtWACUdYajpA==", "license": "MIT", "optional": true, "dependencies": { @@ -13775,49 +13815,49 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-minify-gradients": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.4.tgz", - "integrity": "sha512-g8MNeNyN+lbwKy8DCtJ6zU6awBL0InBsSOaKmgZ1MdRLVItLQUNFNAzzzBnOp4qowOcyyB6GetTlQ0/0UNXvag==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.5.tgz", + "integrity": "sha512-YraROyQRg3BI1+Hg8E05B/JPdnTm8EDSVu4P2BxdM+CRiOyfmou809+chGIqo6fQqwjPGQ947nbGncSjmTU1WQ==", "license": "MIT", "optional": true, "dependencies": { - "@colordx/core": "^5.2.0", - "cssnano-utils": "^5.0.2", + "@colordx/core": "^5.4.3", + "cssnano-utils": "^5.0.3", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-minify-params": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.8.tgz", - "integrity": "sha512-DIUKM5DZGTmxN7KFKT+rxt0FdPDmRrdK/k3n3+6Po+N/QYn06juwagHcfOVBG0CfCHwcnI612GAUCZc3eT+ZEg==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.9.tgz", + "integrity": "sha512-R8itbB8BhlpoYyBm1ou0dD+vJnQ3F6adQipR4UnkCHUwlo+S9WXJaDRg1RHjC8YVAtIdrQzSWvJl40HnGDTKjA==", "license": "MIT", "optional": true, "dependencies": { "browserslist": "^4.28.2", - "cssnano-utils": "^5.0.2", + "cssnano-utils": "^5.0.3", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-minify-selectors": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.1.0.tgz", - "integrity": "sha512-HYl/6I0aL+UvpA10t65BSa7h+tVjBgE6oRI5N/3ylX3vtwvlDL67G3FT3vYDPnTksxr0riiyJcT0tBtyRVoloA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.1.1.tgz", + "integrity": "sha512-MZWXwSTfcpmNVJIs7tddar/275a4/zT5nG9/gEndHPRZGTAQNpiSkk8s/dq+yZVX2jKfvVn1d5X8Z5SJHWnDoQ==", "license": "MIT", "optional": true, "dependencies": { @@ -13830,26 +13870,26 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-normalize-charset": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.2.tgz", - "integrity": "sha512-YoINoiR4YKlzfB95Y93b0DSxWy7FLw+1SADIaznMHb88AKizpzfF80tolmiDEbYr1UM4r4Hw+NZq37SwT5f3uw==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.3.tgz", + "integrity": "sha512-NoBfZu8PR4c2NlmjvrqQTzCzLY79hwcSRgNQ3ZiNK0ABzf9kYKloE/jNj+/8GQY1wsm8pRRgANk6ydLH8cwo0Q==", "license": "MIT", "optional": true, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-normalize-display-values": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.2.tgz", - "integrity": "sha512-wu/NTSjnp8sX5TnEHVPN+eScjAtRs18ELtEduG+Ek3GxjeUDUT+VAA3PJjVIXBcVIk6fiLYFj2iKH0q99S3T2Q==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.3.tgz", + "integrity": "sha512-ldsCX0QIt05pKIOobZtVQ48wXJecr+czw4+e1/YjVhLMqslShgpVxgPtI2CefURR8oyVoYaU/l829MMwExDMLw==", "license": "MIT", "optional": true, "dependencies": { @@ -13859,13 +13899,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-normalize-positions": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.3.tgz", - "integrity": "sha512-1CJI++oA3yK/fQlPUcEngUfcSWS08Pkt9fK+jVgL53mmtHDBHi0YiuB0m3D9BXwZjmfvCc2GQmFqCAF/CVcPzQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.4.tgz", + "integrity": "sha512-VEvlpeGd3Ju1Hqa/oN4jaP3+ms4laYwkEL9N9u+B6k54PZjXbW1n6wI+aVprf1BQXlCYpS5+1pl/7/vHiKgARg==", "license": "MIT", "optional": true, "dependencies": { @@ -13875,13 +13915,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-normalize-repeat-style": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.3.tgz", - "integrity": "sha512-RvImJ2Ml4LZSx31qC2C8LDiz65IgBNATtwEr9r3Aue+D0cCGbj4rjNojb/uGpEm4QxnOTzFqMvaDYuKiT1Cmpg==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.4.tgz", + "integrity": "sha512-6mPKlY/8cSaDHxX502wERADarJsccwlky6yIrOapHH2ZgfoKAV94SbiTKfKEs4EEpdazuc3J72WsqeYk7hp9+Q==", "license": "MIT", "optional": true, "dependencies": { @@ -13891,13 +13931,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-normalize-string": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.2.tgz", - "integrity": "sha512-FqtrUh2BU2MnVeLeWBbJ2rwOjuDnA91XvoImc1BbgMWIxdxiPTaquflBHsmFBA3xh3pC3wPZO9W5MaIc7wU/Xw==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.3.tgz", + "integrity": "sha512-HnEQPUchi1eznmDKEYrKUTqrprEq97SrpUYClgUkv7V2zRODD9DFoUsYU+m9ZOetmD5ku7fEMZB/lwy8IT6xVQ==", "license": "MIT", "optional": true, "dependencies": { @@ -13907,13 +13947,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-normalize-timing-functions": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.2.tgz", - "integrity": "sha512-5H5fpXBnMACEXzn7k9RP7qWZ1eWg8cuZkUuFygStY7icOj+UucwMWXeMmdkF/iITvTVa7fP85tdRCJeznpdFfQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.3.tgz", + "integrity": "sha512-zmEzHdvpZBZu0OKlbJSfgASQvaayyAoVuWtvyr34IJ/LyS+DaOKvvR3EvFJ9RWWtNIx+CMvO125OVophaxNYew==", "license": "MIT", "optional": true, "dependencies": { @@ -13923,13 +13963,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-normalize-unicode": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.8.tgz", - "integrity": "sha512-imCM3cwK3hvlAG4z1AzYM24m8BPA3/Jk/S71wfbn2I6+E2b+UwFaGvlNqydihXTSl3OFPeQXztqCzg+NGeSibQ==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.9.tgz", + "integrity": "sha512-DRAdWfeh/TjmhLJsw91vdiWCnUod9iwvM7xyS02/nF/sLsCR3A8l3pztrSUrWG8DSBqfX7yEk9FM0USaVJ2mSg==", "license": "MIT", "optional": true, "dependencies": { @@ -13940,13 +13980,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-normalize-url": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.2.tgz", - "integrity": "sha512-bLnNY7t76NLRb9QQyCVmCN4qwoHxiq6vABH/CXav9wTuR6dNGHGQ72AyO/+h2quWxZk3l7BqxNL1vtDi9H6y1g==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.3.tgz", + "integrity": "sha512-CL93wmloq5qsffmFv+bw24MIRbmhHrp53qoh1LDAb/5TtjWEXI/np4xcP/Gw9oWCb2XyWnqHYLDUwiKRoJBA1Q==", "license": "MIT", "optional": true, "dependencies": { @@ -13956,13 +13996,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-normalize-whitespace": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.2.tgz", - "integrity": "sha512-TNSVkuhkeOhl36WruQlflxOb7HweoeZowSusNpfsM1+ZvqJ24Mc+xksu05ecMQxlu+0zgI8pyznO2EWqDCQbLA==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.3.tgz", + "integrity": "sha512-FdHjjn+Ht5Z2ZRjNOmeCbNq6lq09sUYKpmlF/Aq0XjVNSLTL6fmHlA/3swN2wP2caY9GV/tjSDcIIyS7aN7W0A==", "license": "MIT", "optional": true, "dependencies": { @@ -13972,30 +14012,30 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-ordered-values": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.3.tgz", - "integrity": "sha512-FTt6R9RF7NAYfpOHa2XFPm89FVuo5GiIbcfwOXFy1MYF38BeiNW9ke8ybw9Pk62eEsUlRVVbxHWA3B7ERYqOOA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.4.tgz", + "integrity": "sha512-nubSi49hDHQk4E8KIj+IbLY8Bg+8OcSUEhgyolgM+atnOvXjV7EjaR6bac4YGZoFyPa9mWoAF3EaYbWdFkKqVg==", "license": "MIT", "optional": true, "dependencies": { - "cssnano-utils": "^5.0.2", + "cssnano-utils": "^5.0.3", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-reduce-initial": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.8.tgz", - "integrity": "sha512-VeVRmbgpgTZuRcDQdqnsB4iYTeS2dBRV07UdwK6V3x61F1xTQ2pgIzHBIR4rQYRlXRNKBTGYYhEL1eNA7w9vaQ==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.9.tgz", + "integrity": "sha512-ztTNPdIxXTxtBcG03E9u8v44M4ElXbMIRT7pf2onlquGula0Y83nKKxqM22FA/hMgkfCjN7ohevkVlaNwI8iOQ==", "license": "MIT", "optional": true, "dependencies": { @@ -14006,13 +14046,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-reduce-transforms": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.2.tgz", - "integrity": "sha512-OV5P9hMnf7kEkeXVXyS5ESqxbIls7a3TqFymUAV5JICO/9YCBEU+QQhQjZiDHaLwFdV7/CL481kVeBUk5FdY3w==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.3.tgz", + "integrity": "sha512-FXsnN9ZwcZTT8Yf8cAHA8qIGUXcX6WfLd9JoYhrdDfmvsVhhfqkkv7m4AC3rwFOfz+GzkUa87OCKF9dUcicd+g==", "license": "MIT", "optional": true, "dependencies": { @@ -14022,7 +14062,7 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-selector-parser": { @@ -14040,9 +14080,9 @@ } }, "node_modules/postcss-svgo": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.1.2.tgz", - "integrity": "sha512-ixExc8m+/68yuSYQzV/1DgtTup/7nI2dN9eiDS5GMRUzeCH4q9UcqeZPwcSVhdf8ay9fRwXDUHwcY5/XzQSszQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.1.3.tgz", + "integrity": "sha512-2QfoFOYMcj8lwcVEf9WeTlkVIAm7u2QvOEhMzkQU3KUhhGX/l8hVV9EtjMv4iq3E9iI3OeeMN0YoMLbGusuigw==", "license": "MIT", "optional": true, "dependencies": { @@ -14053,13 +14093,13 @@ "node": "^18.12.0 || ^20.9.0 || >= 18" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-unique-selectors": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.6.tgz", - "integrity": "sha512-cDxnYw1QuBMW5w3svZ0BlYF0IA4Amr+1JoTLXzu6vDFPNwohN2QU+sPZNx15b930LR7ce+/600h28/cYoxO9vw==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.7.tgz", + "integrity": "sha512-d+sCkaRnSefghOUdH8CMJZV9yUQhj2ojpe8Nw/lA+LV1UOfeleGkLTl6XdCFFSai9UJ+DJPb69FFuqthXYsY8w==", "license": "MIT", "optional": true, "dependencies": { @@ -14069,7 +14109,7 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/postcss-value-parser": { @@ -14290,7 +14330,6 @@ "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "devOptional": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -14372,7 +14411,6 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@prisma/config": "6.19.3", "@prisma/engines": "6.19.3" @@ -14856,8 +14894,7 @@ "version": "0.1.14", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/repeat-string": { "version": "1.6.1", @@ -15015,6 +15052,7 @@ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", @@ -15031,6 +15069,7 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" @@ -15090,7 +15129,6 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -15156,7 +15194,6 @@ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -15215,6 +15252,7 @@ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", @@ -15241,6 +15279,7 @@ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", + "peer": true, "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", @@ -15865,9 +15904,9 @@ } }, "node_modules/stylehacks": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.10.tgz", - "integrity": "sha512-sRJ7klmhe/Fl5woJcbJUa2qP1Ueffsl1CQI4ePvqXLkZmcIuAt09aP9uT/FOFPqXh9Rh8M5UkgEnwTdTKn/Aag==", + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.11.tgz", + "integrity": "sha512-iODNfhXVLqc5LADs+Y6Oh5wJuK5ZcHbVng8aiK3y9pjMQdc5hLrBW0eFU6FtnpNrE6PoEg/MmFTU4waotj5WNg==", "license": "MIT", "optional": true, "dependencies": { @@ -15878,7 +15917,7 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.5.10" + "postcss": "^8.5.13" } }, "node_modules/subscriptions-transport-ws": { @@ -16119,7 +16158,6 @@ "integrity": "sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw==", "devOptional": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -16531,7 +16569,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -16725,7 +16762,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16745,7 +16781,6 @@ "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "dev": true, "license": "BSD-2-Clause", "optional": true, "bin": { @@ -17103,6 +17138,7 @@ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -17117,6 +17153,7 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=4.0" } @@ -17127,6 +17164,7 @@ "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6ae4d72..9fc910d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -131,7 +131,6 @@ enum DigestFrequency { WEEKLY } - enum DisputeStatus { OPEN UNDER_REVIEW @@ -207,14 +206,14 @@ model User { emailStatus EmailStatus @default(ACTIVE) @map("email_status") fcmToken String? @map("fcm_token") notifications Notification[] - disputeInitiated Dispute[] @relation("DisputeInitiator") - disputeArbitrated Dispute[] @relation("DisputeArbitrator") + disputeInitiated Dispute[] @relation("DisputeInitiator") + disputeArbitrated Dispute[] @relation("DisputeArbitrator") linkClicks LinkClick[] emailEngagements EmailEngagement[] emailBounces EmailBounce[] digestPreference DigestPreference? createdTaxStrategies TransactionTaxStrategy[] @relation("CreatedTransactionTaxStrategies") - + transactionHistory TransactionHistory[] @@index([email]) @@index([role]) @@ -225,23 +224,23 @@ model User { } model DatabaseBackup { - id String @id @default(uuid()) - filename String @unique - filePath String @map("file_path") - status BackupStatus @default(PENDING) - trigger BackupTrigger - sizeBytes BigInt? @map("size_bytes") - checksum String? - startedAt DateTime @default(now()) @map("started_at") - completedAt DateTime? @map("completed_at") - errorMessage String? @map("error_message") @db.Text - initiatedById String? @map("initiated_by_id") - restoreStatus RestoreStatus @default(IDLE) @map("restore_status") - restoredAt DateTime? @map("restored_at") - restoreError String? @map("restore_error") @db.Text - restoredById String? @map("restored_by_id") - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") + id String @id @default(uuid()) + filename String @unique + filePath String @map("file_path") + status BackupStatus @default(PENDING) + trigger BackupTrigger + sizeBytes BigInt? @map("size_bytes") + checksum String? + startedAt DateTime @default(now()) @map("started_at") + completedAt DateTime? @map("completed_at") + errorMessage String? @map("error_message") @db.Text + initiatedById String? @map("initiated_by_id") + restoreStatus RestoreStatus @default(IDLE) @map("restore_status") + restoredAt DateTime? @map("restored_at") + restoreError String? @map("restore_error") @db.Text + restoredById String? @map("restored_by_id") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") initiatedBy User? @relation("BackupInitiatedBy", fields: [initiatedById], references: [id], onDelete: SetNull) restoredBy User? @relation("BackupRestoredBy", fields: [restoredById], references: [id], onDelete: SetNull) @@ -253,13 +252,13 @@ model DatabaseBackup { } model BackupScheduleConfig { - id String @id - enabled Boolean @default(false) - cronExpression String @map("cron_expression") - retentionCount Int @default(10) @map("retention_count") + id String @id + enabled Boolean @default(false) + cronExpression String @map("cron_expression") + retentionCount Int @default(10) @map("retention_count") lastRunAt DateTime? @map("last_run_at") - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") @@map("backup_schedule_configs") } @@ -421,11 +420,14 @@ model Transaction { updatedAt DateTime @updatedAt @map("updated_at") // Relations - property Property @relation(fields: [propertyId], references: [id]) - buyer User @relation("BuyerTransactions", fields: [buyerId], references: [id]) - seller User @relation("SellerTransactions", fields: [sellerId], references: [id]) - fraudAlerts FraudAlert[] - taxStrategies TransactionTaxStrategy[] + property Property @relation(fields: [propertyId], references: [id]) + buyer User @relation("BuyerTransactions", fields: [buyerId], references: [id]) + seller User @relation("SellerTransactions", fields: [sellerId], references: [id]) + fraudAlerts FraudAlert[] + taxStrategies TransactionTaxStrategy[] + disputes Dispute[] + transactionMilestones TransactionMilestone[] + transactionHistory TransactionHistory[] @@index([propertyId]) @@index([buyerId]) @@ -436,19 +438,19 @@ model Transaction { } model TransactionTaxStrategy { - id String @id @default(uuid()) - transactionId String @map("transaction_id") - createdById String? @map("created_by_id") - strategyType String @map("strategy_type") + id String @id @default(uuid()) + transactionId String @map("transaction_id") + createdById String? @map("created_by_id") + strategyType String @map("strategy_type") jurisdiction String? - estimatedTaxRate Decimal? @map("estimated_tax_rate") - estimatedTaxImpact Decimal? @map("estimated_tax_impact") - explanation String @db.Text + estimatedTaxRate Decimal? @map("estimated_tax_rate") + estimatedTaxImpact Decimal? @map("estimated_tax_impact") + explanation String @db.Text metadata Json? - version Int @default(1) - informationalOnly Boolean @default(true) @map("informational_only") - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") + version Int @default(1) + informationalOnly Boolean @default(true) @map("informational_only") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") transaction Transaction @relation(fields: [transactionId], references: [id], onDelete: Cascade) createdBy User? @relation("CreatedTransactionTaxStrategies", fields: [createdById], references: [id], onDelete: SetNull) @@ -502,27 +504,27 @@ model Document { // User Preferences model model UserPreferences { - id String @id @default(uuid()) - userId String @unique @map("user_id") - language String @default("en") - currency String @default("USD") - timezone String @default("UTC") - emailNotifications Boolean @default(true) @map("email_notifications") - smsNotifications Boolean @default(false) @map("sms_notifications") - inAppNotifications Boolean @default(true) @map("in_app_notifications") - pushNotifications Boolean @default(false) @map("push_notifications") - propertyAlerts Boolean @default(true) @map("property_alerts") - marketUpdates Boolean @default(true) @map("market_updates") - theme String @default("light") + id String @id @default(uuid()) + userId String @unique @map("user_id") + language String @default("en") + currency String @default("USD") + timezone String @default("UTC") + emailNotifications Boolean @default(true) @map("email_notifications") + smsNotifications Boolean @default(false) @map("sms_notifications") + inAppNotifications Boolean @default(true) @map("in_app_notifications") + pushNotifications Boolean @default(false) @map("push_notifications") + propertyAlerts Boolean @default(true) @map("property_alerts") + marketUpdates Boolean @default(true) @map("market_updates") + theme String @default("light") // Notification preferences (#370) notificationFrequency String @default("INSTANT") @map("notification_frequency") // INSTANT | HOURLY | DAILY | WEEKLY - notificationEventTypes String[] @default([]) @map("notification_event_types") // e.g. ["TRANSACTION_UPDATE","PROPERTY_ALERT","MARKET_UPDATE","DISPUTE","SYSTEM"] + notificationEventTypes String[] @default([]) @map("notification_event_types") // e.g. ["TRANSACTION_UPDATE","PROPERTY_ALERT","MARKET_UPDATE","DISPUTE","SYSTEM"] quietHoursEnabled Boolean @default(false) @map("quiet_hours_enabled") - quietHoursStart String? @map("quiet_hours_start") // "HH:MM" in user timezone - quietHoursEnd String? @map("quiet_hours_end") // "HH:MM" in user timezone + quietHoursStart String? @map("quiet_hours_start") // "HH:MM" in user timezone + quietHoursEnd String? @map("quiet_hours_end") // "HH:MM" in user timezone perEventSettings Json? @map("per_event_settings") // fine-grained per-event channel overrides - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@ -548,7 +550,6 @@ model ActivityLog { @@index([userId]) @@index([action]) @@index([createdAt]) - @@index([scheduledAt]) @@index([entityType, entityId]) @@map("activity_logs") } @@ -659,14 +660,14 @@ model VerificationDocument { // Saved search filters model SavedFilter { - id String @id @default(uuid()) - userId String @map("user_id") - name String - filters Json // Store filter configuration as JSON - isQuickFilter Boolean @default(false) @map("is_quick_filter") - usageCount Int @default(0) @map("usage_count") - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") + id String @id @default(uuid()) + userId String @map("user_id") + name String + filters Json // Store filter configuration as JSON + isQuickFilter Boolean @default(false) @map("is_quick_filter") + usageCount Int @default(0) @map("usage_count") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@ -677,25 +678,24 @@ model SavedFilter { // Search analytics model SearchAnalytics { - id String @id @default(uuid()) - userId String? @map("user_id") - queryId String @unique @map("query_id") - query String? - filters Json? // Store applied filters as JSON - resultsCount Int @default(0) @map("results_count") - took Int @default(0) // Search time in milliseconds - hasResults Boolean @default(true) @map("has_results") - converted Boolean @default(false) @map("converted") - ipAddress String? @map("ip_address") - userAgent String? @map("user_agent") - createdAt DateTime @default(now()) @map("created_at") + id String @id @default(uuid()) + userId String? @map("user_id") + queryId String @unique @map("query_id") + query String? + filters Json? // Store applied filters as JSON + resultsCount Int @default(0) @map("results_count") + took Int @default(0) // Search time in milliseconds + hasResults Boolean @default(true) @map("has_results") + converted Boolean @default(false) @map("converted") + ipAddress String? @map("ip_address") + userAgent String? @map("user_agent") + createdAt DateTime @default(now()) @map("created_at") user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@index([userId]) @@index([queryId]) @@index([createdAt]) - @@index([scheduledAt]) @@index([hasResults]) @@index([converted]) @@map("search_analytics") @@ -703,12 +703,12 @@ model SearchAnalytics { // Search history for autocomplete model SearchHistory { - id String @id @default(uuid()) - userId String @map("user_id") - query String - frequency Int @default(1) // How many times this query has been searched + id String @id @default(uuid()) + userId String @map("user_id") + query String + frequency Int @default(1) // How many times this query has been searched lastSearched DateTime @default(now()) @map("last_searched") - createdAt DateTime @default(now()) @map("created_at") + createdAt DateTime @default(now()) @map("created_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@ -721,10 +721,10 @@ model SearchHistory { // Popular searches (global) model PopularSearch { - id String @id @default(uuid()) - query String @unique - frequency Int @default(1) - trend String @default("stable") // up, down, stable + id String @id @default(uuid()) + query String @unique + frequency Int @default(1) + trend String @default("stable") // up, down, stable lastUpdated DateTime @default(now()) @map("last_updated") @@index([frequency]) @@ -734,13 +734,13 @@ model PopularSearch { // Search suggestions cache model SearchSuggestion { - id String @id @default(uuid()) - query String + id String @id @default(uuid()) + query String suggestion String - type String // property, location, feature, recent, popular - score Float @default(0) // Relevance score - expiresAt DateTime @map("expires_at") - createdAt DateTime @default(now()) @map("created_at") + type String // property, location, feature, recent, popular + score Float @default(0) // Relevance score + expiresAt DateTime @map("expires_at") + createdAt DateTime @default(now()) @map("created_at") @@index([query]) @@index([type]) @@ -750,15 +750,15 @@ model SearchSuggestion { } model Notification { - id String @id @default(uuid()) - userId String @map("user_id") - title String - message String - type String - status NotificationStatus @default(PENDING) - metadata Json? - createdAt DateTime @default(now()) @map("created_at") - readAt DateTime? @map("read_at") + id String @id @default(uuid()) + userId String @map("user_id") + title String + message String + type String + status NotificationStatus @default(PENDING) + metadata Json? + createdAt DateTime @default(now()) @map("created_at") + readAt DateTime? @map("read_at") scheduledAt DateTime? @map("scheduled_at") isRecurring Boolean @default(false) @map("is_recurring") cron String? @map("cron") @@ -817,6 +817,23 @@ model TransactionMilestone { @@map("transaction_milestones") } +model TransactionHistory { + id String @id @default(uuid()) + transactionId String @map("transaction_id") + status TransactionStatus + actorId String? @map("actor_id") + notes String? @db.Text + metadata Json? + createdAt DateTime @default(now()) @map("created_at") + + transaction Transaction @relation(fields: [transactionId], references: [id], onDelete: Cascade) + actor User? @relation(fields: [actorId], references: [id], onDelete: SetNull) + + @@index([transactionId]) + @@index([createdAt]) + @@map("transaction_history") +} + model LinkClick { id String @id @default(uuid()) url String @@ -831,37 +848,35 @@ model LinkClick { @@index([url]) @@index([userId]) @@index([createdAt]) - @@index([scheduledAt]) @@map("link_clicks") } model EmailEngagement { - id String @id @default(uuid()) - trackingId String @unique @map("tracking_id") - userId String @map("user_id") - emailType String @map("email_type") - openedAt DateTime? @map("opened_at") - ipAddress String? @map("ip_address") - userAgent String? @map("user_agent") - createdAt DateTime @default(now()) @map("created_at") + id String @id @default(uuid()) + trackingId String @unique @map("tracking_id") + userId String @map("user_id") + emailType String @map("email_type") + openedAt DateTime? @map("opened_at") + ipAddress String? @map("ip_address") + userAgent String? @map("user_agent") + createdAt DateTime @default(now()) @map("created_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([trackingId]) @@index([createdAt]) - @@index([scheduledAt]) @@map("email_engagements") } model EmailBounce { - id String @id @default(uuid()) - userId String @map("user_id") - email String - bounceType BounceType @map("bounce_type") - reason String? - rawEvent Json? @map("raw_event") - createdAt DateTime @default(now()) @map("created_at") + id String @id @default(uuid()) + userId String @map("user_id") + email String + bounceType BounceType @map("bounce_type") + reason String? + rawEvent Json? @map("raw_event") + createdAt DateTime @default(now()) @map("created_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@ -871,7 +886,6 @@ model EmailBounce { @@map("email_bounces") } - model DigestPreference { id String @id @default(uuid()) userId String @unique @map("user_id") diff --git a/propchain-backend@1.0.0 b/propchain-backend@1.0.0 new file mode 100644 index 0000000..e69de29 diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts index 1d6af08..e3c84a0 100644 --- a/src/admin/admin.controller.ts +++ b/src/admin/admin.controller.ts @@ -132,8 +132,9 @@ export class AdminController { updateTransactionStatus( @Param('id') transactionId: string, @Body() payload: UpdateTransactionStatusDto, + @CurrentUser() user: AuthUserPayload, ) { - return this.adminService.updateTransactionStatus(transactionId, payload); + return this.adminService.updateTransactionStatus(transactionId, payload, user.sub); } @Get('fraud/alerts') diff --git a/src/admin/admin.service.ts b/src/admin/admin.service.ts index 5af55ff..efb5282 100644 --- a/src/admin/admin.service.ts +++ b/src/admin/admin.service.ts @@ -324,8 +324,12 @@ export class AdminService { }; } - async updateTransactionStatus(transactionId: string, payload: UpdateTransactionStatusDto) { - return this.transactionsService.updateTransactionStatus(transactionId, payload.status); + async updateTransactionStatus( + transactionId: string, + payload: UpdateTransactionStatusDto, + actorId?: string, + ) { + return this.transactionsService.updateTransactionStatus(transactionId, payload.status, actorId); } async listFraudAlerts(query: FraudAlertsQueryDto) { diff --git a/src/notifications/notifications.service.ts b/src/notifications/notifications.service.ts index e1bed35..854763e 100644 --- a/src/notifications/notifications.service.ts +++ b/src/notifications/notifications.service.ts @@ -97,7 +97,7 @@ export class NotificationsService { // FCM Push Integration const user = await this.prisma.user.findUnique({ where: { id: userId }, select: { fcmToken: true } }); if (user?.fcmToken) { - console.log(Sending FCM notification to token: \); + console.log(`Sending FCM notification to token: ${user.fcmToken}`); // In production, use admin.messaging().send() here } const delivered = this.gateway.sendToUser(userId, 'notification', notification); diff --git a/src/transactions/dto/timeline.dto.ts b/src/transactions/dto/timeline.dto.ts index 653a419..83d18a5 100644 --- a/src/transactions/dto/timeline.dto.ts +++ b/src/transactions/dto/timeline.dto.ts @@ -14,6 +14,10 @@ export class CreateMilestoneDto { } export class UpdateMilestoneDto { + @IsOptional() + @IsString() + title?: string; + @IsOptional() @IsEnum(MilestoneStatus) status?: MilestoneStatus; diff --git a/src/transactions/transactions.controller.ts b/src/transactions/transactions.controller.ts index b1ae10b..1c92c39 100644 --- a/src/transactions/transactions.controller.ts +++ b/src/transactions/transactions.controller.ts @@ -16,6 +16,13 @@ import { TransactionsService } from './transactions.service'; export class TransactionsController { constructor(private readonly transactionsService: TransactionsService) {} + @UseGuards(JwtAuthGuard) + @Get() + @ApiOperation({ summary: 'List all transactions for the current user' }) + findAll(@Query() query: TransactionSearchQueryDto, @CurrentUser() user: AuthUserPayload) { + return this.transactionsService.search(query, user); + } + @UseGuards(JwtAuthGuard) @Get('search') @ApiOperation({ summary: 'Search transactions with filters and pagination' }) @@ -24,6 +31,14 @@ export class TransactionsController { return this.transactionsService.search(query, user); } + @UseGuards(JwtAuthGuard) + @Get(':id/history') + @ApiOperation({ summary: 'Get transaction history audit log' }) + @ApiResponse({ status: 200, description: 'Transaction history returned successfully' }) + getHistory(@Param('id') transactionId: string, @CurrentUser() user: AuthUserPayload) { + return this.transactionsService.getTransactionHistory(transactionId, user); + } + @UseGuards(JwtAuthGuard) @Post() create(@Body() createTransactionDto: CreateTransactionDto, @CurrentUser() user: AuthUserPayload) { diff --git a/src/transactions/transactions.service.spec.ts b/src/transactions/transactions.service.spec.ts index 58bcce4..4f242d0 100644 --- a/src/transactions/transactions.service.spec.ts +++ b/src/transactions/transactions.service.spec.ts @@ -10,7 +10,13 @@ describe('TransactionsService', () => { transaction: { findMany: jest.Mock; count: jest.Mock; + findUnique: jest.Mock; + update: jest.Mock; }; + transactionHistory: { + create: jest.Mock; + }; + $transaction: jest.Mock; }; let notificationsService: { sendNotification: jest.Mock; @@ -22,7 +28,13 @@ describe('TransactionsService', () => { transaction: { findMany: jest.fn().mockResolvedValue([{ id: 'tx-1' }]), count: jest.fn().mockResolvedValue(1), + findUnique: jest.fn(), + update: jest.fn(), + }, + transactionHistory: { + create: jest.fn(), }, + $transaction: jest.fn(), }; notificationsService = { @@ -127,4 +139,31 @@ describe('TransactionsService', () => { expect(prisma.transaction.findMany.mock.calls[0][0].where).toEqual({}); }); + + describe('updateStatus', () => { + it('updates status and logs history in a transaction', async () => { + const mockTx = { id: 'tx-123', status: TransactionStatus.PENDING }; + prisma.transaction.findUnique.mockResolvedValue(mockTx); + prisma.transaction.update.mockResolvedValue({ ...mockTx, status: TransactionStatus.COMPLETED }); + prisma.transactionHistory.create.mockResolvedValue({ id: 'hist-1' }); + prisma.$transaction.mockImplementation(async (cb) => cb(prisma)); + + const result = await service.updateStatus('tx-123', TransactionStatus.COMPLETED, 'actor-1'); + + expect(prisma.transaction.update).toHaveBeenCalledWith({ + where: { id: 'tx-123' }, + data: { status: TransactionStatus.COMPLETED }, + }); + expect(prisma.transactionHistory.create).toHaveBeenCalledWith({ + data: { + transactionId: 'tx-123', + status: TransactionStatus.COMPLETED, + actorId: 'actor-1', + notes: 'Status updated from PENDING to COMPLETED', + }, + }); + expect(notificationsService.handleTransactionUpdate).toHaveBeenCalledWith('tx-123'); + expect(result.status).toBe(TransactionStatus.COMPLETED); + }); + }); }); diff --git a/src/transactions/transactions.service.ts b/src/transactions/transactions.service.ts index 36824cd..620087a 100644 --- a/src/transactions/transactions.service.ts +++ b/src/transactions/transactions.service.ts @@ -93,7 +93,7 @@ export class TransactionsService { throw new BadRequestException('Seller must match the property owner'); } - return this.prisma.transaction.create({ + const transaction = await this.prisma.transaction.create({ data: { propertyId: input.propertyId, buyerId: input.buyerId, @@ -131,9 +131,21 @@ export class TransactionsService { }, }, }); + + // Log initial status + await this.prisma.transactionHistory.create({ + data: { + transactionId: transaction.id, + status: transaction.status, + actorId: actor?.sub, + notes: 'Transaction created', + }, + }); + + return transaction; } - async updateStatus(id: string, status: TransactionStatus) { + async updateStatus(id: string, status: TransactionStatus, actorId?: string) { const transaction = await this.prisma.transaction.findUnique({ where: { id }, }); @@ -142,9 +154,26 @@ export class TransactionsService { throw new NotFoundException(`Transaction with ID ${id} not found`); } - const updated = await this.prisma.transaction.update({ - where: { id }, - data: { status }, + if (transaction.status === status) { + return transaction; + } + + const updated = await this.prisma.$transaction(async (tx) => { + const u = await tx.transaction.update({ + where: { id }, + data: { status }, + }); + + await tx.transactionHistory.create({ + data: { + transactionId: id, + status, + actorId, + notes: `Status updated from ${transaction.status} to ${status}`, + }, + }); + + return u; }); // Trigger notification @@ -154,8 +183,43 @@ export class TransactionsService { } // Alias for AdminService compatibility - async updateTransactionStatus(id: string, status: TransactionStatus) { - return this.updateStatus(id, status); + async updateTransactionStatus(id: string, status: TransactionStatus, actorId?: string) { + return this.updateStatus(id, status, actorId); + } + + async getTransactionHistory(transactionId: string, user: AuthUserPayload) { + const transaction = await this.prisma.transaction.findUnique({ + where: { id: transactionId }, + }); + + if (!transaction) { + throw new NotFoundException(`Transaction ${transactionId} not found`); + } + + // RBAC: Only admin or parties to the transaction can see history + if ( + user.role !== UserRole.ADMIN && + user.sub !== transaction.buyerId && + user.sub !== transaction.sellerId + ) { + throw new ForbiddenException('You do not have permission to view this transaction history'); + } + + return this.prisma.transactionHistory.findMany({ + where: { transactionId }, + orderBy: { createdAt: 'desc' }, + include: { + actor: { + select: { + id: true, + firstName: true, + lastName: true, + email: true, + role: true, + }, + }, + }, + }); } async findOne(id: string) { diff --git a/src/types/prisma.types.ts b/src/types/prisma.types.ts index ee68bd3..23886f9 100644 --- a/src/types/prisma.types.ts +++ b/src/types/prisma.types.ts @@ -47,6 +47,16 @@ export interface ApiKey { updatedAt: Date; } +export interface TransactionHistory { + id: string; + transactionId: string; + status: string; + actorId: string | null; + notes: string | null; + metadata: any | null; + createdAt: Date; +} + export enum TokenType { ACCESS = 'ACCESS', REFRESH = 'REFRESH', From 80c16679e2f701c6aa40933f2084588363ddc04b Mon Sep 17 00:00:00 2001 From: Arise -D Nexus Date: Sun, 3 May 2026 13:23:36 +0100 Subject: [PATCH 3/3] #352 Add Transaction History --- src/transactions/transactions.service.ts | 6 ++++++ test/transactions/transactions.service.spec.ts | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/transactions/transactions.service.ts b/src/transactions/transactions.service.ts index 620087a..752f689 100644 --- a/src/transactions/transactions.service.ts +++ b/src/transactions/transactions.service.ts @@ -158,6 +158,12 @@ export class TransactionsService { return transaction; } + if (!canTransitionTransactionStatus(transaction.status as TransactionStatus, status)) { + throw new BadRequestException( + `Cannot transition transaction ${id} from ${transaction.status} to ${status}`, + ); + } + const updated = await this.prisma.$transaction(async (tx) => { const u = await tx.transaction.update({ where: { id }, diff --git a/test/transactions/transactions.service.spec.ts b/test/transactions/transactions.service.spec.ts index 175cc62..3dea852 100644 --- a/test/transactions/transactions.service.spec.ts +++ b/test/transactions/transactions.service.spec.ts @@ -26,10 +26,16 @@ describe('TransactionsService', () => { findFirst: jest.fn(), update: jest.fn(), }, + transactionHistory: { + create: jest.fn(), + findMany: jest.fn(), + }, + $transaction: jest.fn().mockImplementation(async (cb) => cb(mockPrismaService)), } as any; const mockNotificationsService = { sendNotification: jest.fn(), + handleTransactionUpdate: jest.fn(), }; beforeEach(async () => {