From fd3e9f4e7195f4cd49156bb2daba4cb4d80757c0 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 22 Jul 2026 00:18:10 +0100 Subject: [PATCH 1/3] admin audit logs --- .github/prompts/autonomy-version.json | 3 + .github/prompts/autonomy.manifest.json | 8 ++ .../app/(protected)/admin/activity/page.tsx | 80 +++++++++++++++++ .../app/(protected)/admin/audit-logs/page.tsx | 90 +++++++++++++++++++ 4 files changed, 181 insertions(+) create mode 100644 .github/prompts/autonomy-version.json create mode 100644 .github/prompts/autonomy.manifest.json create mode 100644 frontend/app/(protected)/admin/activity/page.tsx create mode 100644 frontend/app/(protected)/admin/audit-logs/page.tsx diff --git a/.github/prompts/autonomy-version.json b/.github/prompts/autonomy-version.json new file mode 100644 index 0000000..39afc29 --- /dev/null +++ b/.github/prompts/autonomy-version.json @@ -0,0 +1,3 @@ +{ + "version": "2025.11" +} diff --git a/.github/prompts/autonomy.manifest.json b/.github/prompts/autonomy.manifest.json new file mode 100644 index 0000000..25f7f88 --- /dev/null +++ b/.github/prompts/autonomy.manifest.json @@ -0,0 +1,8 @@ +{ + "version": "2025.11", + "consent": { + "phrase": "", + "expiresMinutes": 0 + }, + "actions": [] +} \ No newline at end of file diff --git a/frontend/app/(protected)/admin/activity/page.tsx b/frontend/app/(protected)/admin/activity/page.tsx new file mode 100644 index 0000000..1022d1e --- /dev/null +++ b/frontend/app/(protected)/admin/activity/page.tsx @@ -0,0 +1,80 @@ +import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card"; +import { CircleUser } from "lucide-react"; + +// Define the type for the activity data +interface Activity { + id: string; + user_email: string; + action: string; + created_at: string; +} + +async function getActivity(): Promise { + // This is a server component, so we can fetch data directly. + // In a real app, you'd fetch from your API endpoint. + // The URL should be absolute, e.g., process.env.API_URL. + // For this example, we'll use mock data. + try { + // const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/activity`); + // if (!res.ok) { + // throw new Error('Failed to fetch activity data'); + // } + // const data = await res.json(); + // return data; + + // Mock data for demonstration + const mockData: Activity[] = [ + { id: '1', user_email: 'admin@example.com', action: 'User login', created_at: new Date().toISOString() }, + { id: '2', user_email: 'test@example.com', action: 'File upload: "document.pdf"', created_at: new Date(Date.now() - 1000 * 60 * 5).toISOString() }, + { id: '3', user_email: 'admin@example.com', action: 'File delete: "image.jpg"', created_at: new Date(Date.now() - 1000 * 60 * 10).toISOString() }, + { id: '4', user_email: 'guest@example.com', action: 'Viewed page: "Dashboard"', created_at: new Date(Date.now() - 1000 * 60 * 15).toISOString() }, + ]; + return mockData; + } catch (error) { + console.error(error); + return []; + } +} + +export default async function AdminActivityPage() { + const activities = await getActivity(); + + return ( +
+

User Activity Timeline

+ + + Activity Feed + + An overview of recent user actions on the platform. + + + +
+ {/* The timeline line */} +
+ + {activities.map((activity, index) => ( +
+ {/* The timeline dot */} +
+ +
+ +

{activity.user_email}

+
+

{activity.action}

+

+ {new Date(activity.created_at).toLocaleString()} +

+
+ ))} + {activities.length === 0 && ( +

No recent activity.

+ )} +
+
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/app/(protected)/admin/audit-logs/page.tsx b/frontend/app/(protected)/admin/audit-logs/page.tsx new file mode 100644 index 0000000..6a01ce1 --- /dev/null +++ b/frontend/app/(protected)/admin/audit-logs/page.tsx @@ -0,0 +1,90 @@ +import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card"; +import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table"; + +// Define the type for the audit log data +interface AuditLog { + id: string; + ip_address: string; + timestamp: string; + method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; + path: string; + status_code: number; + user_agent: string; +} + +async function getAuditLogs(): Promise { + // This is a server component, so we can fetch data directly. + // In a real app, you'd fetch from your API endpoint. + // For this example, we'll use mock data. + try { + // const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/audit`); + // if (!res.ok) { + // throw new Error('Failed to fetch audit logs'); + // } + // const data = await res.json(); + // return data; + + // Mock data for demonstration + const mockData: AuditLog[] = [ + { id: '1', ip_address: '192.168.1.1', timestamp: new Date().toISOString(), method: 'GET', path: '/api/users', status_code: 200, user_agent: 'Mozilla/5.0' }, + { id: '2', ip_address: '10.0.0.1', timestamp: new Date(Date.now() - 1000 * 60 * 2).toISOString(), method: 'POST', path: '/api/auth/login', status_code: 200, user_agent: 'Chrome/91.0' }, + { id: '3', ip_address: '172.16.0.1', timestamp: new Date(Date.now() - 1000 * 60 * 5).toISOString(), method: 'GET', path: '/api/files/123', status_code: 404, user_agent: 'curl/7.64.1' }, + { id: '4', ip_address: '192.168.1.2', timestamp: new Date(Date.now() - 1000 * 60 * 10).toISOString(), method: 'DELETE', path: '/api/files/456', status_code: 204, user_agent: 'Mozilla/5.0' }, + ]; + return mockData; + } catch (error) { + console.error(error); + return []; + } +} + +export default async function AdminAuditLogsPage() { + const auditLogs = await getAuditLogs(); + + return ( +
+

HTTP Access Logs

+ + + Audit Logs + + A record of all HTTP requests made to the server. + + + + + + + IP Address + Timestamp + Method + Path + Status + User Agent + + + + {auditLogs.map((log) => ( + + {log.ip_address} + {new Date(log.timestamp).toLocaleString()} + {log.method} + {log.path} + {log.status_code} + {log.user_agent} + + ))} + {auditLogs.length === 0 && ( + + + No audit logs found. + + + )} + +
+
+
+
+ ); +} \ No newline at end of file From 843ec2b949d301b7539ffefd977c379d176cf10e Mon Sep 17 00:00:00 2001 From: DIVINE Date: Sun, 26 Jul 2026 21:41:41 +0100 Subject: [PATCH 2/3] build fix --- .kilo/kilo.jsonc | 4 + backend/package-lock.json | 36 +-- .../src/access-logs/access-logs.service.ts | 103 +++--- .../access-logs/entities/access-log.entity.ts | 7 +- .../activity-tracker.service.ts | 56 ++-- backend/src/auth/auth.module.ts | 5 +- backend/src/auth/auth.service.ts | 19 +- backend/src/auth/guards/roles.guard.ts | 7 +- .../src/auth/strategies/github.strategy.ts | 6 +- .../src/auth/strategies/google.strategy.ts | 6 +- .../common/filters/http-exception.filter.ts | 5 +- backend/src/common/logger.config.ts | 6 +- .../common/middleware/logger.middleware.ts | 7 +- backend/src/documents/documents.controller.ts | 11 +- backend/src/documents/documents.module.ts | 1 - .../src/documents/documents.service.spec.ts | 5 +- backend/src/documents/documents.service.ts | 5 +- .../dto/validation-request.dto.ts | 6 +- .../entities/validation-request.entity.ts | 13 +- .../external-validation.service.ts | 298 ++++++++++------- .../validation-provider.interface.ts | 9 +- .../business-registration.provider.ts | 5 +- .../providers/government-id.provider.ts | 5 +- .../providers/land-registry.provider.ts | 5 +- backend/src/queue/document.processor.ts | 15 +- backend/src/queue/queue.service.ts | 3 +- .../risk-assessment.service.ts | 9 +- backend/src/stellar/stellar.service.ts | 38 ++- backend/src/users/users.service.ts | 4 +- .../src/verification/verification.service.ts | 5 +- frontend/app/[locale]/layout.tsx | 50 +-- frontend/app/layout.tsx | 55 ++++ frontend/components/ui/card.tsx | 43 +++ frontend/components/ui/table.tsx | 51 +++ frontend/next.config.ts | 1 - frontend/next.config.ts.bak | 17 + frontend/package-lock.json | 300 +++++++++++++++++- frontend/package.json | 3 +- frontend/tsconfig.json | 63 ++-- frontend/types/lucide-react.d.ts | 16 + frontend/types/test-setup.d.ts | 1 + package-lock.json | 6 + 42 files changed, 958 insertions(+), 352 deletions(-) create mode 100644 .kilo/kilo.jsonc create mode 100644 frontend/app/layout.tsx create mode 100644 frontend/components/ui/card.tsx create mode 100644 frontend/components/ui/table.tsx create mode 100644 frontend/next.config.ts.bak create mode 100644 frontend/types/lucide-react.d.ts create mode 100644 frontend/types/test-setup.d.ts create mode 100644 package-lock.json diff --git a/.kilo/kilo.jsonc b/.kilo/kilo.jsonc new file mode 100644 index 0000000..0603291 --- /dev/null +++ b/.kilo/kilo.jsonc @@ -0,0 +1,4 @@ +{ + "$schema": "https://app.kilo.ai/config.json", + "snapshot": false +} \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index 125fda8..9ba55a7 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -2334,7 +2334,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.19.tgz", "integrity": "sha512-qeiTt2tv+e5QyDKqG8HlVZb2wx64FEaSGFJouqTSRs+kG44iTfl3xlz1XqVped+rihx4hmjWgL5gkhtdK3E6+Q==", "license": "MIT", - "peer": true, "dependencies": { "file-type": "21.3.4", "iterare": "1.2.1", @@ -2382,7 +2381,6 @@ "integrity": "sha512-6nJkWa2efrYi+XlU686J9y5L7OvxpLVjT0T/sxRKE7Jvpffiihelup4WSvLvRhdHDjj/5SuoWEwqReXAaaeHmw==", "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@nuxt/opencollective": "0.4.1", "fast-safe-stringify": "2.1.1", @@ -2466,7 +2464,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.19.tgz", "integrity": "sha512-Vpdv8jyCQdThfoTx+UTn+DRYr6H6X02YUqcpZ3qP6G3ZUwtVp7eS+hoQPGd4UuCnlnFG8Wqr2J9bGEzQdi1rIg==", "license": "MIT", - "peer": true, "dependencies": { "cors": "2.8.6", "express": "5.2.1", @@ -2488,7 +2485,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.1.19.tgz", "integrity": "sha512-gu1nPIEaP5Qjjg/Cl8wXyvwGpdZGzgbtK4KcH65YRAA+GTKUkIHb4BNpLJ27Ymq/wqLJKNEbCjajfzD0BEjMGA==", "license": "MIT", - "peer": true, "dependencies": { "socket.io": "4.8.3", "tslib": "2.8.1" @@ -2686,7 +2682,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.1.tgz", "integrity": "sha512-8rw/nKT0S+L+MkzgE9F2/mox7mAgsPlwfzmW9gsESN1lmQtIrVEfiiBwC2O8+guS1jBfQehJIdcdUj2OAp4VUQ==", "license": "MIT", - "peer": true, "peerDependencies": { "@nestjs/common": "^10.0.0 || ^11.0.0", "@nestjs/core": "^10.0.0 || ^11.0.0", @@ -2700,7 +2695,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.1.19.tgz", "integrity": "sha512-2qo8jtIwwwgkqAI1BtnJ02EaFLrRkKA39eYXS8IhZCHilhBHCWdjnJ5cLcFq4oF+s+KZ7LcLGD/3stxJy8ijzg==", "license": "MIT", - "peer": true, "dependencies": { "iterare": "1.2.1", "object-hash": "3.0.0", @@ -2854,7 +2848,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" }, @@ -3204,7 +3197,6 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -3359,7 +3351,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" } @@ -3610,7 +3601,6 @@ "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/types": "8.59.1", @@ -4010,7 +4000,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "devOptional": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4060,7 +4049,6 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -4841,7 +4829,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -5042,7 +5029,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", @@ -5308,15 +5294,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", @@ -6369,7 +6353,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", @@ -6426,7 +6409,6 @@ "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -7425,6 +7407,7 @@ "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=8" }, @@ -8121,7 +8104,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -9655,6 +9637,7 @@ "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.10.tgz", "integrity": "sha512-iCZNq+HszvF+fC3anCm4nBmWEnbeIAfpDs6IStAEKhQ2YSgkjzVG2FF9XJqwwQh5bH3N9OUTUt4QwVN6MLMLtA==", "license": "MIT", + "peer": true, "optionalDependencies": { "msgpackr-extract": "^3.0.2" } @@ -10109,7 +10092,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", @@ -10307,7 +10289,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", @@ -10573,7 +10554,6 @@ "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -10839,7 +10819,6 @@ "resolved": "https://registry.npmjs.org/redis/-/redis-5.12.1.tgz", "integrity": "sha512-LDsoVvb/CpoV9EN3FXvgvSHNJWuCIzl9MiO3ppOevuGLpSGJhwfQjpEwfFJcQvNSddHADDdZaWx0HnmMxRXG7g==", "license": "MIT", - "peer": true, "dependencies": { "@redis/bloom": "5.12.1", "@redis/client": "5.12.1", @@ -11204,7 +11183,6 @@ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -12390,7 +12368,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -12556,7 +12533,6 @@ "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.28.tgz", "integrity": "sha512-6GH7wXhtfq2D33ZuRXYwIsl/qM5685WZcODZb7noOOcRMteM9KF2x2ap3H0EBjnSV0VO4gNAfJT5Ukp0PkOlvg==", "license": "MIT", - "peer": true, "dependencies": { "@sqltools/formatter": "^1.2.5", "ansis": "^4.2.0", @@ -12782,7 +12758,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13161,6 +13136,7 @@ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -13179,6 +13155,7 @@ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -13193,6 +13170,7 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=4.0" } @@ -13203,6 +13181,7 @@ "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -13270,7 +13249,6 @@ "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", "license": "MIT", - "peer": true, "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", diff --git a/backend/src/access-logs/access-logs.service.ts b/backend/src/access-logs/access-logs.service.ts index cbf1a7b..0c1bdf3 100644 --- a/backend/src/access-logs/access-logs.service.ts +++ b/backend/src/access-logs/access-logs.service.ts @@ -1,70 +1,77 @@ -import { Injectable, Logger } from "@nestjs/common" -import { type Repository, Between, type FindOptionsWhere } from "typeorm" -import type { AccessLog } from "./entities/access-log.entity" -import type { CreateAccessLogDto } from "./dto/create-access-log.dto" -import type { FilterAccessLogsDto } from "./dto/filter-access-logs.dto" +import { Injectable, Logger } from '@nestjs/common'; +import { type Repository, Between, type FindOptionsWhere } from 'typeorm'; +import type { AccessLog } from './entities/access-log.entity'; +import type { CreateAccessLogDto } from './dto/create-access-log.dto'; +import type { FilterAccessLogsDto } from './dto/filter-access-logs.dto'; export interface PaginatedAccessLogs { - data: AccessLog[] - total: number - page: number - limit: number - totalPages: number + data: AccessLog[]; + total: number; + page: number; + limit: number; + totalPages: number; } @Injectable() export class AccessLogsService { - private readonly logger = new Logger(AccessLogsService.name) + private readonly logger = new Logger(AccessLogsService.name); constructor(private readonly accessLogRepository: Repository) {} async create(createAccessLogDto: CreateAccessLogDto): Promise { try { - const accessLog = this.accessLogRepository.create(createAccessLogDto) - return await this.accessLogRepository.save(accessLog) + const accessLog = this.accessLogRepository.create(createAccessLogDto); + return await this.accessLogRepository.save(accessLog); } catch (error) { - this.logger.error("Failed to create access log", error.stack) - throw error + this.logger.error('Failed to create access log', error.stack); + throw error; } } async findAll(filterDto: FilterAccessLogsDto): Promise { - const { page = 1, limit = 50, sortByDateDesc = true, ...filters } = filterDto + const { + page = 1, + limit = 50, + sortByDateDesc = true, + ...filters + } = filterDto; - const where: FindOptionsWhere = {} + const where: FindOptionsWhere = {}; // Apply filters if (filters.userId) { - where.userId = filters.userId + where.userId = filters.userId; } if (filters.routePath) { - where.routePath = filters.routePath + where.routePath = filters.routePath; } if (filters.httpMethod) { - where.httpMethod = filters.httpMethod + where.httpMethod = filters.httpMethod; } if (filters.ipAddress) { - where.ipAddress = filters.ipAddress + where.ipAddress = filters.ipAddress; } // Date range filter if (filters.startDate || filters.endDate) { - const startDate = filters.startDate ? new Date(filters.startDate) : new Date("1970-01-01") - const endDate = filters.endDate ? new Date(filters.endDate) : new Date() - where.createdAt = Between(startDate, endDate) + const startDate = filters.startDate + ? new Date(filters.startDate) + : new Date('1970-01-01'); + const endDate = filters.endDate ? new Date(filters.endDate) : new Date(); + where.createdAt = Between(startDate, endDate); } const [data, total] = await this.accessLogRepository.findAndCount({ where, order: { - createdAt: sortByDateDesc ? "DESC" : "ASC", + createdAt: sortByDateDesc ? 'DESC' : 'ASC', }, skip: (page - 1) * limit, take: limit, - }) + }); return { data, @@ -72,15 +79,15 @@ export class AccessLogsService { page, limit, totalPages: Math.ceil(total / limit), - } + }; } async findByUser(userId: string, limit = 100): Promise { return this.accessLogRepository.find({ where: { userId }, - order: { createdAt: "DESC" }, + order: { createdAt: 'DESC' }, take: limit, - }) + }); } async findByTimeRange(startDate: Date, endDate: Date): Promise { @@ -88,32 +95,34 @@ export class AccessLogsService { where: { createdAt: Between(startDate, endDate), }, - order: { createdAt: "DESC" }, - }) + order: { createdAt: 'DESC' }, + }); } async getAccessLogStats(userId?: string): Promise<{ - totalRequests: number - uniqueIPs: number - topRoutes: { routePath: string; count: number }[] + totalRequests: number; + uniqueIPs: number; + topRoutes: { routePath: string; count: number }[]; }> { - const queryBuilder = this.accessLogRepository.createQueryBuilder("log") + const queryBuilder = this.accessLogRepository.createQueryBuilder('log'); if (userId) { - queryBuilder.where("log.userId = :userId", { userId }) + queryBuilder.where('log.userId = :userId', { userId }); } - const totalRequests = await queryBuilder.getCount() + const totalRequests = await queryBuilder.getCount(); - const uniqueIPsResult = await queryBuilder.select("COUNT(DISTINCT log.ipAddress)", "count").getRawOne() + const uniqueIPsResult = await queryBuilder + .select('COUNT(DISTINCT log.ipAddress)', 'count') + .getRawOne(); const topRoutesResult = await queryBuilder - .select("log.routePath", "routePath") - .addSelect("COUNT(*)", "count") - .groupBy("log.routePath") - .orderBy("count", "DESC") + .select('log.routePath', 'routePath') + .addSelect('COUNT(*)', 'count') + .groupBy('log.routePath') + .orderBy('count', 'DESC') .limit(10) - .getRawMany() + .getRawMany(); return { totalRequests, @@ -122,12 +131,12 @@ export class AccessLogsService { routePath: route.routePath, count: Number.parseInt(route.count), })), - } + }; } async deleteOldLogs(olderThan: Date): Promise { await this.accessLogRepository.delete({ - createdAt: Between(new Date("1970-01-01"), olderThan), - }) - } + createdAt: Between(new Date('1970-01-01'), olderThan), + }); + } } diff --git a/backend/src/access-logs/entities/access-log.entity.ts b/backend/src/access-logs/entities/access-log.entity.ts index 2bdee2e..6702abe 100644 --- a/backend/src/access-logs/entities/access-log.entity.ts +++ b/backend/src/access-logs/entities/access-log.entity.ts @@ -1,4 +1,9 @@ -import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { + Column, + CreateDateColumn, + Entity, + PrimaryGeneratedColumn, +} from 'typeorm'; @Entity('access_logs') export class AccessLog { diff --git a/backend/src/activity-tracker/activity-tracker.service.ts b/backend/src/activity-tracker/activity-tracker.service.ts index 91db870..6f16332 100644 --- a/backend/src/activity-tracker/activity-tracker.service.ts +++ b/backend/src/activity-tracker/activity-tracker.service.ts @@ -1,8 +1,8 @@ -import { Injectable } from "@nestjs/common" -import type { Repository } from "typeorm" -import type { Activity } from "./entities/activity.entity" -import type { CreateActivityDto } from "./dto/create-activity.dto" -import type { FilterActivityDto } from "./dto/filter-activity.dto" +import { Injectable } from '@nestjs/common'; +import type { Repository } from 'typeorm'; +import type { Activity } from './entities/activity.entity'; +import type { CreateActivityDto } from './dto/create-activity.dto'; +import type { FilterActivityDto } from './dto/filter-activity.dto'; @Injectable() export class ActivityTrackerService { @@ -14,8 +14,8 @@ export class ActivityTrackerService { * @returns The created activity entity. */ async logActivity(createActivityDto: CreateActivityDto): Promise { - const activity = this.activityRepository.create(createActivityDto) - return this.activityRepository.save(activity) + const activity = this.activityRepository.create(createActivityDto); + return this.activityRepository.save(activity); } /** @@ -23,7 +23,9 @@ export class ActivityTrackerService { * @param filterDto The DTO containing filter, pagination, and sort parameters. * @returns An object containing activity entries and total count. */ - async findActivities(filterDto: FilterActivityDto): Promise<{ data: Activity[]; total: number }> { + async findActivities( + filterDto: FilterActivityDto, + ): Promise<{ data: Activity[]; total: number }> { const { userId, actionType, @@ -31,34 +33,40 @@ export class ActivityTrackerService { endDate, page = 1, limit = 10, - sortBy = "timestamp", - sortOrder = "DESC", - } = filterDto + sortBy = 'timestamp', + sortOrder = 'DESC', + } = filterDto; - const queryBuilder = this.activityRepository.createQueryBuilder("activity") + const queryBuilder = this.activityRepository.createQueryBuilder('activity'); // Apply filters if (userId) { - queryBuilder.andWhere("activity.userId = :userId", { userId }) + queryBuilder.andWhere('activity.userId = :userId', { userId }); } if (actionType) { - queryBuilder.andWhere("activity.actionType = :actionType", { actionType }) + queryBuilder.andWhere('activity.actionType = :actionType', { + actionType, + }); } if (startDate) { - queryBuilder.andWhere("activity.timestamp >= :startDate", { startDate: new Date(startDate) }) + queryBuilder.andWhere('activity.timestamp >= :startDate', { + startDate: new Date(startDate), + }); } if (endDate) { - queryBuilder.andWhere("activity.timestamp <= :endDate", { endDate: new Date(endDate) }) + queryBuilder.andWhere('activity.timestamp <= :endDate', { + endDate: new Date(endDate), + }); } // Order by - queryBuilder.orderBy(`activity.${sortBy}`, sortOrder) + queryBuilder.orderBy(`activity.${sortBy}`, sortOrder); // Pagination - queryBuilder.skip((page - 1) * limit).take(limit) + queryBuilder.skip((page - 1) * limit).take(limit); - const [data, total] = await queryBuilder.getManyAndCount() - return { data, total } + const [data, total] = await queryBuilder.getManyAndCount(); + return { data, total }; } /** @@ -69,14 +77,14 @@ export class ActivityTrackerService { async findActivitiesByUserId(userId: string): Promise { return this.activityRepository.find({ where: { userId }, - order: { timestamp: "DESC" }, - }) + order: { timestamp: 'DESC' }, + }); } async findActivitiesByActionType(actionType: string): Promise { return this.activityRepository.find({ where: { actionType }, - order: { timestamp: "DESC" }, - }) + order: { timestamp: 'DESC' }, + }); } } diff --git a/backend/src/auth/auth.module.ts b/backend/src/auth/auth.module.ts index 033d5b9..03395d9 100644 --- a/backend/src/auth/auth.module.ts +++ b/backend/src/auth/auth.module.ts @@ -19,7 +19,10 @@ import { GithubStrategy } from './strategies/github.strategy'; inject: [ConfigService], useFactory: (config: ConfigService) => ({ secret: config.get('JWT_SECRET'), - signOptions: { expiresIn: (config.get('JWT_EXPIRATION') || '1h') as unknown as number }, + signOptions: { + expiresIn: (config.get('JWT_EXPIRATION') || + '1h') as unknown as number, + }, }), }), ], diff --git a/backend/src/auth/auth.service.ts b/backend/src/auth/auth.service.ts index 50f5de3..5aa8a6d 100644 --- a/backend/src/auth/auth.service.ts +++ b/backend/src/auth/auth.service.ts @@ -51,7 +51,9 @@ export class AuthService { async handleOAuthLogin(email: string, fullName?: string) { if (!email) { - throw new BadRequestException('Email is required from the OAuth provider'); + throw new BadRequestException( + 'Email is required from the OAuth provider', + ); } let user = await this.usersService.findByEmail(email); @@ -76,9 +78,12 @@ export class AuthService { } try { - const payload = await this.jwtService.verifyAsync(refreshToken, { - secret: this.getRefreshSecret(), - }); + const payload = await this.jwtService.verifyAsync( + refreshToken, + { + secret: this.getRefreshSecret(), + }, + ); const user = await this.usersService.findById(payload.sub); if (!user) { @@ -135,8 +140,10 @@ export class AuthService { } private getRefreshSecret() { - return this.configService.get('JWT_REFRESH_SECRET') ?? - this.configService.get('JWT_SECRET'); + return ( + this.configService.get('JWT_REFRESH_SECRET') ?? + this.configService.get('JWT_SECRET') + ); } private getRefreshExpiration() { diff --git a/backend/src/auth/guards/roles.guard.ts b/backend/src/auth/guards/roles.guard.ts index bf8d508..db37e4d 100644 --- a/backend/src/auth/guards/roles.guard.ts +++ b/backend/src/auth/guards/roles.guard.ts @@ -1,4 +1,9 @@ -import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common'; +import { + Injectable, + CanActivate, + ExecutionContext, + ForbiddenException, +} from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { JwtService } from '@nestjs/jwt'; import { ConfigService } from '@nestjs/config'; diff --git a/backend/src/auth/strategies/github.strategy.ts b/backend/src/auth/strategies/github.strategy.ts index 98304c5..3ee665b 100644 --- a/backend/src/auth/strategies/github.strategy.ts +++ b/backend/src/auth/strategies/github.strategy.ts @@ -23,7 +23,11 @@ export class GithubStrategy extends PassportStrategy(Strategy, 'github') { }); } - async validate(_accessToken: string, _refreshToken: string, profile: Profile) { + async validate( + _accessToken: string, + _refreshToken: string, + profile: Profile, + ) { return profile; } } diff --git a/backend/src/auth/strategies/google.strategy.ts b/backend/src/auth/strategies/google.strategy.ts index 9c0da93..49bf060 100644 --- a/backend/src/auth/strategies/google.strategy.ts +++ b/backend/src/auth/strategies/google.strategy.ts @@ -23,7 +23,11 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { }); } - async validate(_accessToken: string, _refreshToken: string, profile: Profile) { + async validate( + _accessToken: string, + _refreshToken: string, + profile: Profile, + ) { return profile; } } diff --git a/backend/src/common/filters/http-exception.filter.ts b/backend/src/common/filters/http-exception.filter.ts index b1c67c4..84ea3dc 100644 --- a/backend/src/common/filters/http-exception.filter.ts +++ b/backend/src/common/filters/http-exception.filter.ts @@ -38,10 +38,7 @@ export class HttpExceptionFilter implements ExceptionFilter { path: request.url, }; - this.logger.error( - `${status} -> `, - (exception as Error)?.stack, - ); + this.logger.error(`${status} -> `, (exception as Error)?.stack); if (!this.isProduction && exception instanceof Error) { Object.assign(payload, { stack: exception.stack }); diff --git a/backend/src/common/logger.config.ts b/backend/src/common/logger.config.ts index 4982e68..79f3c58 100644 --- a/backend/src/common/logger.config.ts +++ b/backend/src/common/logger.config.ts @@ -1,7 +1,11 @@ import { WinstonModuleOptions } from 'nest-winston'; import { format, transports } from 'winston'; -const baseFormat = format.combine(format.timestamp(), format.errors({ stack: true }), format.json()); +const baseFormat = format.combine( + format.timestamp(), + format.errors({ stack: true }), + format.json(), +); export function buildWinstonOptions(level?: string): WinstonModuleOptions { return { diff --git a/backend/src/common/middleware/logger.middleware.ts b/backend/src/common/middleware/logger.middleware.ts index 33434f4..33fe79b 100644 --- a/backend/src/common/middleware/logger.middleware.ts +++ b/backend/src/common/middleware/logger.middleware.ts @@ -14,9 +14,10 @@ export class LoggerMiddleware implements NestMiddleware { const start = Date.now(); const userAgent = req.headers['user-agent'] || 'unknown'; const forwarded = req.headers['x-forwarded-for']; - const ip = typeof forwarded === 'string' - ? forwarded.split(',')[0].trim() - : forwarded?.[0] ?? req.ip; + const ip = + typeof forwarded === 'string' + ? forwarded.split(',')[0].trim() + : (forwarded?.[0] ?? req.ip); res.on('finish', () => { const duration = Date.now() - start; diff --git a/backend/src/documents/documents.controller.ts b/backend/src/documents/documents.controller.ts index ae9e6e8..819d34f 100644 --- a/backend/src/documents/documents.controller.ts +++ b/backend/src/documents/documents.controller.ts @@ -37,7 +37,9 @@ const fileFilter: multer.Options['fileFilter'] = (_req, file, callback) => { return callback(null, true); } - return callback(new BadRequestException('Only PDF, PNG, or JPEG files are allowed')); + return callback( + new BadRequestException('Only PDF, PNG, or JPEG files are allowed'), + ); }; @Controller('documents') @@ -78,7 +80,8 @@ export class DocumentsController { return res.status(200).send(existing); } - const uploadDir = this.configService.get('UPLOAD_DIR') || './uploads'; + const uploadDir = + this.configService.get('UPLOAD_DIR') || './uploads'; await fs.mkdir(uploadDir, { recursive: true }); const extension = extname(file.originalname) || ''; @@ -130,7 +133,9 @@ export class DocumentsController { const record = await this.verificationService.findLatestByDocument(id); if (!record) { - throw new NotFoundException('No verification record found for this document'); + throw new NotFoundException( + 'No verification record found for this document', + ); } return record; diff --git a/backend/src/documents/documents.module.ts b/backend/src/documents/documents.module.ts index fe752f1..43e558b 100644 --- a/backend/src/documents/documents.module.ts +++ b/backend/src/documents/documents.module.ts @@ -9,7 +9,6 @@ import { VerificationModule } from '../verification/verification.module'; import { QueueModule } from '../queue/queue.module'; @Module({ - imports: [ ConfigModule, TypeOrmModule.forFeature([Document]), diff --git a/backend/src/documents/documents.service.spec.ts b/backend/src/documents/documents.service.spec.ts index de86779..810adc8 100644 --- a/backend/src/documents/documents.service.spec.ts +++ b/backend/src/documents/documents.service.spec.ts @@ -86,7 +86,10 @@ describe('DocumentsService', () => { ...mockDocument, status: DocumentStatus.VERIFIED, }); - const result = await service.updateStatus('doc-123', DocumentStatus.VERIFIED); + const result = await service.updateStatus( + 'doc-123', + DocumentStatus.VERIFIED, + ); expect(mockRepository.update).toHaveBeenCalledWith('doc-123', { status: DocumentStatus.VERIFIED, }); diff --git a/backend/src/documents/documents.service.ts b/backend/src/documents/documents.service.ts index fbcd509..882f3f3 100644 --- a/backend/src/documents/documents.service.ts +++ b/backend/src/documents/documents.service.ts @@ -27,7 +27,10 @@ export class DocumentsService { return this.documentRepository.findOne({ where: { fileHash } }); } - async updateStatus(id: string, status: DocumentStatus): Promise { + async updateStatus( + id: string, + status: DocumentStatus, + ): Promise { await this.documentRepository.update(id, { status }); return this.findById(id); } diff --git a/backend/src/external-validation/dto/validation-request.dto.ts b/backend/src/external-validation/dto/validation-request.dto.ts index 09c3540..7470a32 100644 --- a/backend/src/external-validation/dto/validation-request.dto.ts +++ b/backend/src/external-validation/dto/validation-request.dto.ts @@ -1,4 +1,8 @@ -import { ValidationType, ValidationStatus, ValidationResult } from '../entities/validation-request.entity'; +import { + ValidationType, + ValidationStatus, + ValidationResult, +} from '../entities/validation-request.entity'; export class CreateValidationRequestDto { documentId: string; diff --git a/backend/src/external-validation/entities/validation-request.entity.ts b/backend/src/external-validation/entities/validation-request.entity.ts index 364337e..e18d719 100644 --- a/backend/src/external-validation/entities/validation-request.entity.ts +++ b/backend/src/external-validation/entities/validation-request.entity.ts @@ -1,4 +1,9 @@ -import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { + Column, + CreateDateColumn, + Entity, + PrimaryGeneratedColumn, +} from 'typeorm'; export enum ValidationStatus { PENDING = 'PENDING', @@ -40,7 +45,11 @@ export class ValidationRequest { @Column({ type: 'jsonb', nullable: true }) metadata: Record; - @Column({ type: 'enum', enum: ValidationStatus, default: ValidationStatus.PENDING }) + @Column({ + type: 'enum', + enum: ValidationStatus, + default: ValidationStatus.PENDING, + }) status: ValidationStatus; @Column({ type: 'enum', enum: ValidationResult, nullable: true }) diff --git a/backend/src/external-validation/external-validation.service.ts b/backend/src/external-validation/external-validation.service.ts index 0ac9002..401062b 100644 --- a/backend/src/external-validation/external-validation.service.ts +++ b/backend/src/external-validation/external-validation.service.ts @@ -1,26 +1,31 @@ -import { Injectable, Logger, NotFoundException, BadRequestException } from "@nestjs/common" -import type { Repository } from "typeorm" +import { + Injectable, + Logger, + NotFoundException, + BadRequestException, +} from '@nestjs/common'; +import type { Repository } from 'typeorm'; import { ValidationRequest, type ValidationProvider, ValidationStatus, ValidationResult, ValidationType, -} from "./entities/validation-request.entity" +} from './entities/validation-request.entity'; import type { CreateValidationRequestDto, QueryValidationRequestDto, RetryValidationDto, -} from "./dto/validation-request.dto" -import type { IValidationProvider } from "./interfaces/validation-provider.interface" -import type { LandRegistryProvider } from "./providers/land-registry.provider" -import type { GovernmentIdProvider } from "./providers/government-id.provider" -import type { BusinessRegistrationProvider } from "./providers/business-registration.provider" +} from './dto/validation-request.dto'; +import type { IValidationProvider } from './interfaces/validation-provider.interface'; +import type { LandRegistryProvider } from './providers/land-registry.provider'; +import type { GovernmentIdProvider } from './providers/government-id.provider'; +import type { BusinessRegistrationProvider } from './providers/business-registration.provider'; @Injectable() export class ExternalValidationService { - private readonly logger = new Logger(ExternalValidationService.name) - private readonly providers = new Map() + private readonly logger = new Logger(ExternalValidationService.name); + private readonly providers = new Map(); constructor( private validationRequestRepository: Repository, @@ -29,24 +34,33 @@ export class ExternalValidationService { private governmentIdProvider: GovernmentIdProvider, private businessRegistrationProvider: BusinessRegistrationProvider, ) { - this.initializeProviders() + this.initializeProviders(); } private initializeProviders() { - this.providers.set(ValidationType.LAND_REGISTRY, this.landRegistryProvider) - this.providers.set(ValidationType.GOVERNMENT_ID, this.governmentIdProvider) - this.providers.set(ValidationType.BUSINESS_REGISTRATION, this.businessRegistrationProvider) - - this.logger.log(`Initialized ${this.providers.size} validation providers`) + this.providers.set(ValidationType.LAND_REGISTRY, this.landRegistryProvider); + this.providers.set(ValidationType.GOVERNMENT_ID, this.governmentIdProvider); + this.providers.set( + ValidationType.BUSINESS_REGISTRATION, + this.businessRegistrationProvider, + ); + + this.logger.log(`Initialized ${this.providers.size} validation providers`); } - async createValidationRequest(createDto: CreateValidationRequestDto): Promise { - this.logger.log(`Creating validation request for document: ${createDto.documentId}`) + async createValidationRequest( + createDto: CreateValidationRequestDto, + ): Promise { + this.logger.log( + `Creating validation request for document: ${createDto.documentId}`, + ); // Check if provider is available - const provider = this.providers.get(createDto.validationType) + const provider = this.providers.get(createDto.validationType); if (!provider) { - throw new BadRequestException(`No provider available for validation type: ${createDto.validationType}`) + throw new BadRequestException( + `No provider available for validation type: ${createDto.validationType}`, + ); } // Create validation request record @@ -57,44 +71,54 @@ export class ExternalValidationService { requestedBy: createDto.requestedBy, metadata: createDto.metadata, status: ValidationStatus.PENDING, - }) + }); - const savedRequest = await this.validationRequestRepository.save(validationRequest) + const savedRequest = + await this.validationRequestRepository.save(validationRequest); // Process validation asynchronously - this.processValidationAsync(savedRequest.id) + this.processValidationAsync(savedRequest.id); - return savedRequest + return savedRequest; } private async processValidationAsync(requestId: string) { try { - await this.processValidation(requestId) + await this.processValidation(requestId); } catch (error) { - this.logger.error(`Async validation processing failed for request ${requestId}: ${error.message}`, error.stack) + this.logger.error( + `Async validation processing failed for request ${requestId}: ${error.message}`, + error.stack, + ); } } async processValidation(requestId: string): Promise { - const request = await this.validationRequestRepository.findOne({ where: { id: requestId } }) + const request = await this.validationRequestRepository.findOne({ + where: { id: requestId }, + }); if (!request) { - throw new NotFoundException(`Validation request ${requestId} not found`) + throw new NotFoundException(`Validation request ${requestId} not found`); } if (request.status !== ValidationStatus.PENDING) { - throw new BadRequestException(`Validation request ${requestId} is not in pending status`) + throw new BadRequestException( + `Validation request ${requestId} is not in pending status`, + ); } - this.logger.log(`Processing validation request: ${requestId}`) + this.logger.log(`Processing validation request: ${requestId}`); // Update status to in progress - request.status = ValidationStatus.IN_PROGRESS - await this.validationRequestRepository.save(request) + request.status = ValidationStatus.IN_PROGRESS; + await this.validationRequestRepository.save(request); try { - const provider = this.providers.get(request.validationType) + const provider = this.providers.get(request.validationType); if (!provider) { - throw new Error(`No provider available for validation type: ${request.validationType}`) + throw new Error( + `No provider available for validation type: ${request.validationType}`, + ); } // Perform validation @@ -103,116 +127,151 @@ export class ExternalValidationService { validationType: request.validationType, payload: request.requestPayload, metadata: request.metadata, - }) + }); // Update request with response - request.status = ValidationStatus.COMPLETED - request.result = validationResponse.result - request.responsePayload = validationResponse.data + request.status = ValidationStatus.COMPLETED; + request.result = validationResponse.result; + request.responsePayload = validationResponse.data; request.validationDetails = { confidenceScore: validationResponse.confidenceScore, externalReferenceId: validationResponse.externalReferenceId, validatedAt: validationResponse.validatedAt, expiresAt: validationResponse.expiresAt, metadata: validationResponse.metadata, - } - request.validatedAt = validationResponse.validatedAt - request.expiresAt = validationResponse.expiresAt - request.confidenceScore = validationResponse.confidenceScore - request.externalReferenceId = validationResponse.externalReferenceId + }; + request.validatedAt = validationResponse.validatedAt; + request.expiresAt = validationResponse.expiresAt; + request.confidenceScore = validationResponse.confidenceScore; + request.externalReferenceId = validationResponse.externalReferenceId; if (!validationResponse.success) { - request.errorMessage = validationResponse.errorMessage + request.errorMessage = validationResponse.errorMessage; } - const updatedRequest = await this.validationRequestRepository.save(request) + const updatedRequest = + await this.validationRequestRepository.save(request); - this.logger.log(`Validation completed for request ${requestId}: ${request.result}`) - return updatedRequest + this.logger.log( + `Validation completed for request ${requestId}: ${request.result}`, + ); + return updatedRequest; } catch (error) { - this.logger.error(`Validation failed for request ${requestId}: ${error.message}`, error.stack) + this.logger.error( + `Validation failed for request ${requestId}: ${error.message}`, + error.stack, + ); // Update request with error - request.status = ValidationStatus.FAILED - request.result = ValidationResult.ERROR - request.errorMessage = error.message - request.responsePayload = { error: error.message } + request.status = ValidationStatus.FAILED; + request.result = ValidationResult.ERROR; + request.errorMessage = error.message; + request.responsePayload = { error: error.message }; - return this.validationRequestRepository.save(request) + return this.validationRequestRepository.save(request); } } - async findAll(queryDto: QueryValidationRequestDto): Promise<{ requests: ValidationRequest[]; total: number }> { - const { documentId, validationType, status, result, requestedBy, limit, offset } = queryDto - - const queryBuilder = this.validationRequestRepository.createQueryBuilder("validation_request") + async findAll( + queryDto: QueryValidationRequestDto, + ): Promise<{ requests: ValidationRequest[]; total: number }> { + const { + documentId, + validationType, + status, + result, + requestedBy, + limit, + offset, + } = queryDto; + + const queryBuilder = + this.validationRequestRepository.createQueryBuilder('validation_request'); if (documentId) { - queryBuilder.andWhere("validation_request.documentId = :documentId", { documentId }) + queryBuilder.andWhere('validation_request.documentId = :documentId', { + documentId, + }); } if (validationType) { - queryBuilder.andWhere("validation_request.validationType = :validationType", { validationType }) + queryBuilder.andWhere( + 'validation_request.validationType = :validationType', + { validationType }, + ); } if (status) { - queryBuilder.andWhere("validation_request.status = :status", { status }) + queryBuilder.andWhere('validation_request.status = :status', { status }); } if (result) { - queryBuilder.andWhere("validation_request.result = :result", { result }) + queryBuilder.andWhere('validation_request.result = :result', { result }); } if (requestedBy) { - queryBuilder.andWhere("validation_request.requestedBy = :requestedBy", { requestedBy }) + queryBuilder.andWhere('validation_request.requestedBy = :requestedBy', { + requestedBy, + }); } - queryBuilder.orderBy("validation_request.createdAt", "DESC").skip(offset).take(limit) + queryBuilder + .orderBy('validation_request.createdAt', 'DESC') + .skip(offset) + .take(limit); - const [requests, total] = await queryBuilder.getManyAndCount() + const [requests, total] = await queryBuilder.getManyAndCount(); - return { requests, total } + return { requests, total }; } async findOne(id: string): Promise { - const request = await this.validationRequestRepository.findOne({ where: { id } }) + const request = await this.validationRequestRepository.findOne({ + where: { id }, + }); if (!request) { - throw new NotFoundException(`Validation request with ID ${id} not found`) + throw new NotFoundException(`Validation request with ID ${id} not found`); } - return request + return request; } async findByDocument(documentId: string): Promise { return this.validationRequestRepository.find({ where: { documentId }, - order: { createdAt: "DESC" }, - }) + order: { createdAt: 'DESC' }, + }); } - async retryValidation(requestId: string, retryDto: RetryValidationDto): Promise { - const request = await this.findOne(requestId) + async retryValidation( + requestId: string, + retryDto: RetryValidationDto, + ): Promise { + const request = await this.findOne(requestId); if (request.status === ValidationStatus.IN_PROGRESS) { - throw new BadRequestException("Validation is already in progress") + throw new BadRequestException('Validation is already in progress'); } // Update payload if provided if (retryDto.updatedPayload) { - request.requestPayload = { ...request.requestPayload, ...retryDto.updatedPayload } + request.requestPayload = { + ...request.requestPayload, + ...retryDto.updatedPayload, + }; } // Reset status and clear previous results - request.status = ValidationStatus.PENDING - request.result = null - request.responsePayload = null - request.validationDetails = null - request.errorMessage = null - request.validatedAt = null - request.expiresAt = null - request.confidenceScore = null - request.externalReferenceId = null + request.status = ValidationStatus.PENDING; + request.result = null; + request.responsePayload = null; + request.validationDetails = null; + request.errorMessage = null; + request.validatedAt = null; + request.expiresAt = null; + request.confidenceScore = null; + request.externalReferenceId = null; // Add retry metadata request.metadata = { @@ -220,76 +279,85 @@ export class ExternalValidationService { retryCount: (request.metadata?.retryCount || 0) + 1, retryReason: retryDto.reason, retriedAt: new Date().toISOString(), - } + }; - const updatedRequest = await this.validationRequestRepository.save(request) + const updatedRequest = await this.validationRequestRepository.save(request); // Process validation asynchronously - this.processValidationAsync(updatedRequest.id) + this.processValidationAsync(updatedRequest.id); - return updatedRequest + return updatedRequest; } async getValidationStats(): Promise { const stats = await this.validationRequestRepository - .createQueryBuilder("validation_request") + .createQueryBuilder('validation_request') .select([ - "validation_request.validationType as validationType", - "validation_request.status as status", - "validation_request.result as result", - "COUNT(*) as count", - "AVG(validation_request.confidenceScore) as avgConfidenceScore", + 'validation_request.validationType as validationType', + 'validation_request.status as status', + 'validation_request.result as result', + 'COUNT(*) as count', + 'AVG(validation_request.confidenceScore) as avgConfidenceScore', ]) - .groupBy("validation_request.validationType, validation_request.status, validation_request.result") - .getRawMany() + .groupBy( + 'validation_request.validationType, validation_request.status, validation_request.result', + ) + .getRawMany(); - return stats + return stats; } async checkProviderHealth(): Promise> { - const healthStatus: Record = {} + const healthStatus: Record = {}; for (const [type, provider] of this.providers.entries()) { try { - healthStatus[type] = await provider.healthCheck() + healthStatus[type] = await provider.healthCheck(); } catch (error) { - this.logger.error(`Health check failed for provider ${type}: ${error.message}`) - healthStatus[type] = false + this.logger.error( + `Health check failed for provider ${type}: ${error.message}`, + ); + healthStatus[type] = false; } } - return healthStatus + return healthStatus; } async isDocumentValidated( documentId: string, ): Promise<{ isValidated: boolean; validationResults: ValidationRequest[] }> { - const validationResults = await this.findByDocument(documentId) + const validationResults = await this.findByDocument(documentId); const completedValidations = validationResults.filter( - (v) => v.status === ValidationStatus.COMPLETED && v.result === ValidationResult.VALID, - ) + (v) => + v.status === ValidationStatus.COMPLETED && + v.result === ValidationResult.VALID, + ); return { isValidated: completedValidations.length > 0, validationResults, - } + }; } - async expireOldValidations(daysToExpire = 30): Promise { - const cutoffDate = new Date() - cutoffDate.setDate(cutoffDate.getDate() - daysToExpire) - + async expireOldValidations(daysToExpire = 30): Promise { + const cutoffDate = new Date(); + cutoffDate.setDate(cutoffDate.getDate() - daysToExpire); + const result = await this.validationRequestRepository .createQueryBuilder() .update(ValidationRequest) .set({ status: ValidationStatus.EXPIRED }) - .where("expiresAt < :cutoffDate", { cutoffDate }) - .andWhere("status IN (:...activeStatuses)", { - activeStatuses: [ValidationStatus.PENDING, ValidationStatus.IN_PROGRESS, ValidationStatus.COMPLETED], + .where('expiresAt < :cutoffDate', { cutoffDate }) + .andWhere('status IN (:...activeStatuses)', { + activeStatuses: [ + ValidationStatus.PENDING, + ValidationStatus.IN_PROGRESS, + ValidationStatus.COMPLETED, + ], }) - .execute() - return result.affected || 0 - + .execute(); + return result.affected || 0; } } diff --git a/backend/src/external-validation/interfaces/validation-provider.interface.ts b/backend/src/external-validation/interfaces/validation-provider.interface.ts index 1d14ad1..ef332c9 100644 --- a/backend/src/external-validation/interfaces/validation-provider.interface.ts +++ b/backend/src/external-validation/interfaces/validation-provider.interface.ts @@ -1,4 +1,7 @@ -import { ValidationType, ValidationResult } from '../entities/validation-request.entity'; +import { + ValidationType, + ValidationResult, +} from '../entities/validation-request.entity'; export interface ValidationDocumentParams { documentId: string; @@ -20,6 +23,8 @@ export interface ValidationResponse { } export interface IValidationProvider { - validateDocument(params: ValidationDocumentParams): Promise; + validateDocument( + params: ValidationDocumentParams, + ): Promise; healthCheck(): Promise; } diff --git a/backend/src/external-validation/providers/business-registration.provider.ts b/backend/src/external-validation/providers/business-registration.provider.ts index bf1e5f8..2ad2536 100644 --- a/backend/src/external-validation/providers/business-registration.provider.ts +++ b/backend/src/external-validation/providers/business-registration.provider.ts @@ -1,5 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { IValidationProvider, ValidationResponse } from '../interfaces/validation-provider.interface'; +import { + IValidationProvider, + ValidationResponse, +} from '../interfaces/validation-provider.interface'; import { ValidationResult } from '../entities/validation-request.entity'; @Injectable() diff --git a/backend/src/external-validation/providers/government-id.provider.ts b/backend/src/external-validation/providers/government-id.provider.ts index 3c8498d..d60832e 100644 --- a/backend/src/external-validation/providers/government-id.provider.ts +++ b/backend/src/external-validation/providers/government-id.provider.ts @@ -1,5 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { IValidationProvider, ValidationResponse } from '../interfaces/validation-provider.interface'; +import { + IValidationProvider, + ValidationResponse, +} from '../interfaces/validation-provider.interface'; import { ValidationResult } from '../entities/validation-request.entity'; @Injectable() diff --git a/backend/src/external-validation/providers/land-registry.provider.ts b/backend/src/external-validation/providers/land-registry.provider.ts index 7afc39c..5f16b72 100644 --- a/backend/src/external-validation/providers/land-registry.provider.ts +++ b/backend/src/external-validation/providers/land-registry.provider.ts @@ -1,5 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { IValidationProvider, ValidationResponse } from '../interfaces/validation-provider.interface'; +import { + IValidationProvider, + ValidationResponse, +} from '../interfaces/validation-provider.interface'; import { ValidationResult } from '../entities/validation-request.entity'; @Injectable() diff --git a/backend/src/queue/document.processor.ts b/backend/src/queue/document.processor.ts index 50269e3..072aaca 100644 --- a/backend/src/queue/document.processor.ts +++ b/backend/src/queue/document.processor.ts @@ -37,7 +37,11 @@ export class DocumentProcessor implements OnModuleDestroy { ); this.worker.on('failed', (job, err) => { - this.logger.error(`Job ${job.id} (${job.name}) failed`, err?.message, err?.stack); + this.logger.error( + `Job ${job.id} (${job.name}) failed`, + err?.message, + err?.stack, + ); }); } @@ -48,7 +52,9 @@ export class DocumentProcessor implements OnModuleDestroy { return; } - const { txHash, ledger } = await this.stellarService.anchorHash(document.fileHash); + const { txHash, ledger } = await this.stellarService.anchorHash( + document.fileHash, + ); await this.verificationService.create({ documentId, stellarTxHash: txHash, @@ -57,7 +63,10 @@ export class DocumentProcessor implements OnModuleDestroy { status: VerificationStatus.CONFIRMED, }); - await this.documentsService.updateStatus(documentId, DocumentStatus.VERIFIED); + await this.documentsService.updateStatus( + documentId, + DocumentStatus.VERIFIED, + ); this.logger.log(`Document ${documentId} verified on ledger ${ledger}`); } diff --git a/backend/src/queue/queue.service.ts b/backend/src/queue/queue.service.ts index c1e875f..978662e 100644 --- a/backend/src/queue/queue.service.ts +++ b/backend/src/queue/queue.service.ts @@ -24,7 +24,8 @@ export class QueueService implements OnModuleDestroy { private buildConnection(): RedisConnectionOptions { const host = this.configService.get('REDIS_HOST') || '127.0.0.1'; const port = Number(this.configService.get('REDIS_PORT') || '6379'); - const password = this.configService.get('REDIS_PASSWORD') || undefined; + const password = + this.configService.get('REDIS_PASSWORD') || undefined; return { host, port, password }; } diff --git a/backend/src/risk-assessment/risk-assessment.service.ts b/backend/src/risk-assessment/risk-assessment.service.ts index e47365a..71b25f5 100644 --- a/backend/src/risk-assessment/risk-assessment.service.ts +++ b/backend/src/risk-assessment/risk-assessment.service.ts @@ -51,7 +51,9 @@ export class RiskAssessmentService { flags.push(RiskFlag.MISSING_PARCEL_ID); } - const ownerDocuments = await this.documentsService.findByOwner(document.ownerId); + const ownerDocuments = await this.documentsService.findByOwner( + document.ownerId, + ); if (ownerDocuments.some((doc) => doc.id !== document.id)) { flags.push(RiskFlag.OVERLAPPING_CLAIM); } @@ -80,7 +82,10 @@ export class RiskAssessmentService { } private calculateScore(flags: RiskFlag[]): number { - const rawScore = flags.reduce((total, flag) => total + (FLAG_WEIGHTS[flag] ?? 0), 0); + const rawScore = flags.reduce( + (total, flag) => total + (FLAG_WEIGHTS[flag] ?? 0), + 0, + ); return Math.min(100, Math.max(0, rawScore)); } } diff --git a/backend/src/stellar/stellar.service.ts b/backend/src/stellar/stellar.service.ts index a198c6a..d8d2ec9 100644 --- a/backend/src/stellar/stellar.service.ts +++ b/backend/src/stellar/stellar.service.ts @@ -1,6 +1,16 @@ -import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; +import { + Injectable, + InternalServerErrorException, + Logger, +} from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { Keypair, Horizon, Networks, Operation, TransactionBuilder } from 'stellar-sdk'; +import { + Keypair, + Horizon, + Networks, + Operation, + TransactionBuilder, +} from 'stellar-sdk'; @Injectable() export class StellarService { @@ -12,12 +22,16 @@ export class StellarService { constructor(private readonly configService: ConfigService) { const secretKey = this.configService.get('STELLAR_SECRET_KEY'); - const horizonUrl = this.configService.get('STELLAR_HORIZON_URL') || 'https://horizon-testnet.stellar.org'; + const horizonUrl = + this.configService.get('STELLAR_HORIZON_URL') || + 'https://horizon-testnet.stellar.org'; this.networkPassphrase = this.configService.get('STELLAR_NETWORK') || Networks.TESTNET; if (!secretKey) { - throw new InternalServerErrorException('Stellar secret key is not configured'); + throw new InternalServerErrorException( + 'Stellar secret key is not configured', + ); } this.anchorKeypair = Keypair.fromSecret(secretKey); @@ -33,7 +47,9 @@ export class StellarService { async anchorHash(hash: string): Promise<{ txHash: string; ledger: number }> { if (!hash) { - throw new InternalServerErrorException('Hash is required to anchor a document'); + throw new InternalServerErrorException( + 'Hash is required to anchor a document', + ); } try { @@ -56,13 +72,17 @@ export class StellarService { return { txHash: result.hash, ledger: result.ledger }; } catch (error) { this.logger.error('Failed to anchor document hash', error); - throw new InternalServerErrorException('Unable to anchor document hash on Stellar'); + throw new InternalServerErrorException( + 'Unable to anchor document hash on Stellar', + ); } } async verifyHash(hash: string): Promise { if (!hash) { - throw new InternalServerErrorException('Hash is required to verify a document'); + throw new InternalServerErrorException( + 'Hash is required to verify a document', + ); } try { @@ -74,7 +94,9 @@ export class StellarService { return false; } this.logger.error('Failed to verify document hash', error); - throw new InternalServerErrorException('Unable to verify document hash on Stellar'); + throw new InternalServerErrorException( + 'Unable to verify document hash on Stellar', + ); } } } diff --git a/backend/src/users/users.service.ts b/backend/src/users/users.service.ts index 3fe1012..e3cdd9d 100644 --- a/backend/src/users/users.service.ts +++ b/backend/src/users/users.service.ts @@ -5,7 +5,9 @@ import { User } from './entities/user.entity'; @Injectable() export class UsersService { - constructor(@InjectRepository(User) private readonly userRepository: Repository) {} + constructor( + @InjectRepository(User) private readonly userRepository: Repository, + ) {} async create(data: Partial): Promise { const user = this.userRepository.create(data); diff --git a/backend/src/verification/verification.service.ts b/backend/src/verification/verification.service.ts index 398b2d7..dfeaeb1 100644 --- a/backend/src/verification/verification.service.ts +++ b/backend/src/verification/verification.service.ts @@ -30,7 +30,10 @@ export class VerificationService { }); } - async updateStatus(id: string, status: VerificationStatus): Promise { + async updateStatus( + id: string, + status: VerificationStatus, + ): Promise { await this.verificationRepository.update(id, { status }); return this.verificationRepository.findOne({ where: { id } }); } diff --git a/frontend/app/[locale]/layout.tsx b/frontend/app/[locale]/layout.tsx index e1572c9..6e25391 100644 --- a/frontend/app/[locale]/layout.tsx +++ b/frontend/app/[locale]/layout.tsx @@ -1,30 +1,3 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import { notFound } from "next/navigation"; -import { NextIntlClientProvider } from "next-intl"; -import { getMessages, setRequestLocale } from "next-intl/server"; -import { routing } from "@/i18n/routing"; -import "../globals.css"; - -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); - -export const metadata: Metadata = { - title: "SMALDA", - description: "Secure land document verification and analysis", -}; - -export function generateStaticParams() { - return routing.locales.map((locale) => ({ locale })); -} - export default async function LocaleLayout({ children, params, @@ -34,26 +7,5 @@ export default async function LocaleLayout({ }>) { const { locale } = await params; - // Guard against unsupported locales reaching the layout. - if (!(routing.locales as readonly string[]).includes(locale)) { - notFound(); - } - - // Enable static rendering for the resolved locale. - setRequestLocale(locale); - - // Provide the message catalog to Client Components below. - const messages = await getMessages(); - - return ( - - - - {children} - - - - ); + return children; } diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx new file mode 100644 index 0000000..c552994 --- /dev/null +++ b/frontend/app/layout.tsx @@ -0,0 +1,55 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import { NextIntlClientProvider } from "next-intl"; +import { getMessages, setRequestLocale } from "next-intl/server"; +import { routing } from "@/i18n/routing"; +import { notFound } from "next/navigation"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "SMALDA", + description: "Secure land document verification and analysis", +}; + +export function generateStaticParams() { + return routing.locales.map((locale) => ({ locale })); +} + +export default async function RootLayout({ + children, + params, +}: Readonly<{ + children: React.ReactNode; + params: Promise<{ locale: string }>; +}>) { + const { locale } = await params; + + if (!routing.locales.includes(locale as (typeof routing.locales)[number])) { + notFound(); + } + + setRequestLocale(locale); + const messages = await getMessages(); + + return ( + + + + {children} + + + + ); +} diff --git a/frontend/components/ui/card.tsx b/frontend/components/ui/card.tsx new file mode 100644 index 0000000..0852b78 --- /dev/null +++ b/frontend/components/ui/card.tsx @@ -0,0 +1,43 @@ +import React from "react"; + +type CardProps = React.HTMLAttributes; + +export function Card({ className, children, ...props }: CardProps) { + return ( +
+ {children} +
+ ); +} + +export function CardHeader({ className, children, ...props }: CardProps) { + return ( +
+ {children} +
+ ); +} + +export function CardTitle({ className, children, ...props }: CardProps) { + return ( +

+ {children} +

+ ); +} + +export function CardDescription({ className, children, ...props }: CardProps) { + return ( +

+ {children} +

+ ); +} + +export function CardContent({ className, children, ...props }: CardProps) { + return ( +
+ {children} +
+ ); +} diff --git a/frontend/components/ui/table.tsx b/frontend/components/ui/table.tsx new file mode 100644 index 0000000..97a1749 --- /dev/null +++ b/frontend/components/ui/table.tsx @@ -0,0 +1,51 @@ +import React from "react"; + +type TableProps = React.HTMLAttributes; + +export function Table({ className, children, ...props }: TableProps) { + return ( + + {children} +
+ ); +} + +export function TableHeader({ className, children, ...props }: React.HTMLAttributes) { + return ( + + {children} + + ); +} + +export function TableRow({ className, children, ...props }: React.HTMLAttributes) { + return ( + + {children} + + ); +} + +export function TableHead({ className, children, ...props }: React.ThHTMLAttributes) { + return ( + + {children} + + ); +} + +export function TableBody({ className, children, ...props }: React.HTMLAttributes) { + return ( + + {children} + + ); +} + +export function TableCell({ className, children, ...props }: React.TdHTMLAttributes) { + return ( + + {children} + + ); +} diff --git a/frontend/next.config.ts b/frontend/next.config.ts index 0c05faf..4491209 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -4,7 +4,6 @@ import createNextIntlPlugin from "next-intl/plugin"; const withNextIntl = createNextIntlPlugin("./i18n/request.ts"); const nextConfig: NextConfig = { - /* config options here */ webpack: (config) => { config.resolve.alias = { ...config.resolve.alias, diff --git a/frontend/next.config.ts.bak b/frontend/next.config.ts.bak new file mode 100644 index 0000000..0c05faf --- /dev/null +++ b/frontend/next.config.ts.bak @@ -0,0 +1,17 @@ +import type { NextConfig } from "next"; +import createNextIntlPlugin from "next-intl/plugin"; + +const withNextIntl = createNextIntlPlugin("./i18n/request.ts"); + +const nextConfig: NextConfig = { + /* config options here */ + webpack: (config) => { + config.resolve.alias = { + ...config.resolve.alias, + canvas: false, + }; + return config; + }, +}; + +export default withNextIntl(nextConfig); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b1bc887..7bd61f5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -27,8 +27,9 @@ "devDependencies": { "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4", - "@testing-library/jest-dom": "^6.1.4", + "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.0.0", + "@types/jest": "^30.0.0", "@types/leaflet": "^1.9.20", "@types/node": "^20", "@types/react": "^19.1.12", @@ -1556,6 +1557,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@jest/diff-sequences": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", + "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/environment": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", @@ -1617,6 +1628,16 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/globals": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", @@ -1633,6 +1654,30 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/pattern": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", + "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern/node_modules/jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/reporters": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", @@ -3172,6 +3217,232 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, + "node_modules/@types/jest/node_modules/@jest/expect-utils": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", + "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/@jest/schemas": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/@jest/types": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", + "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.4.0", + "@jest/schemas": "30.4.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jest/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@types/jest/node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@types/jest/node_modules/expect": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.4.1", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/jest-diff": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", + "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.4.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/jest-matcher-utils": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", + "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.4.1", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/jest-message-util": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", + "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.4.1", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-util": "30.4.1", + "picomatch": "^4.0.3", + "pretty-format": "30.4.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/jest-mock": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", + "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/jest-util": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", + "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@types/jest/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@types/jsdom": { "version": "20.0.1", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", @@ -9023,17 +9294,6 @@ } } }, - "node_modules/next-intl/node_modules/@swc/helpers": { - "version": "0.5.23", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", - "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.8.0" - } - }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -9802,6 +10062,22 @@ "license": "MIT", "peer": true }, + "node_modules/react-is-18": { + "name": "react-is", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-is-19": { + "name": "react-is", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", + "dev": true, + "license": "MIT" + }, "node_modules/react-leaflet": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-5.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index fab7322..b57d238 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,8 +31,9 @@ "devDependencies": { "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4", - "@testing-library/jest-dom": "^6.1.4", + "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.0.0", + "@types/jest": "^30.0.0", "@types/leaflet": "^1.9.20", "@types/node": "^20", "@types/react": "^19.1.12", diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index d8b9323..5044554 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -1,27 +1,40 @@ { - "compilerOptions": { - "target": "ES2017", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ], - "paths": { - "@/*": ["./*"] - } - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "compilerOptions": { + "target": "ES2017", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": [ + "./*" + ] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] } diff --git a/frontend/types/lucide-react.d.ts b/frontend/types/lucide-react.d.ts new file mode 100644 index 0000000..b5ffa53 --- /dev/null +++ b/frontend/types/lucide-react.d.ts @@ -0,0 +1,16 @@ +declare module "lucide-react" { + import { FC, SVGProps } from "react"; + + type LucideIcon = FC>; + + export const CircleUser: LucideIcon; + export const AlertTriangle: LucideIcon; + export const CheckCircle2: LucideIcon; + export const IdCard: LucideIcon; + export const Landmark: LucideIcon; + export const RefreshCw: LucideIcon; + export const XCircle: LucideIcon; + export const Building2: LucideIcon; + + export type { LucideIcon }; +} diff --git a/frontend/types/test-setup.d.ts b/frontend/types/test-setup.d.ts new file mode 100644 index 0000000..d0de870 --- /dev/null +++ b/frontend/types/test-setup.d.ts @@ -0,0 +1 @@ +import "@testing-library/jest-dom"; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ba7937e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "SMALDA", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} From 6c370f389d24d1b86b3bd7fe3dd985084a774a62 Mon Sep 17 00:00:00 2001 From: DIVINE Date: Mon, 27 Jul 2026 13:59:00 +0100 Subject: [PATCH 3/3] build fix --- package-lock.json | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index ba7937e..0000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "SMALDA", - "lockfileVersion": 3, - "requires": true, - "packages": {} -}