From f9ea94abeb1fc051200a259a25bc51196e8287a9 Mon Sep 17 00:00:00 2001 From: Agbasimere Date: Sun, 29 Mar 2026 14:38:54 +0100 Subject: [PATCH 1/6] feat: enforce API versioning with /v1 prefix --- next.config.ts | 8 + src/app/api-docs/page.tsx | 3 +- .../{ => v1}/auth/2fa/disable/route.test.ts | 4 +- .../api/{ => v1}/auth/2fa/disable/route.ts | 0 .../2fa/regenerate-backup-codes/route.test.ts | 4 +- .../auth/2fa/regenerate-backup-codes/route.ts | 0 .../api/{ => v1}/auth/2fa/setup/route.test.ts | 4 +- src/app/api/{ => v1}/auth/2fa/setup/route.ts | 0 .../{ => v1}/auth/2fa/status/route.test.ts | 4 +- src/app/api/{ => v1}/auth/2fa/status/route.ts | 0 .../auth/2fa/verify-setup/route.test.ts | 4 +- .../{ => v1}/auth/2fa/verify-setup/route.ts | 0 .../{ => v1}/auth/2fa/verify/route.test.ts | 4 +- src/app/api/{ => v1}/auth/2fa/verify/route.ts | 0 .../api/{ => v1}/auth/account/route.test.ts | 6 +- src/app/api/{ => v1}/auth/account/route.ts | 0 src/app/api/{ => v1}/auth/apple/route.test.ts | 4 +- src/app/api/{ => v1}/auth/apple/route.ts | 0 .../auth/change-password/route.test.ts | 4 +- .../{ => v1}/auth/change-password/route.ts | 0 .../{ => v1}/auth/forgot-password/route.ts | 0 .../api/{ => v1}/auth/google/route.spec.ts | 22 +- .../api/{ => v1}/auth/google/route.test.ts | 4 +- src/app/api/{ => v1}/auth/google/route.ts | 0 src/app/api/{ => v1}/auth/login/route.test.ts | 4 +- src/app/api/{ => v1}/auth/login/route.ts | 0 .../api/{ => v1}/auth/logout/route.test.ts | 4 +- src/app/api/{ => v1}/auth/logout/route.ts | 149 ++++++------- .../api/{ => v1}/auth/refresh/route.test.ts | 4 +- src/app/api/{ => v1}/auth/refresh/route.ts | 197 ++++++++++-------- .../api/{ => v1}/auth/register/route.test.ts | 4 +- src/app/api/{ => v1}/auth/register/route.ts | 0 .../{ => v1}/auth/resend-otp/route.test.ts | 4 +- src/app/api/{ => v1}/auth/resend-otp/route.ts | 0 .../api/{ => v1}/auth/reset-password/route.ts | 0 .../{ => v1}/auth/verify-email/route.test.ts | 4 +- .../api/{ => v1}/auth/verify-email/route.ts | 0 src/app/api/{ => v1}/company/profile/route.ts | 2 +- .../dashboard/attention/route.test.ts | 4 +- .../api/{ => v1}/dashboard/attention/route.ts | 2 +- .../dashboard/onboarding/route.test.ts | 4 +- .../{ => v1}/dashboard/onboarding/route.ts | 2 +- .../dashboard/user-summary/route.test.ts | 4 +- .../{ => v1}/dashboard/user-summary/route.ts | 2 +- src/app/api/{ => v1}/docs/route.ts | 0 .../finance/transactions/[id]/route.test.ts | 0 .../finance/transactions/[id]/route.ts | 0 .../{ => v1}/finance/transactions/route.ts | 2 +- src/app/api/{ => v1}/kyb/status/route.test.ts | 4 +- src/app/api/{ => v1}/kyb/status/route.ts | 0 src/app/api/{ => v1}/kyb/submit/route.test.ts | 4 +- src/app/api/{ => v1}/kyb/submit/route.ts | 0 .../api/{ => v1}/team/employees/[id]/route.ts | 0 .../api/{ => v1}/team/employees/route.test.ts | 6 +- src/app/api/{ => v1}/team/employees/route.ts | 0 .../team/expenses/[id]/status/route.test.ts | 0 .../team/expenses/[id]/status/route.ts | 0 src/app/api/{ => v1}/team/expenses/route.ts | 0 .../team/milestones/[id]/status/route.test.ts | 0 .../team/milestones/[id]/status/route.ts | 0 src/app/api/{ => v1}/team/milestones/route.ts | 0 .../team/time-off/[id]/status/route.test.ts | 0 .../team/time-off/[id]/status/route.ts | 0 .../api/{ => v1}/team/time-off/route.test.ts | 8 +- src/app/api/{ => v1}/team/time-off/route.ts | 2 +- .../team/timesheets/[id]/status/route.test.ts | 0 .../team/timesheets/[id]/status/route.ts | 0 src/app/api/{ => v1}/team/timesheets/route.ts | 0 .../features/auth/RegistrationWizard.tsx | 4 +- .../features/auth/business-details.tsx | 4 +- .../auth/register-steps/Step3VerifyEmail.tsx | 4 +- .../dashboard/home/checklist/CompleteKYB.tsx | 4 +- src/hooks/useAuth.ts | 4 +- src/lib/api/auth.ts | 12 +- src/server/services/BLOCKCHAIN_SERVICE.md | 6 +- src/server/services/user.service.ts | 33 +-- src/server/swagger-config.ts | 7 +- test_results.txt | Bin 0 -> 67510 bytes 78 files changed, 318 insertions(+), 251 deletions(-) rename src/app/api/{ => v1}/auth/2fa/disable/route.test.ts (98%) rename src/app/api/{ => v1}/auth/2fa/disable/route.ts (100%) rename src/app/api/{ => v1}/auth/2fa/regenerate-backup-codes/route.test.ts (97%) rename src/app/api/{ => v1}/auth/2fa/regenerate-backup-codes/route.ts (100%) rename src/app/api/{ => v1}/auth/2fa/setup/route.test.ts (98%) rename src/app/api/{ => v1}/auth/2fa/setup/route.ts (100%) rename src/app/api/{ => v1}/auth/2fa/status/route.test.ts (96%) rename src/app/api/{ => v1}/auth/2fa/status/route.ts (100%) rename src/app/api/{ => v1}/auth/2fa/verify-setup/route.test.ts (97%) rename src/app/api/{ => v1}/auth/2fa/verify-setup/route.ts (100%) rename src/app/api/{ => v1}/auth/2fa/verify/route.test.ts (98%) rename src/app/api/{ => v1}/auth/2fa/verify/route.ts (100%) rename src/app/api/{ => v1}/auth/account/route.test.ts (98%) rename src/app/api/{ => v1}/auth/account/route.ts (100%) rename src/app/api/{ => v1}/auth/apple/route.test.ts (98%) rename src/app/api/{ => v1}/auth/apple/route.ts (100%) rename src/app/api/{ => v1}/auth/change-password/route.test.ts (98%) rename src/app/api/{ => v1}/auth/change-password/route.ts (100%) rename src/app/api/{ => v1}/auth/forgot-password/route.ts (100%) rename src/app/api/{ => v1}/auth/google/route.spec.ts (97%) rename src/app/api/{ => v1}/auth/google/route.test.ts (98%) rename src/app/api/{ => v1}/auth/google/route.ts (100%) rename src/app/api/{ => v1}/auth/login/route.test.ts (98%) rename src/app/api/{ => v1}/auth/login/route.ts (100%) rename src/app/api/{ => v1}/auth/logout/route.test.ts (98%) rename src/app/api/{ => v1}/auth/logout/route.ts (96%) rename src/app/api/{ => v1}/auth/refresh/route.test.ts (97%) rename src/app/api/{ => v1}/auth/refresh/route.ts (89%) rename src/app/api/{ => v1}/auth/register/route.test.ts (98%) rename src/app/api/{ => v1}/auth/register/route.ts (100%) rename src/app/api/{ => v1}/auth/resend-otp/route.test.ts (99%) rename src/app/api/{ => v1}/auth/resend-otp/route.ts (100%) rename src/app/api/{ => v1}/auth/reset-password/route.ts (100%) rename src/app/api/{ => v1}/auth/verify-email/route.test.ts (97%) rename src/app/api/{ => v1}/auth/verify-email/route.ts (100%) rename src/app/api/{ => v1}/company/profile/route.ts (99%) rename src/app/api/{ => v1}/dashboard/attention/route.test.ts (98%) rename src/app/api/{ => v1}/dashboard/attention/route.ts (98%) rename src/app/api/{ => v1}/dashboard/onboarding/route.test.ts (99%) rename src/app/api/{ => v1}/dashboard/onboarding/route.ts (98%) rename src/app/api/{ => v1}/dashboard/user-summary/route.test.ts (97%) rename src/app/api/{ => v1}/dashboard/user-summary/route.ts (98%) rename src/app/api/{ => v1}/docs/route.ts (100%) rename src/app/api/{ => v1}/finance/transactions/[id]/route.test.ts (100%) rename src/app/api/{ => v1}/finance/transactions/[id]/route.ts (100%) rename src/app/api/{ => v1}/finance/transactions/route.ts (99%) rename src/app/api/{ => v1}/kyb/status/route.test.ts (98%) rename src/app/api/{ => v1}/kyb/status/route.ts (100%) rename src/app/api/{ => v1}/kyb/submit/route.test.ts (99%) rename src/app/api/{ => v1}/kyb/submit/route.ts (100%) rename src/app/api/{ => v1}/team/employees/[id]/route.ts (100%) rename src/app/api/{ => v1}/team/employees/route.test.ts (98%) rename src/app/api/{ => v1}/team/employees/route.ts (100%) rename src/app/api/{ => v1}/team/expenses/[id]/status/route.test.ts (100%) rename src/app/api/{ => v1}/team/expenses/[id]/status/route.ts (100%) rename src/app/api/{ => v1}/team/expenses/route.ts (100%) rename src/app/api/{ => v1}/team/milestones/[id]/status/route.test.ts (100%) rename src/app/api/{ => v1}/team/milestones/[id]/status/route.ts (100%) rename src/app/api/{ => v1}/team/milestones/route.ts (100%) rename src/app/api/{ => v1}/team/time-off/[id]/status/route.test.ts (100%) rename src/app/api/{ => v1}/team/time-off/[id]/status/route.ts (100%) rename src/app/api/{ => v1}/team/time-off/route.test.ts (97%) rename src/app/api/{ => v1}/team/time-off/route.ts (99%) rename src/app/api/{ => v1}/team/timesheets/[id]/status/route.test.ts (100%) rename src/app/api/{ => v1}/team/timesheets/[id]/status/route.ts (100%) rename src/app/api/{ => v1}/team/timesheets/route.ts (100%) create mode 100644 test_results.txt diff --git a/next.config.ts b/next.config.ts index e19f3e0b..e60f19c2 100644 --- a/next.config.ts +++ b/next.config.ts @@ -25,6 +25,14 @@ const nextConfig: NextConfig = { }, ], }, + async rewrites() { + return [ + { + source: "/api/:path((?!v1/).*)", + destination: "/api/v1/:path", + }, + ]; + }, }; export default nextConfig; diff --git a/src/app/api-docs/page.tsx b/src/app/api-docs/page.tsx index 7a49cc4a..b8262c45 100644 --- a/src/app/api-docs/page.tsx +++ b/src/app/api-docs/page.tsx @@ -22,7 +22,8 @@ const SwaggerUI = dynamic(() => import("swagger-ui-react"), { export default function ApiDocsPage() { return (
- +
); } + diff --git a/src/app/api/auth/2fa/disable/route.test.ts b/src/app/api/v1/auth/2fa/disable/route.test.ts similarity index 98% rename from src/app/api/auth/2fa/disable/route.test.ts rename to src/app/api/v1/auth/2fa/disable/route.test.ts index 3dbd72ac..6edea53c 100644 --- a/src/app/api/auth/2fa/disable/route.test.ts +++ b/src/app/api/v1/auth/2fa/disable/route.test.ts @@ -11,7 +11,7 @@ vi.mock("@/server/services/two-factor.service"); vi.mock("@/server/utils/auth"); vi.mock("@/server/services/email.service"); -describe("POST /api/auth/2fa/disable", () => { +describe("POST /api/v1/auth/2fa/disable", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -137,3 +137,5 @@ describe("POST /api/auth/2fa/disable", () => { expect(data.message).toBe("Invalid password"); }); }); + + diff --git a/src/app/api/auth/2fa/disable/route.ts b/src/app/api/v1/auth/2fa/disable/route.ts similarity index 100% rename from src/app/api/auth/2fa/disable/route.ts rename to src/app/api/v1/auth/2fa/disable/route.ts diff --git a/src/app/api/auth/2fa/regenerate-backup-codes/route.test.ts b/src/app/api/v1/auth/2fa/regenerate-backup-codes/route.test.ts similarity index 97% rename from src/app/api/auth/2fa/regenerate-backup-codes/route.test.ts rename to src/app/api/v1/auth/2fa/regenerate-backup-codes/route.test.ts index d64abe0f..b64508b6 100644 --- a/src/app/api/auth/2fa/regenerate-backup-codes/route.test.ts +++ b/src/app/api/v1/auth/2fa/regenerate-backup-codes/route.test.ts @@ -11,7 +11,7 @@ vi.mock("@/server/services/two-factor.service"); vi.mock("@/server/utils/auth"); vi.mock("@/server/services/email.service"); -describe("POST /api/auth/2fa/regenerate-backup-codes", () => { +describe("POST /api/v1/auth/2fa/regenerate-backup-codes", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -80,3 +80,5 @@ describe("POST /api/auth/2fa/regenerate-backup-codes", () => { expect(response.status).toBe(401); }); }); + + diff --git a/src/app/api/auth/2fa/regenerate-backup-codes/route.ts b/src/app/api/v1/auth/2fa/regenerate-backup-codes/route.ts similarity index 100% rename from src/app/api/auth/2fa/regenerate-backup-codes/route.ts rename to src/app/api/v1/auth/2fa/regenerate-backup-codes/route.ts diff --git a/src/app/api/auth/2fa/setup/route.test.ts b/src/app/api/v1/auth/2fa/setup/route.test.ts similarity index 98% rename from src/app/api/auth/2fa/setup/route.test.ts rename to src/app/api/v1/auth/2fa/setup/route.test.ts index d9f506e5..0a4eee9e 100644 --- a/src/app/api/auth/2fa/setup/route.test.ts +++ b/src/app/api/v1/auth/2fa/setup/route.test.ts @@ -13,7 +13,7 @@ import { vi.mock("@/server/services/two-factor.service"); vi.mock("@/server/utils/auth"); -describe("POST /api/auth/2fa/setup", () => { +describe("POST /api/v1/auth/2fa/setup", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -124,3 +124,5 @@ describe("POST /api/auth/2fa/setup", () => { }); }); + + diff --git a/src/app/api/auth/2fa/setup/route.ts b/src/app/api/v1/auth/2fa/setup/route.ts similarity index 100% rename from src/app/api/auth/2fa/setup/route.ts rename to src/app/api/v1/auth/2fa/setup/route.ts diff --git a/src/app/api/auth/2fa/status/route.test.ts b/src/app/api/v1/auth/2fa/status/route.test.ts similarity index 96% rename from src/app/api/auth/2fa/status/route.test.ts rename to src/app/api/v1/auth/2fa/status/route.test.ts index 196806a9..6814191c 100644 --- a/src/app/api/auth/2fa/status/route.test.ts +++ b/src/app/api/v1/auth/2fa/status/route.test.ts @@ -9,7 +9,7 @@ import { UnauthorizedError } from "@/server/utils/errors"; vi.mock("@/server/services/two-factor.service"); vi.mock("@/server/utils/auth"); -describe("GET /api/auth/2fa/status", () => { +describe("GET /api/v1/auth/2fa/status", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -48,3 +48,5 @@ describe("GET /api/auth/2fa/status", () => { }); }); + + diff --git a/src/app/api/auth/2fa/status/route.ts b/src/app/api/v1/auth/2fa/status/route.ts similarity index 100% rename from src/app/api/auth/2fa/status/route.ts rename to src/app/api/v1/auth/2fa/status/route.ts diff --git a/src/app/api/auth/2fa/verify-setup/route.test.ts b/src/app/api/v1/auth/2fa/verify-setup/route.test.ts similarity index 97% rename from src/app/api/auth/2fa/verify-setup/route.test.ts rename to src/app/api/v1/auth/2fa/verify-setup/route.test.ts index aab7a5d6..77a81b90 100644 --- a/src/app/api/auth/2fa/verify-setup/route.test.ts +++ b/src/app/api/v1/auth/2fa/verify-setup/route.test.ts @@ -11,7 +11,7 @@ vi.mock("@/server/services/two-factor.service"); vi.mock("@/server/utils/auth"); vi.mock("@/server/services/email.service"); -describe("POST /api/auth/2fa/verify-setup", () => { +describe("POST /api/v1/auth/2fa/verify-setup", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -76,3 +76,5 @@ describe("POST /api/auth/2fa/verify-setup", () => { }); }); + + diff --git a/src/app/api/auth/2fa/verify-setup/route.ts b/src/app/api/v1/auth/2fa/verify-setup/route.ts similarity index 100% rename from src/app/api/auth/2fa/verify-setup/route.ts rename to src/app/api/v1/auth/2fa/verify-setup/route.ts diff --git a/src/app/api/auth/2fa/verify/route.test.ts b/src/app/api/v1/auth/2fa/verify/route.test.ts similarity index 98% rename from src/app/api/auth/2fa/verify/route.test.ts rename to src/app/api/v1/auth/2fa/verify/route.test.ts index b0c2f242..c602559b 100644 --- a/src/app/api/auth/2fa/verify/route.test.ts +++ b/src/app/api/v1/auth/2fa/verify/route.test.ts @@ -19,7 +19,7 @@ vi.mock("@/server/db", () => ({ }, })); -describe("POST /api/auth/2fa/verify", () => { +describe("POST /api/v1/auth/2fa/verify", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -128,3 +128,5 @@ describe("POST /api/auth/2fa/verify", () => { }); }); + + diff --git a/src/app/api/auth/2fa/verify/route.ts b/src/app/api/v1/auth/2fa/verify/route.ts similarity index 100% rename from src/app/api/auth/2fa/verify/route.ts rename to src/app/api/v1/auth/2fa/verify/route.ts diff --git a/src/app/api/auth/account/route.test.ts b/src/app/api/v1/auth/account/route.test.ts similarity index 98% rename from src/app/api/auth/account/route.test.ts rename to src/app/api/v1/auth/account/route.test.ts index b039d990..38f45c3a 100644 --- a/src/app/api/auth/account/route.test.ts +++ b/src/app/api/v1/auth/account/route.test.ts @@ -36,7 +36,7 @@ vi.mock("@/server/services/logger.service", () => ({ }, })); -describe("DELETE /api/auth/account", () => { +describe("DELETE /api/v1/auth/account", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -139,4 +139,6 @@ describe("DELETE /api/auth/account", () => { expect(AccountDeletionService.deleteAccount).not.toHaveBeenCalled(); }); -}); \ No newline at end of file +}); + + diff --git a/src/app/api/auth/account/route.ts b/src/app/api/v1/auth/account/route.ts similarity index 100% rename from src/app/api/auth/account/route.ts rename to src/app/api/v1/auth/account/route.ts diff --git a/src/app/api/auth/apple/route.test.ts b/src/app/api/v1/auth/apple/route.test.ts similarity index 98% rename from src/app/api/auth/apple/route.test.ts rename to src/app/api/v1/auth/apple/route.test.ts index 6571fde1..084e84af 100644 --- a/src/app/api/auth/apple/route.test.ts +++ b/src/app/api/v1/auth/apple/route.test.ts @@ -11,7 +11,7 @@ vi.mock("@/server/services/oauth-user-provisioning.service"); vi.mock("@/server/services/jwt.service"); vi.mock("@/server/services/session.service"); -describe("POST /api/auth/apple", () => { +describe("POST /api/v1/auth/apple", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -81,3 +81,5 @@ describe("POST /api/auth/apple", () => { expect(response.status).toBe(500); }); }); + + diff --git a/src/app/api/auth/apple/route.ts b/src/app/api/v1/auth/apple/route.ts similarity index 100% rename from src/app/api/auth/apple/route.ts rename to src/app/api/v1/auth/apple/route.ts diff --git a/src/app/api/auth/change-password/route.test.ts b/src/app/api/v1/auth/change-password/route.test.ts similarity index 98% rename from src/app/api/auth/change-password/route.test.ts rename to src/app/api/v1/auth/change-password/route.test.ts index ace83f5b..7a727bf7 100644 --- a/src/app/api/auth/change-password/route.test.ts +++ b/src/app/api/v1/auth/change-password/route.test.ts @@ -11,7 +11,7 @@ import { vi.mock("@/server/services/auth.service"); vi.mock("@/server/utils/auth"); -describe("POST /api/auth/change-password", () => { +describe("POST /api/v1/auth/change-password", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -182,3 +182,5 @@ describe("POST /api/auth/change-password", () => { expect(data.errors.fieldErrors.newPassword).toBeDefined(); }); }); + + diff --git a/src/app/api/auth/change-password/route.ts b/src/app/api/v1/auth/change-password/route.ts similarity index 100% rename from src/app/api/auth/change-password/route.ts rename to src/app/api/v1/auth/change-password/route.ts diff --git a/src/app/api/auth/forgot-password/route.ts b/src/app/api/v1/auth/forgot-password/route.ts similarity index 100% rename from src/app/api/auth/forgot-password/route.ts rename to src/app/api/v1/auth/forgot-password/route.ts diff --git a/src/app/api/auth/google/route.spec.ts b/src/app/api/v1/auth/google/route.spec.ts similarity index 97% rename from src/app/api/auth/google/route.spec.ts rename to src/app/api/v1/auth/google/route.spec.ts index 08f723ed..3dbf22a6 100644 --- a/src/app/api/auth/google/route.spec.ts +++ b/src/app/api/v1/auth/google/route.spec.ts @@ -16,7 +16,7 @@ vi.mock("@/server/services/oauth-user-provisioning.service"); vi.mock("@/server/services/jwt.service"); vi.mock("@/server/services/session.service"); -describe("POST /api/auth/google", () => { +describe("POST /api/v1/auth/google", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -55,7 +55,7 @@ describe("POST /api/auth/google", () => { id: "session-123", }); - const request = new NextRequest("http://localhost:3000/api/auth/google", { + const request = new NextRequest("http://localhost:3000/api/v1/auth/google", { method: "POST", body: JSON.stringify({ idToken: "valid_google_token" }), }); @@ -111,7 +111,7 @@ describe("POST /api/auth/google", () => { id: "session-456", }); - const request = new NextRequest("http://localhost:3000/api/auth/google", { + const request = new NextRequest("http://localhost:3000/api/v1/auth/google", { method: "POST", body: JSON.stringify({ idToken: "valid_google_token" }), }); @@ -125,7 +125,7 @@ describe("POST /api/auth/google", () => { }); it("should return 400 for missing idToken", async () => { - const request = new NextRequest("http://localhost:3000/api/auth/google", { + const request = new NextRequest("http://localhost:3000/api/v1/auth/google", { method: "POST", body: JSON.stringify({}), }); @@ -139,7 +139,7 @@ describe("POST /api/auth/google", () => { }); it("should return 400 for invalid idToken format", async () => { - const request = new NextRequest("http://localhost:3000/api/auth/google", { + const request = new NextRequest("http://localhost:3000/api/v1/auth/google", { method: "POST", body: JSON.stringify({ idToken: "" }), }); @@ -156,7 +156,7 @@ describe("POST /api/auth/google", () => { .fn() .mockRejectedValue(new InvalidTokenError("Invalid Google token")); - const request = new NextRequest("http://localhost:3000/api/auth/google", { + const request = new NextRequest("http://localhost:3000/api/v1/auth/google", { method: "POST", body: JSON.stringify({ idToken: "invalid_token" }), }); @@ -174,7 +174,7 @@ describe("POST /api/auth/google", () => { .fn() .mockRejectedValue(new TokenExpiredError("Google token has expired")); - const request = new NextRequest("http://localhost:3000/api/auth/google", { + const request = new NextRequest("http://localhost:3000/api/v1/auth/google", { method: "POST", body: JSON.stringify({ idToken: "expired_token" }), }); @@ -194,7 +194,7 @@ describe("POST /api/auth/google", () => { new AudienceMismatchError("Token audience mismatch") ); - const request = new NextRequest("http://localhost:3000/api/auth/google", { + const request = new NextRequest("http://localhost:3000/api/v1/auth/google", { method: "POST", body: JSON.stringify({ idToken: "wrong_audience_token" }), }); @@ -211,7 +211,7 @@ describe("POST /api/auth/google", () => { .fn() .mockRejectedValue(new Error("Database connection failed")); - const request = new NextRequest("http://localhost:3000/api/auth/google", { + const request = new NextRequest("http://localhost:3000/api/v1/auth/google", { method: "POST", body: JSON.stringify({ idToken: "valid_token" }), }); @@ -258,7 +258,7 @@ describe("POST /api/auth/google", () => { id: "session-789", }); - const request = new NextRequest("http://localhost:3000/api/auth/google", { + const request = new NextRequest("http://localhost:3000/api/v1/auth/google", { method: "POST", body: JSON.stringify({ idToken: "valid_google_token" }), }); @@ -271,3 +271,5 @@ describe("POST /api/auth/google", () => { expect(setCookieHeader).toContain("Path=/"); }); }); + + diff --git a/src/app/api/auth/google/route.test.ts b/src/app/api/v1/auth/google/route.test.ts similarity index 98% rename from src/app/api/auth/google/route.test.ts rename to src/app/api/v1/auth/google/route.test.ts index 184bbb7d..4ff73e30 100644 --- a/src/app/api/auth/google/route.test.ts +++ b/src/app/api/v1/auth/google/route.test.ts @@ -11,7 +11,7 @@ vi.mock("@/server/services/oauth-user-provisioning.service"); vi.mock("@/server/services/jwt.service"); vi.mock("@/server/services/session.service"); -describe("POST /api/auth/google", () => { +describe("POST /api/v1/auth/google", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -71,3 +71,5 @@ describe("POST /api/auth/google", () => { expect(response.status).toBe(500); }); }); + + diff --git a/src/app/api/auth/google/route.ts b/src/app/api/v1/auth/google/route.ts similarity index 100% rename from src/app/api/auth/google/route.ts rename to src/app/api/v1/auth/google/route.ts diff --git a/src/app/api/auth/login/route.test.ts b/src/app/api/v1/auth/login/route.test.ts similarity index 98% rename from src/app/api/auth/login/route.test.ts rename to src/app/api/v1/auth/login/route.test.ts index be3674b1..32d7a11a 100644 --- a/src/app/api/auth/login/route.test.ts +++ b/src/app/api/v1/auth/login/route.test.ts @@ -16,7 +16,7 @@ vi.mock("@/server/utils/auth", () => ({ }, })); -describe("POST /api/auth/login", () => { +describe("POST /api/v1/auth/login", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -128,3 +128,5 @@ describe("POST /api/auth/login", () => { expect(data.message).toBe("Internal server error"); }); }); + + diff --git a/src/app/api/auth/login/route.ts b/src/app/api/v1/auth/login/route.ts similarity index 100% rename from src/app/api/auth/login/route.ts rename to src/app/api/v1/auth/login/route.ts diff --git a/src/app/api/auth/logout/route.test.ts b/src/app/api/v1/auth/logout/route.test.ts similarity index 98% rename from src/app/api/auth/logout/route.test.ts rename to src/app/api/v1/auth/logout/route.test.ts index b4b76af0..587d4e1c 100644 --- a/src/app/api/auth/logout/route.test.ts +++ b/src/app/api/v1/auth/logout/route.test.ts @@ -12,7 +12,7 @@ vi.mock("@/server/services/logout.service"); vi.mock("@/server/utils/auth"); vi.mock("@/server/services/logger.service"); -describe("POST /api/auth/logout", () => { +describe("POST /api/v1/auth/logout", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -78,3 +78,5 @@ describe("POST /api/auth/logout", () => { expect(response.status).toBe(500); }); }); + + diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/v1/auth/logout/route.ts similarity index 96% rename from src/app/api/auth/logout/route.ts rename to src/app/api/v1/auth/logout/route.ts index 76fd40e0..afec78b5 100644 --- a/src/app/api/auth/logout/route.ts +++ b/src/app/api/v1/auth/logout/route.ts @@ -1,74 +1,75 @@ -import { NextRequest } from "next/server"; -import { cookies } from "next/headers"; -import { LogoutService } from "@/server/services/logout.service"; -import { ApiResponse } from "@/server/utils/api-response"; -import { AuthUtils } from "@/server/utils/auth"; -import { AppError } from "@/server/utils/errors"; -import { logoutSchema } from "@/server/validations/auth-logout.schema"; -import { Logger } from "@/server/services/logger.service"; - -/** - * @swagger - * /auth/logout: - * post: - * summary: User logout - * description: Clear security cookies and invalidate current session - * tags: [Auth] - * security: - * - bearerAuth: [] - * requestBody: - * content: - * application/json: - * schema: - * type: object - * properties: - * refreshToken: - * type: string - * responses: - * 200: - * description: Logged out successfully - * 500: - * description: Internal server error - */ -export async function POST(request: NextRequest) { - try { - const cookieStore = await cookies(); - const cookieToken = cookieStore.get("refreshToken")?.value; - - let refreshToken = cookieToken; - - if (!refreshToken) { - try { - const body = await request.json(); - const validation = logoutSchema.safeParse(body); - if (validation.success && validation.data.refreshToken) { - refreshToken = validation.data.refreshToken; - } - } catch { - } - } - - const ipAddress = AuthUtils.getClientIp(request); - const userAgent = AuthUtils.getUserAgent(request); - - Logger.info("Logout attempt initiated", { ip: ipAddress }); - - await LogoutService.logout(refreshToken, { userAgent, ipAddress }); - - const response = ApiResponse.success({ - message: "Logged out successfully", - }); - response.cookies.delete("refreshToken"); - - return response; - } catch (error) { - if (error instanceof AppError) { - if (error.statusCode === 500) { - Logger.error("Logout internal error", { message: error.message }); - return ApiResponse.error(error.message, 500); - } - } - Logger.error("Unhandled logout error", { error }); - return ApiResponse.error("Internal server error", 500); - } -} +import { NextRequest } from "next/server"; +import { cookies } from "next/headers"; +import { LogoutService } from "@/server/services/logout.service"; +import { ApiResponse } from "@/server/utils/api-response"; +import { AuthUtils } from "@/server/utils/auth"; +import { AppError } from "@/server/utils/errors"; +import { logoutSchema } from "@/server/validations/auth-logout.schema"; +import { Logger } from "@/server/services/logger.service"; + +/** + * @swagger + * /auth/logout: + * post: + * summary: User logout + * description: Clear security cookies and invalidate current session + * tags: [Auth] + * security: + * - bearerAuth: [] + * requestBody: + * content: + * application/json: + * schema: + * type: object + * properties: + * refreshToken: + * type: string + * responses: + * 200: + * description: Logged out successfully + * 500: + * description: Internal server error + */ +export async function POST(request: NextRequest) { + try { + const cookieStore = await cookies(); + const cookieToken = cookieStore.get("refreshToken")?.value; + + let refreshToken = cookieToken; + + if (!refreshToken) { + try { + const body = await request.json(); + const validation = logoutSchema.safeParse(body); + if (validation.success && validation.data.refreshToken) { + refreshToken = validation.data.refreshToken; + } + } catch { + + } + } + + const ipAddress = AuthUtils.getClientIp(request); + const userAgent = AuthUtils.getUserAgent(request); + + Logger.info("Logout attempt initiated", { ip: ipAddress }); + + await LogoutService.logout(refreshToken, { userAgent, ipAddress }); + + const response = ApiResponse.success({ + message: "Logged out successfully", + }); + response.cookies.delete("refreshToken"); + + return response; + } catch (error) { + if (error instanceof AppError) { + if (error.statusCode === 500) { + Logger.error("Logout internal error", { message: error.message }); + return ApiResponse.error(error.message, 500); + } + } + Logger.error("Unhandled logout error", { error }); + return ApiResponse.error("Internal server error", 500); + } +} diff --git a/src/app/api/auth/refresh/route.test.ts b/src/app/api/v1/auth/refresh/route.test.ts similarity index 97% rename from src/app/api/auth/refresh/route.test.ts rename to src/app/api/v1/auth/refresh/route.test.ts index 91afa825..19e9f6f4 100644 --- a/src/app/api/auth/refresh/route.test.ts +++ b/src/app/api/v1/auth/refresh/route.test.ts @@ -12,7 +12,7 @@ vi.mock("next/headers", () => ({ vi.mock("@/server/services/token-refresh.service"); vi.mock("@/server/utils/auth"); -describe("POST /api/auth/refresh", () => { +describe("POST /api/v1/auth/refresh", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -70,3 +70,5 @@ describe("POST /api/auth/refresh", () => { expect(data.message).toBe("Invalid token"); }); }); + + diff --git a/src/app/api/auth/refresh/route.ts b/src/app/api/v1/auth/refresh/route.ts similarity index 89% rename from src/app/api/auth/refresh/route.ts rename to src/app/api/v1/auth/refresh/route.ts index 838bca29..8304a477 100644 --- a/src/app/api/auth/refresh/route.ts +++ b/src/app/api/v1/auth/refresh/route.ts @@ -1,90 +1,107 @@ -import { NextRequest } from "next/server"; -import { cookies } from "next/headers"; -import { TokenRefreshService } from "@/server/services/token-refresh.service"; -import { ApiResponse } from "@/server/utils/api-response"; -import { AuthUtils } from "@/server/utils/auth"; -import { AppError } from "@/server/utils/errors"; -import { refreshSchema } from "@/server/validations/auth-refresh.schema"; -import { - InvalidTokenFormatError, - InvalidTokenSignatureError, - ExpiredTokenError, - TokenSessionMismatchError, - SessionNotFoundError, -} from "@/server/utils/auth-errors"; - -/** - * @swagger - * /auth/refresh: - * post: - * summary: Refresh access token - * description: Rotate refresh token and get a new access token - * tags: [Auth] - * requestBody: - * content: - * application/json: - * schema: - * type: object - * properties: - * refreshToken: - * type: string - * responses: - * 200: - * description: Token refreshed successfully - * 400: - * description: Refresh token is required - * 401: - * description: Invalid or expired token - */ -export async function POST(request: NextRequest) { - try { - const cookieStore = await cookies(); - const cookieToken = cookieStore.get("refreshToken")?.value; - - let refreshToken = cookieToken; - if (!refreshToken) { - try { - const body = await request.json(); - const validation = refreshSchema.safeParse(body); - if (validation.success) { - refreshToken = validation.data.refreshToken; - } - } catch { - } - } - - if (!refreshToken) { - return ApiResponse.error("Refresh token is required", 400); - } - - const ipAddress = AuthUtils.getClientIp(request); - const userAgent = AuthUtils.getUserAgent(request); - const result = await TokenRefreshService.refresh( - refreshToken, - userAgent, - ipAddress, - ); - const response = ApiResponse.success( - result, - "Token refreshed successfully", - ); - - const cookieOptions = { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict" as const, - path: "/", - maxAge: 30 * 24 * 60 * 60, - }; - response.cookies.set("refreshToken", result.refreshToken, cookieOptions); - - return response; - } catch (error) { - if (error instanceof AppError) { - return ApiResponse.error(error.message, error.statusCode, error.errors); - } - - console.error("Refresh route error:", error); - return ApiResponse.error("Internal server error", 500); - } -} +import { NextRequest } from "next/server"; +import { cookies } from "next/headers"; +import { TokenRefreshService } from "@/server/services/token-refresh.service"; +import { ApiResponse } from "@/server/utils/api-response"; +import { AuthUtils } from "@/server/utils/auth"; +import { AppError } from "@/server/utils/errors"; +import { refreshSchema } from "@/server/validations/auth-refresh.schema"; +import { + InvalidTokenFormatError, + InvalidTokenSignatureError, + ExpiredTokenError, + TokenSessionMismatchError, + SessionNotFoundError, +} from "@/server/utils/auth-errors"; + +/** + * @swagger + * /auth/refresh: + * post: + * summary: Refresh access token + * description: Rotate refresh token and get a new access token + * tags: [Auth] + * requestBody: + * content: + * application/json: + * schema: + * type: object + * properties: + * refreshToken: + * type: string + * responses: + * 200: + * description: Token refreshed successfully + * 400: + * description: Refresh token is required + * 401: + * description: Invalid or expired token + */ +export async function POST(request: NextRequest) { + try { + const cookieStore = await cookies(); + const cookieToken = cookieStore.get("refreshToken")?.value; + + let refreshToken = cookieToken; + + if (!refreshToken) { + try { + const body = await request.json(); + const validation = refreshSchema.safeParse(body); + if (validation.success) { + refreshToken = validation.data.refreshToken; + } + } catch { + + } + } + + if (!refreshToken) { + + return ApiResponse.error("Refresh token is required", 400); + } + + const ipAddress = AuthUtils.getClientIp(request); + const userAgent = AuthUtils.getUserAgent(request); + + const result = await TokenRefreshService.refresh( + refreshToken, + userAgent, + ipAddress, + ); + + const response = ApiResponse.success( + result, + "Token refreshed successfully", + ); + + const cookieOptions = { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict" as const, + path: "/", + + + + + + + + + maxAge: 30 * 24 * 60 * 60, + }; + + + response.cookies.set("refreshToken", result.refreshToken, cookieOptions); + + return response; + } catch (error) { + if (error instanceof AppError) { + return ApiResponse.error(error.message, error.statusCode, error.errors); + } + + + + console.error("Refresh route error:", error); + return ApiResponse.error("Internal server error", 500); + } +} diff --git a/src/app/api/auth/register/route.test.ts b/src/app/api/v1/auth/register/route.test.ts similarity index 98% rename from src/app/api/auth/register/route.test.ts rename to src/app/api/v1/auth/register/route.test.ts index 606992cc..25d21f2f 100644 --- a/src/app/api/auth/register/route.test.ts +++ b/src/app/api/v1/auth/register/route.test.ts @@ -6,7 +6,7 @@ import { ConflictError, AppError } from "@/server/utils/errors"; vi.mock("@/server/services/auth.service"); -describe("POST /api/auth/register", () => { +describe("POST /api/v1/auth/register", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -106,3 +106,5 @@ describe("POST /api/auth/register", () => { expect(data.message).toBe("Internal server error"); }); }); + + diff --git a/src/app/api/auth/register/route.ts b/src/app/api/v1/auth/register/route.ts similarity index 100% rename from src/app/api/auth/register/route.ts rename to src/app/api/v1/auth/register/route.ts diff --git a/src/app/api/auth/resend-otp/route.test.ts b/src/app/api/v1/auth/resend-otp/route.test.ts similarity index 99% rename from src/app/api/auth/resend-otp/route.test.ts rename to src/app/api/v1/auth/resend-otp/route.test.ts index b6287941..37e9fd7f 100644 --- a/src/app/api/auth/resend-otp/route.test.ts +++ b/src/app/api/v1/auth/resend-otp/route.test.ts @@ -10,7 +10,7 @@ import { vi.mock("@/server/services/otp-resend.service"); -describe("POST /api/auth/resend-otp", () => { +describe("POST /api/v1/auth/resend-otp", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -207,3 +207,5 @@ describe("POST /api/auth/resend-otp", () => { consoleSpy.mockRestore(); }); }); + + diff --git a/src/app/api/auth/resend-otp/route.ts b/src/app/api/v1/auth/resend-otp/route.ts similarity index 100% rename from src/app/api/auth/resend-otp/route.ts rename to src/app/api/v1/auth/resend-otp/route.ts diff --git a/src/app/api/auth/reset-password/route.ts b/src/app/api/v1/auth/reset-password/route.ts similarity index 100% rename from src/app/api/auth/reset-password/route.ts rename to src/app/api/v1/auth/reset-password/route.ts diff --git a/src/app/api/auth/verify-email/route.test.ts b/src/app/api/v1/auth/verify-email/route.test.ts similarity index 97% rename from src/app/api/auth/verify-email/route.test.ts rename to src/app/api/v1/auth/verify-email/route.test.ts index c12e3944..57370771 100644 --- a/src/app/api/auth/verify-email/route.test.ts +++ b/src/app/api/v1/auth/verify-email/route.test.ts @@ -6,7 +6,7 @@ import { AppError } from "@/server/utils/errors"; vi.mock("@/server/services/email-verification.service"); -describe("POST /api/auth/verify-email", () => { +describe("POST /api/v1/auth/verify-email", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -88,3 +88,5 @@ describe("POST /api/auth/verify-email", () => { expect(data.message).toBe("Invalid or expired OTP"); }); }); + + diff --git a/src/app/api/auth/verify-email/route.ts b/src/app/api/v1/auth/verify-email/route.ts similarity index 100% rename from src/app/api/auth/verify-email/route.ts rename to src/app/api/v1/auth/verify-email/route.ts diff --git a/src/app/api/company/profile/route.ts b/src/app/api/v1/company/profile/route.ts similarity index 99% rename from src/app/api/company/profile/route.ts rename to src/app/api/v1/company/profile/route.ts index 2fc8f254..075aca6d 100644 --- a/src/app/api/company/profile/route.ts +++ b/src/app/api/v1/company/profile/route.ts @@ -6,7 +6,7 @@ import { CompanyService } from "@/server/services/company.service"; /** * @swagger - * /api/company/profile: + * /company/profile: * get: * summary: Get company profile * description: Return the authenticated user's organization legal and contact details diff --git a/src/app/api/dashboard/attention/route.test.ts b/src/app/api/v1/dashboard/attention/route.test.ts similarity index 98% rename from src/app/api/dashboard/attention/route.test.ts rename to src/app/api/v1/dashboard/attention/route.test.ts index 97aca76e..8f849e2e 100644 --- a/src/app/api/dashboard/attention/route.test.ts +++ b/src/app/api/v1/dashboard/attention/route.test.ts @@ -8,7 +8,7 @@ import { UnauthorizedError, ForbiddenError } from "@/server/utils/errors"; vi.mock("@/server/services/attention.service"); vi.mock("@/server/utils/auth"); -describe("GET /api/dashboard/attention", () => { +describe("GET /api/v1/dashboard/attention", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -122,3 +122,5 @@ describe("GET /api/dashboard/attention", () => { expect(data.message).toBe("Internal server error"); }); }); + + diff --git a/src/app/api/dashboard/attention/route.ts b/src/app/api/v1/dashboard/attention/route.ts similarity index 98% rename from src/app/api/dashboard/attention/route.ts rename to src/app/api/v1/dashboard/attention/route.ts index 29593273..010a56d8 100644 --- a/src/app/api/dashboard/attention/route.ts +++ b/src/app/api/v1/dashboard/attention/route.ts @@ -6,7 +6,7 @@ import { AttentionService } from "@/server/services/attention.service"; /** * @swagger - * /api/dashboard/attention: + * /dashboard/attention: * get: * summary: Get required attention items * description: Retrieve counts of items needing immediate action across contracts, milestones, invoices, timesheets, expenses, and time-off requests diff --git a/src/app/api/dashboard/onboarding/route.test.ts b/src/app/api/v1/dashboard/onboarding/route.test.ts similarity index 99% rename from src/app/api/dashboard/onboarding/route.test.ts rename to src/app/api/v1/dashboard/onboarding/route.test.ts index 22455490..320dfcec 100644 --- a/src/app/api/dashboard/onboarding/route.test.ts +++ b/src/app/api/v1/dashboard/onboarding/route.test.ts @@ -39,7 +39,7 @@ const buildMockStatus = (overrides: { }; }; -describe("GET /api/dashboard/onboarding", () => { +describe("GET /api/v1/dashboard/onboarding", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -183,3 +183,5 @@ describe("GET /api/dashboard/onboarding", () => { expect(data.message).toBe("Internal server error"); }); }); + + diff --git a/src/app/api/dashboard/onboarding/route.ts b/src/app/api/v1/dashboard/onboarding/route.ts similarity index 98% rename from src/app/api/dashboard/onboarding/route.ts rename to src/app/api/v1/dashboard/onboarding/route.ts index 788a57a4..114beace 100644 --- a/src/app/api/dashboard/onboarding/route.ts +++ b/src/app/api/v1/dashboard/onboarding/route.ts @@ -6,7 +6,7 @@ import { OnboardingService } from "@/server/services/onboarding.service"; /** * @swagger - * /api/dashboard/onboarding: + * /dashboard/onboarding: * get: * summary: Get onboarding status * description: Retrieve the organization's onboarding progress across email verification, company profile, KYB verification, and wallet funding diff --git a/src/app/api/dashboard/user-summary/route.test.ts b/src/app/api/v1/dashboard/user-summary/route.test.ts similarity index 97% rename from src/app/api/dashboard/user-summary/route.test.ts rename to src/app/api/v1/dashboard/user-summary/route.test.ts index bf16771a..325b92f5 100644 --- a/src/app/api/dashboard/user-summary/route.test.ts +++ b/src/app/api/v1/dashboard/user-summary/route.test.ts @@ -8,7 +8,7 @@ import { UnauthorizedError } from "@/server/utils/errors"; vi.mock("@/server/services/dashboard.service"); vi.mock("@/server/utils/auth"); -describe("GET /api/dashboard/user-summary", () => { +describe("GET /api/v1/dashboard/user-summary", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -70,3 +70,5 @@ describe("GET /api/dashboard/user-summary", () => { expect(data.message).toBe("User not found"); }); }); + + diff --git a/src/app/api/dashboard/user-summary/route.ts b/src/app/api/v1/dashboard/user-summary/route.ts similarity index 98% rename from src/app/api/dashboard/user-summary/route.ts rename to src/app/api/v1/dashboard/user-summary/route.ts index fb46a56e..b432b76b 100644 --- a/src/app/api/dashboard/user-summary/route.ts +++ b/src/app/api/v1/dashboard/user-summary/route.ts @@ -6,7 +6,7 @@ import { DashboardService } from "@/server/services/dashboard.service"; /** * @swagger - * /api/dashboard/user-summary: + * /dashboard/user-summary: * get: * summary: Get user summary * description: Retrieve identity details for the authenticated user to populate dashboard header greeting and profile previews diff --git a/src/app/api/docs/route.ts b/src/app/api/v1/docs/route.ts similarity index 100% rename from src/app/api/docs/route.ts rename to src/app/api/v1/docs/route.ts diff --git a/src/app/api/finance/transactions/[id]/route.test.ts b/src/app/api/v1/finance/transactions/[id]/route.test.ts similarity index 100% rename from src/app/api/finance/transactions/[id]/route.test.ts rename to src/app/api/v1/finance/transactions/[id]/route.test.ts diff --git a/src/app/api/finance/transactions/[id]/route.ts b/src/app/api/v1/finance/transactions/[id]/route.ts similarity index 100% rename from src/app/api/finance/transactions/[id]/route.ts rename to src/app/api/v1/finance/transactions/[id]/route.ts diff --git a/src/app/api/finance/transactions/route.ts b/src/app/api/v1/finance/transactions/route.ts similarity index 99% rename from src/app/api/finance/transactions/route.ts rename to src/app/api/v1/finance/transactions/route.ts index a5065184..dd539dbc 100644 --- a/src/app/api/finance/transactions/route.ts +++ b/src/app/api/v1/finance/transactions/route.ts @@ -7,7 +7,7 @@ import { ListTransactionsSchema } from "@/server/validations/finance.schema"; /** * @swagger - * /api/finance/transactions: + * /finance/transactions: * get: * summary: List finance transactions * description: Retrieve the paginated history of all wallet movements for the organization, with support for asset filtering. diff --git a/src/app/api/kyb/status/route.test.ts b/src/app/api/v1/kyb/status/route.test.ts similarity index 98% rename from src/app/api/kyb/status/route.test.ts rename to src/app/api/v1/kyb/status/route.test.ts index 260944f5..f714fc67 100644 --- a/src/app/api/kyb/status/route.test.ts +++ b/src/app/api/v1/kyb/status/route.test.ts @@ -8,7 +8,7 @@ import { UnauthorizedError } from "@/server/utils/errors"; vi.mock("@/server/services/kyb.service"); vi.mock("@/server/utils/auth"); -describe("GET /api/kyb/status", () => { +describe("GET /api/v1/kyb/status", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -118,3 +118,5 @@ describe("GET /api/kyb/status", () => { expect(data.message).toBe("Internal server error"); }); }); + + diff --git a/src/app/api/kyb/status/route.ts b/src/app/api/v1/kyb/status/route.ts similarity index 100% rename from src/app/api/kyb/status/route.ts rename to src/app/api/v1/kyb/status/route.ts diff --git a/src/app/api/kyb/submit/route.test.ts b/src/app/api/v1/kyb/submit/route.test.ts similarity index 99% rename from src/app/api/kyb/submit/route.test.ts rename to src/app/api/v1/kyb/submit/route.test.ts index b2e1ff41..413b027a 100644 --- a/src/app/api/kyb/submit/route.test.ts +++ b/src/app/api/v1/kyb/submit/route.test.ts @@ -8,7 +8,7 @@ import { ConflictError, UnauthorizedError } from "@/server/utils/errors"; vi.mock("@/server/services/kyb.service"); vi.mock("@/server/utils/auth"); -describe("POST /api/kyb/submit", () => { +describe("POST /api/v1/kyb/submit", () => { beforeEach(() => { vi.clearAllMocks(); @@ -244,3 +244,5 @@ describe("POST /api/kyb/submit", () => { expect(data.message).toBe("Validation failed"); }); }); + + diff --git a/src/app/api/kyb/submit/route.ts b/src/app/api/v1/kyb/submit/route.ts similarity index 100% rename from src/app/api/kyb/submit/route.ts rename to src/app/api/v1/kyb/submit/route.ts diff --git a/src/app/api/team/employees/[id]/route.ts b/src/app/api/v1/team/employees/[id]/route.ts similarity index 100% rename from src/app/api/team/employees/[id]/route.ts rename to src/app/api/v1/team/employees/[id]/route.ts diff --git a/src/app/api/team/employees/route.test.ts b/src/app/api/v1/team/employees/route.test.ts similarity index 98% rename from src/app/api/team/employees/route.test.ts rename to src/app/api/v1/team/employees/route.test.ts index 670b5ee0..55057400 100644 --- a/src/app/api/team/employees/route.test.ts +++ b/src/app/api/v1/team/employees/route.test.ts @@ -8,7 +8,7 @@ import { UnauthorizedError, ForbiddenError } from "@/server/utils/errors"; vi.mock("@/server/services/employee.service"); vi.mock("@/server/utils/auth"); -describe("GET /api/team/employees", () => { +describe("GET /api/v1/team/employees", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -16,7 +16,7 @@ describe("GET /api/team/employees", () => { const createMockRequest = ( query: Record = {}, ): NextRequest => { - const url = new URL("http://localhost:3000/api/team/employees"); + const url = new URL("http://localhost:3000/api/v1/team/employees"); Object.entries(query).forEach(([key, value]) => { url.searchParams.set(key, value); }); @@ -206,3 +206,5 @@ describe("GET /api/team/employees", () => { expect(data.message).toBe("Internal server error"); }); }); + + diff --git a/src/app/api/team/employees/route.ts b/src/app/api/v1/team/employees/route.ts similarity index 100% rename from src/app/api/team/employees/route.ts rename to src/app/api/v1/team/employees/route.ts diff --git a/src/app/api/team/expenses/[id]/status/route.test.ts b/src/app/api/v1/team/expenses/[id]/status/route.test.ts similarity index 100% rename from src/app/api/team/expenses/[id]/status/route.test.ts rename to src/app/api/v1/team/expenses/[id]/status/route.test.ts diff --git a/src/app/api/team/expenses/[id]/status/route.ts b/src/app/api/v1/team/expenses/[id]/status/route.ts similarity index 100% rename from src/app/api/team/expenses/[id]/status/route.ts rename to src/app/api/v1/team/expenses/[id]/status/route.ts diff --git a/src/app/api/team/expenses/route.ts b/src/app/api/v1/team/expenses/route.ts similarity index 100% rename from src/app/api/team/expenses/route.ts rename to src/app/api/v1/team/expenses/route.ts diff --git a/src/app/api/team/milestones/[id]/status/route.test.ts b/src/app/api/v1/team/milestones/[id]/status/route.test.ts similarity index 100% rename from src/app/api/team/milestones/[id]/status/route.test.ts rename to src/app/api/v1/team/milestones/[id]/status/route.test.ts diff --git a/src/app/api/team/milestones/[id]/status/route.ts b/src/app/api/v1/team/milestones/[id]/status/route.ts similarity index 100% rename from src/app/api/team/milestones/[id]/status/route.ts rename to src/app/api/v1/team/milestones/[id]/status/route.ts diff --git a/src/app/api/team/milestones/route.ts b/src/app/api/v1/team/milestones/route.ts similarity index 100% rename from src/app/api/team/milestones/route.ts rename to src/app/api/v1/team/milestones/route.ts diff --git a/src/app/api/team/time-off/[id]/status/route.test.ts b/src/app/api/v1/team/time-off/[id]/status/route.test.ts similarity index 100% rename from src/app/api/team/time-off/[id]/status/route.test.ts rename to src/app/api/v1/team/time-off/[id]/status/route.test.ts diff --git a/src/app/api/team/time-off/[id]/status/route.ts b/src/app/api/v1/team/time-off/[id]/status/route.ts similarity index 100% rename from src/app/api/team/time-off/[id]/status/route.ts rename to src/app/api/v1/team/time-off/[id]/status/route.ts diff --git a/src/app/api/team/time-off/route.test.ts b/src/app/api/v1/team/time-off/route.test.ts similarity index 97% rename from src/app/api/team/time-off/route.test.ts rename to src/app/api/v1/team/time-off/route.test.ts index f0fcb7c6..17ca5475 100644 --- a/src/app/api/team/time-off/route.test.ts +++ b/src/app/api/v1/team/time-off/route.test.ts @@ -8,13 +8,13 @@ import { UnauthorizedError, ForbiddenError } from "@/server/utils/errors"; vi.mock("@/server/services/time-off.service"); vi.mock("@/server/utils/auth"); -describe("GET /api/team/time-off", () => { +describe("GET /api/v1/team/time-off", () => { beforeEach(() => { vi.clearAllMocks(); }); const createMockRequest = (): NextRequest => { - return new NextRequest(new URL("http://localhost:3000/api/team/time-off")); + return new NextRequest(new URL("http://localhost:3000/api/v1/team/time-off")); }; const mockTimeOffResponse = [ @@ -89,4 +89,6 @@ describe("GET /api/team/time-off", () => { expect(response.status).toBe(500); }); -}); \ No newline at end of file +}); + + diff --git a/src/app/api/team/time-off/route.ts b/src/app/api/v1/team/time-off/route.ts similarity index 99% rename from src/app/api/team/time-off/route.ts rename to src/app/api/v1/team/time-off/route.ts index 94fb23d4..91034935 100644 --- a/src/app/api/team/time-off/route.ts +++ b/src/app/api/v1/team/time-off/route.ts @@ -36,4 +36,4 @@ export async function GET(req: NextRequest) { console.error("[Team Time-Off Error]", error); return ApiResponse.error("Internal server error", 500); } -} \ No newline at end of file +} diff --git a/src/app/api/team/timesheets/[id]/status/route.test.ts b/src/app/api/v1/team/timesheets/[id]/status/route.test.ts similarity index 100% rename from src/app/api/team/timesheets/[id]/status/route.test.ts rename to src/app/api/v1/team/timesheets/[id]/status/route.test.ts diff --git a/src/app/api/team/timesheets/[id]/status/route.ts b/src/app/api/v1/team/timesheets/[id]/status/route.ts similarity index 100% rename from src/app/api/team/timesheets/[id]/status/route.ts rename to src/app/api/v1/team/timesheets/[id]/status/route.ts diff --git a/src/app/api/team/timesheets/route.ts b/src/app/api/v1/team/timesheets/route.ts similarity index 100% rename from src/app/api/team/timesheets/route.ts rename to src/app/api/v1/team/timesheets/route.ts diff --git a/src/components/features/auth/RegistrationWizard.tsx b/src/components/features/auth/RegistrationWizard.tsx index dc3e8e5e..5765a593 100644 --- a/src/components/features/auth/RegistrationWizard.tsx +++ b/src/components/features/auth/RegistrationWizard.tsx @@ -60,7 +60,7 @@ export default function RegistrationWizard() { const handleStep4Submit = async () => { setIsLoading(true); try { - const response = await fetch("/api/auth/register", { + const response = await fetch("/api/v1/auth/register", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(formData), @@ -160,3 +160,5 @@ export default function RegistrationWizard() { ); } + + diff --git a/src/components/features/auth/business-details.tsx b/src/components/features/auth/business-details.tsx index 02076870..9551bd42 100644 --- a/src/components/features/auth/business-details.tsx +++ b/src/components/features/auth/business-details.tsx @@ -3,7 +3,7 @@ import React, { useState, useEffect } from "react"; import { ChevronDown } from "lucide-react"; import ModalWelcomeOnboard from "@/components/shared/modal-welcome-onboard"; import Stepper from "@/components/features/auth/Stepper"; -import { AuthService } from "@/lib/api/auth"; +import { AuthService } from "@/lib/api/v1/auth"; interface FormData { companyName: string; @@ -385,3 +385,5 @@ const BusinessRegistrationForm: React.FC = () => { }; export default BusinessRegistrationForm; + + diff --git a/src/components/features/auth/register-steps/Step3VerifyEmail.tsx b/src/components/features/auth/register-steps/Step3VerifyEmail.tsx index f59f1f3f..f9821859 100644 --- a/src/components/features/auth/register-steps/Step3VerifyEmail.tsx +++ b/src/components/features/auth/register-steps/Step3VerifyEmail.tsx @@ -16,7 +16,7 @@ export default function Step3VerifyEmail({ }: Step3Props) { const handleVerify = async (otp: string) => { try { - const response = await fetch("/api/auth/verify-email", { + const response = await fetch("/api/v1/auth/verify-email", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, otp }), @@ -53,3 +53,5 @@ export default function Step3VerifyEmail({ ); } + + diff --git a/src/components/features/dashboard/home/checklist/CompleteKYB.tsx b/src/components/features/dashboard/home/checklist/CompleteKYB.tsx index beb2ac3b..ea29644a 100644 --- a/src/components/features/dashboard/home/checklist/CompleteKYB.tsx +++ b/src/components/features/dashboard/home/checklist/CompleteKYB.tsx @@ -76,7 +76,7 @@ export default function CompleteKYBPage() { submitData.append("formC02C07", formData.formC02C07); } - const response = await fetch("/api/kyb/submit", { + const response = await fetch("/api/v1/kyb/submit", { method: "POST", body: submitData, }); @@ -217,3 +217,5 @@ export default function CompleteKYBPage() { ); } + + diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts index d9b5a2df..e144958f 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAuth.ts @@ -1,6 +1,6 @@ import { useState } from "react"; import { useRouter } from "next/navigation"; -import { AuthService } from "@/lib/api/auth"; +import { AuthService } from "@/lib/api/v1/auth"; interface UseAuthReturn { login: (email: string, password: string) => Promise; @@ -108,3 +108,5 @@ export const useAuth = (): UseAuthReturn => { clearError, }; }; + + diff --git a/src/lib/api/auth.ts b/src/lib/api/auth.ts index 368ae00d..4d381e76 100644 --- a/src/lib/api/auth.ts +++ b/src/lib/api/auth.ts @@ -32,7 +32,7 @@ interface CompleteRegistrationData { export class AuthService { static async login(credentials: LoginCredentials) { - const response = await fetch("/api/auth/login", { + const response = await fetch("/api/v1/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(credentials), @@ -46,7 +46,7 @@ export class AuthService { return response.json(); } static async register(credentials: RegisterData) { - const response = await fetch("/api/auth/register", { + const response = await fetch("/api/v1/auth/register", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(credentials), @@ -60,7 +60,7 @@ export class AuthService { return response.json(); } static async forgotPassword(data: ForgotPasswordData) { - const response = await fetch("/api/auth/forgot-password", { + const response = await fetch("/api/v1/auth/forgot-password", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), @@ -80,7 +80,7 @@ export class AuthService { } static async resetPassword(data: ResetPasswordData) { - const response = await fetch("/api/auth/reset-password", { + const response = await fetch("/api/v1/auth/reset-password", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), @@ -100,7 +100,7 @@ export class AuthService { } static async completeRegistration(data: CompleteRegistrationData) { - const response = await fetch("/api/auth/complete-registration", { + const response = await fetch("/api/v1/auth/complete-registration", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), @@ -114,3 +114,5 @@ export class AuthService { return response.json(); } } + + diff --git a/src/server/services/BLOCKCHAIN_SERVICE.md b/src/server/services/BLOCKCHAIN_SERVICE.md index 736c5182..5196af1d 100644 --- a/src/server/services/BLOCKCHAIN_SERVICE.md +++ b/src/server/services/BLOCKCHAIN_SERVICE.md @@ -2,8 +2,8 @@ A backend service that wraps the **Stellar Horizon API** and **Soroban RPC**, providing reusable helpers for account management, XDR generation, transaction simulation, and submission. -**Location:** `src/api/services/blockchain.service.ts` -**Tests:** `src/api/services/blockchain.service.spec.ts` +**Location:** `src/api/v1/services/blockchain.service.ts` +**Tests:** `src/api/v1/services/blockchain.service.spec.ts` **Dependency:** `@stellar/stellar-sdk ^14.5.0` --- @@ -348,7 +348,7 @@ Tests are located in `blockchain.service.spec.ts` and use **Vitest** with mocked ```bash # Run only blockchain service tests -pnpm test -- src/api/services/blockchain.service.spec.ts +pnpm test -- src/api/v1/services/blockchain.service.spec.ts # Run all tests pnpm test diff --git a/src/server/services/user.service.ts b/src/server/services/user.service.ts index a5164134..a1997a23 100644 --- a/src/server/services/user.service.ts +++ b/src/server/services/user.service.ts @@ -1,5 +1,6 @@ import { eq } from "drizzle-orm"; import { db, users, userStatusEnum } from "../db"; +import { AuditLogService } from "./audit-log.service"; export type UserStatus = (typeof userStatusEnum.enumValues)[number]; @@ -83,6 +84,11 @@ export class UserService { const oldUser = await this.findById(userId); if (!oldUser) return null; + // Validate avatar URL if provided + if (data.avatarUrl !== undefined && data.avatarUrl !== null) { + validateAvatarUrl(data.avatarUrl); + } + const [updatedUser] = await db .update(users) .set({ ...data, updatedAt: new Date() }) @@ -112,31 +118,4 @@ export class UserService { return updatedUser || null; } - - static async update( - userId: string, - data: { - firstName?: string; - lastName?: string; - avatarUrl?: string; - role?: string; - organizationName?: string; - }, - ) { - // Validate avatar URL if provided - if (data.avatarUrl !== undefined) { - validateAvatarUrl(data.avatarUrl); - } - - const [updatedUser] = await db - .update(users) - .set({ - ...data, - updatedAt: new Date(), - }) - .where(eq(users.id, userId)) - .returning(); - - return updatedUser || null; - } } diff --git a/src/server/swagger-config.ts b/src/server/swagger-config.ts index 4d259bf6..eb387645 100644 --- a/src/server/swagger-config.ts +++ b/src/server/swagger-config.ts @@ -10,8 +10,8 @@ const options: swaggerJSDoc.Options = { }, servers: [ { - url: "/api", - description: "Standard API base", + url: "/api/v1/v1", + description: "Standard API v1 base", }, ], components: { @@ -25,7 +25,8 @@ const options: swaggerJSDoc.Options = { }, }, - apis: ["./src/app/api*.ts"], + apis: ["./src/app/api/v1/**/*.ts"], }; export const swaggerSpec = swaggerJSDoc(options); + diff --git a/test_results.txt b/test_results.txt new file mode 100644 index 0000000000000000000000000000000000000000..445be10c011d8fe3d0c357aa72396871f40c9431 GIT binary patch literal 67510 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 Jr-CW3{|BFefD-@! literal 0 HcmV?d00001 From 3aa12b5e7fb37a2668d183a8fca2f7f687bf9fb9 Mon Sep 17 00:00:00 2001 From: Agbasimere Date: Sun, 29 Mar 2026 15:47:20 +0100 Subject: [PATCH 2/6] fix: resolve eslint parsing errors and prefer-const rule --- src/server/health/route.ts | 69 +++++++-------------- src/server/services/bank-account.service.ts | 10 +-- src/server/services/invitation.service.ts | 2 +- src/server/services/webhook.service.ts | 5 +- 4 files changed, 32 insertions(+), 54 deletions(-) diff --git a/src/server/health/route.ts b/src/server/health/route.ts index a5cdb1f1..d6ea4a02 100644 --- a/src/server/health/route.ts +++ b/src/server/health/route.ts @@ -2,59 +2,36 @@ import { ApiResponse } from "@/server/utils/api-response"; import { pingDb } from "@/server/utils/ping-db"; import { BlockchainService } from "@/server/services/blockchain.service"; import { Logger } from "@/server/services/logger.service"; +import { getServiceDiscovery } from "@/server/utils/service-discovery"; export async function GET() { try { - const blockchainService = new BlockchainService(); - const [rpcHealthy, ledgerHealth] = await Promise.all([ + const blockchainService = new BlockchainService("testnet", getServiceDiscovery()); + + const [dbHealthy, rpcHealthy] = await Promise.all([ + pingDb(), blockchainService.isHealthy(), - blockchainService.getLedgerHealth(), - ]); - const degraded = ledgerHealth.ledgerAgeSeconds > 60; + ]).catch(() => [false, false]); + + const data = { + uptime: process.uptime(), + timestamp: new Date().toISOString(), + environment: process.env.NODE_ENV, + db: dbHealthy ? "ok" : "error", + rpc: rpcHealthy ? "ok" : "error", + }; + + if (!dbHealthy || !rpcHealthy) { + return ApiResponse.error("System is unhealthy", 503, { + status: "unhealthy", + ...data, + }); + } - return ApiResponse.success( - { - uptime: process.uptime(), - timestamp: new Date().toISOString(), - environment: process.env.NODE_ENV, - ledger: ledgerHealth.ledger, - ledgerAgeSeconds: ledgerHealth.ledgerAgeSeconds, - status: !rpcHealthy ? "unhealthy" : degraded ? "degraded" : "healthy", - }, - !rpcHealthy - ? "System is unhealthy" - : degraded - ? "System is degraded" - : "System is healthy", - ); + return ApiResponse.success(data, "System is healthy"); } catch (error) { Logger.error("Health check failed", { error: String(error) }); return ApiResponse.error("Health check failed", 500); } -import { getServiceDiscovery } from "@/server/utils/service-discovery"; - -export async function GET() { - const blockchainService = new BlockchainService("testnet", getServiceDiscovery()); - - const [dbHealthy, rpcHealthy] = await Promise.all([ - pingDb(), - blockchainService.isHealthy(), - ]).catch(() => [false, false]); - - const data = { - uptime: process.uptime(), - timestamp: new Date().toISOString(), - environment: process.env.NODE_ENV, - db: dbHealthy ? "ok" : "error", - rpc: rpcHealthy ? "ok" : "error", - }; - - if (!dbHealthy) { - return ApiResponse.error("System is unhealthy", 503, { - status: "unhealthy", - ...data, - }); - } - - return ApiResponse.success(data, "System is healthy"); } + diff --git a/src/server/services/bank-account.service.ts b/src/server/services/bank-account.service.ts index ce233509..e2ebb9cc 100644 --- a/src/server/services/bank-account.service.ts +++ b/src/server/services/bank-account.service.ts @@ -1,7 +1,7 @@ import { db } from "../db"; import { employees } from "../db/schema"; import { eq } from "drizzle-orm"; - +import { Logger } from "@/server/services/logger.service"; export interface BankValidationResult { isValid: boolean; bankName?: string; @@ -95,7 +95,7 @@ class BankAccountService { return result; } catch (error) { - console.error("[Bank Validation Error]", error); + Logger.error("Bank Validation Error", { error }); return { isValid: false, error: "Bank validation service unavailable", @@ -115,7 +115,7 @@ class BankAccountService { }) .where(eq(employees.id, employeeId)); } catch (error) { - console.error("[Update Employee Account Error]", error); + Logger.error("Update Employee Account Error", { error }); throw new Error("Failed to update employee account details"); } } @@ -188,7 +188,7 @@ class BankAccountService { }, }; } catch (error) { - console.error("[Verify Employee Account Error]", error); + Logger.error("Verify Employee Account Error", { error }); return { isValid: false, isVerified: false, @@ -223,7 +223,7 @@ class BankAccountService { return employee[0] || null; } catch (error) { - console.error("[Get Employee Account Error]", error); + Logger.error("Get Employee Account Error", { error }); throw new Error("Failed to retrieve employee account details"); } } diff --git a/src/server/services/invitation.service.ts b/src/server/services/invitation.service.ts index b67761e2..d45ef4da 100644 --- a/src/server/services/invitation.service.ts +++ b/src/server/services/invitation.service.ts @@ -194,7 +194,7 @@ class InvitationService { const { status, role, page = 1, limit = 20 } = options; const offset = (page - 1) * limit; - let whereConditions = [eq(organizationInvitations.organizationId, organizationId)]; + const whereConditions = [eq(organizationInvitations.organizationId, organizationId)]; if (status) { whereConditions.push(eq(organizationInvitations.status, status)); diff --git a/src/server/services/webhook.service.ts b/src/server/services/webhook.service.ts index d3b94cba..d3ae2ebe 100644 --- a/src/server/services/webhook.service.ts +++ b/src/server/services/webhook.service.ts @@ -1,4 +1,5 @@ import crypto from "crypto"; +import { Logger } from "@/server/services/logger.service"; export class WebhookService { static verifyMonnifySignature(rawBody: string, signature: string) { @@ -14,7 +15,7 @@ export class WebhookService { static async logWebhookPayload(provider: string, payload: any) { // TODO: Replace with DB insert when webhook_audit_logs table is available - console.log("Webhook Audit Log:", { + Logger.info("Webhook Audit Log:", { provider, payload, receivedAt: new Date().toISOString(), @@ -23,7 +24,7 @@ export class WebhookService { static async processSuccessfulDeposit(reference: string, amount: number) { // TODO: Replace with real DB logic - console.log("Processing successful deposit:", { + Logger.info("Processing successful deposit:", { reference, amount, }); From 6b2828653604a8ac19925384d5eb7787a7dfc6a0 Mon Sep 17 00:00:00 2001 From: Agbasimere Date: Sun, 29 Mar 2026 16:32:24 +0100 Subject: [PATCH 3/6] fix: resolve typescript and linting errors after API restructuring --- src/app/api/v1/organizations/logo/route.ts | 9 ++--- .../features/auth/business-details.tsx | 2 +- src/hooks/useAuth.ts | 2 +- src/server/services/auth.service.ts | 2 +- src/server/services/blockchain.service.ts | 35 +++++++------------ .../services/fiat/monnify.provider.test.ts | 4 +-- .../services/invitation.service.spec.ts | 6 ++-- src/server/services/invitation.service.ts | 6 ++-- src/server/services/user.service.ts | 4 +-- src/server/utils/slug.test.ts | 1 + .../validations/bank-verification.schema.ts | 4 +-- src/types/images.d.ts | 8 +++++ 12 files changed, 41 insertions(+), 42 deletions(-) create mode 100644 src/types/images.d.ts diff --git a/src/app/api/v1/organizations/logo/route.ts b/src/app/api/v1/organizations/logo/route.ts index 219780b1..d86232ce 100644 --- a/src/app/api/v1/organizations/logo/route.ts +++ b/src/app/api/v1/organizations/logo/route.ts @@ -112,7 +112,7 @@ export async function PATCH(req: NextRequest) { const [existingProfile] = await db .select() .from(companyProfiles) - .where(eq(companyProfiles.organizationId, user.organizationId)) + .where(eq(companyProfiles.organizationId, user.organizationId as string)) .limit(1); if (!existingProfile) { @@ -120,8 +120,8 @@ export async function PATCH(req: NextRequest) { const [newProfile] = await db .insert(companyProfiles) .values({ - userId, - organizationId: user.organizationId, + userId: userId, + organizationId: user.organizationId as string, logoUrl, // Required fields - we'll use placeholders for now brandName: user.organizationName || "Unknown", @@ -130,6 +130,7 @@ export async function PATCH(req: NextRequest) { country: "", address: "", city: "", + billingCountry: "NG", // Default for now }) .returning(); @@ -146,7 +147,7 @@ export async function PATCH(req: NextRequest) { logoUrl, updatedAt: new Date(), }) - .where(eq(companyProfiles.organizationId, user.organizationId)) + .where(eq(companyProfiles.organizationId, user.organizationId as string)) .returning(); return ApiResponse.success( diff --git a/src/components/features/auth/business-details.tsx b/src/components/features/auth/business-details.tsx index 9f9dcbc7..9ffdeae2 100644 --- a/src/components/features/auth/business-details.tsx +++ b/src/components/features/auth/business-details.tsx @@ -3,7 +3,7 @@ import React, { useState, useEffect } from "react"; import { ChevronDown } from "lucide-react"; import ModalWelcomeOnboard from "@/components/shared/modal-welcome-onboard"; import Stepper from "@/components/features/auth/Stepper"; -import { AuthService } from "@/lib/api/v1/auth"; +import { AuthService } from "@/lib/api/auth"; interface FormData { companyName: string; diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts index b14c9d75..ba4475b9 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAuth.ts @@ -1,6 +1,6 @@ import { useState } from "react"; import { useRouter } from "next/navigation"; -import { AuthService } from "@/lib/api/v1/auth"; +import { AuthService } from "@/lib/api/auth"; interface UseAuthReturn { login: (email: string, password: string) => Promise; diff --git a/src/server/services/auth.service.ts b/src/server/services/auth.service.ts index 7f84b65c..05400cce 100644 --- a/src/server/services/auth.service.ts +++ b/src/server/services/auth.service.ts @@ -7,8 +7,8 @@ import { biometricLogs, passkeyRegistrationChallenges, } from "../db"; -import pc from "picocolors"; import crypto from "crypto"; +import { generateSlug } from "../utils/slug"; import { AuditLogService } from "./audit-log.service"; import { OTP_EXPIRATION_MINUTES } from "./email-verification.service"; import { UserService } from "./user.service"; diff --git a/src/server/services/blockchain.service.ts b/src/server/services/blockchain.service.ts index 7a203870..99aba3aa 100644 --- a/src/server/services/blockchain.service.ts +++ b/src/server/services/blockchain.service.ts @@ -541,22 +541,8 @@ export class BlockchainService { ); } - const data = (await response.json()) as - | { - sequence: number | string; - closed_at: string; - } - | { - _embedded?: { - records?: Array<{ - sequence: number | string; - closed_at: string; - }>; - }; - }; - - const ledgerRecord = - "_embedded" in data ? data._embedded?.records?.[0] : data; + const data = (await response.json()) as any; + const ledgerRecord = data._embedded ? data._embedded.records?.[0] : data; if (!ledgerRecord?.closed_at || ledgerRecord.sequence == null) { throw new Error(params.missingDataMessage); @@ -634,7 +620,7 @@ export class BlockchainService { params: GetContractEventsParams, ): Promise { try { - const response = await this.rpcServer.getEvents({ + const requestParams: any = { filters: params.contractId ? [ { @@ -643,15 +629,20 @@ export class BlockchainService { }, ] : [], - startLedger: params.fromLedger, limit: params.limit, - }); + }; + + if (params.fromLedger) { + requestParams.startLedger = params.fromLedger; + } + + const response = await this.rpcServer.getEvents(requestParams); - return response.events.map((event) => ({ + return response.events.map((event: any) => ({ id: event.id, ledger: event.ledger, - contractId: event.contractId, - topics: event.topic.map((t) => scValToNative(t)), + contractId: typeof event.contractId === "string" ? event.contractId : event.contractId?.toString() || "", + topics: event.topic.map((t: any) => scValToNative(t)), value: scValToNative(event.value), })); } catch (error) { diff --git a/src/server/services/fiat/monnify.provider.test.ts b/src/server/services/fiat/monnify.provider.test.ts index 164b3351..baa0408d 100644 --- a/src/server/services/fiat/monnify.provider.test.ts +++ b/src/server/services/fiat/monnify.provider.test.ts @@ -133,8 +133,8 @@ describe("MonnifyProvider", () => { }); // auth was called only once (first fetch call) - const authCalls = fetchMock.mock.calls.filter(([url]: [string]) => - url.includes("/auth/login") + const authCalls = fetchMock.mock.calls.filter((args: any[]) => + args[0].includes("/auth/login") ); expect(authCalls).toHaveLength(1); }); diff --git a/src/server/services/invitation.service.spec.ts b/src/server/services/invitation.service.spec.ts index 88ec211b..1a31c8ca 100644 --- a/src/server/services/invitation.service.spec.ts +++ b/src/server/services/invitation.service.spec.ts @@ -1,7 +1,7 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { invitationService } from "../invitation.service"; -import { db } from "../../db"; -import { organizationInvitations, users, organizations } from "../../db/schema"; +import { invitationService } from "./invitation.service"; +import { db } from "../db"; +import { organizationInvitations, users, organizations } from "../db/schema"; import { eq } from "drizzle-orm"; describe("InvitationService", () => { diff --git a/src/server/services/invitation.service.ts b/src/server/services/invitation.service.ts index d45ef4da..7798e558 100644 --- a/src/server/services/invitation.service.ts +++ b/src/server/services/invitation.service.ts @@ -1,7 +1,7 @@ import { db } from "../db"; import { organizationInvitations, users, organizations } from "../db/schema"; import { eq, and, desc, lt } from "drizzle-orm"; -import { nanoid } from "nanoid"; +import crypto from "crypto"; import { addDays, isPast } from "date-fns"; import { EmailService } from "./email.service"; @@ -74,7 +74,7 @@ class InvitationService { } // Generate invitation token - const token = nanoid(32); + const token = crypto.randomBytes(32).toString('hex'); const expiresAt = addDays(new Date(), 7); // 7 days expiry // Create invitation @@ -313,7 +313,7 @@ class InvitationService { } // Generate new token and extend expiry - const newToken = nanoid(32); + const newToken = crypto.randomBytes(32).toString('hex'); const newExpiresAt = addDays(new Date(), 7); await db diff --git a/src/server/services/user.service.ts b/src/server/services/user.service.ts index 8dcc0201..bb2a47aa 100644 --- a/src/server/services/user.service.ts +++ b/src/server/services/user.service.ts @@ -1,7 +1,7 @@ import { eq, sql } from "drizzle-orm"; import { db, users, userStatusEnum, signerTypeEnum } from "../db"; import { AuditLogService } from "./audit-log.service"; -import type { Transaction } from "drizzle-orm/postgres-core"; +import type { PgTransaction } from "drizzle-orm/pg-core"; export type UserStatus = (typeof userStatusEnum.enumValues)[number]; export type SignerType = (typeof signerTypeEnum.enumValues)[number]; @@ -58,7 +58,7 @@ export class UserService { lastName: string; email: string; }, - tx?: Transaction, + tx?: PgTransaction, ) { const normalizedEmail = data.email.toLowerCase().trim(); diff --git a/src/server/utils/slug.test.ts b/src/server/utils/slug.test.ts index 1974e392..7719acef 100644 --- a/src/server/utils/slug.test.ts +++ b/src/server/utils/slug.test.ts @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { generateSlug } from './slug'; describe('generateSlug', () => { diff --git a/src/server/validations/bank-verification.schema.ts b/src/server/validations/bank-verification.schema.ts index 7f7b3da4..00f13ba7 100644 --- a/src/server/validations/bank-verification.schema.ts +++ b/src/server/validations/bank-verification.schema.ts @@ -17,9 +17,7 @@ export const BankVerificationSchema = z "Numeric or alphanumeric bank/institution code as defined by the payment provider (e.g. Paystack bank code '044' for Access Bank Nigeria). Used by the provider to route the account lookup.", ), providerId: z - .enum(["paystack", "flutterwave"], { - errorMap: () => ({ message: "Provider must be paystack or flutterwave" }), - }) + .enum(["paystack", "flutterwave"]) .describe( "Payment provider to use for the account name-enquiry lookup. 'paystack' = Paystack Resolve Account API; 'flutterwave' = Flutterwave Verify Bank Account API.", ), diff --git a/src/types/images.d.ts b/src/types/images.d.ts new file mode 100644 index 00000000..2041fb26 --- /dev/null +++ b/src/types/images.d.ts @@ -0,0 +1,8 @@ +declare module "*.png" { + const value: any; + export default value; +} +declare module "*.svg" { + const value: any; + export default value; +} From dcba363e8aabcf9bebd519f4a024eeb27c923d01 Mon Sep 17 00:00:00 2001 From: Agbasimere Date: Sun, 29 Mar 2026 19:56:54 +0100 Subject: [PATCH 4/6] Fix remaining lint and type errors in services and tests --- git_status.txt | Bin 0 -> 9620 bytes git_status_after_resolve.txt | 94 +++++++++++++ git_status_utf8.txt | 97 +++++++++++++ git_status_v2.txt | Bin 0 -> 2080 bytes lint_output_utf8.txt | 29 ++++ lint_results_v2.txt | Bin 0 -> 5824 bytes src/server/services/auth.service.spec.ts | 18 +-- src/server/services/blockchain.service.ts | 11 +- .../services/fiat/monnify.provider.test.ts | 4 +- .../services/invitation.service.spec.ts | 79 +++++------ src/server/services/rate-limit.service.ts | 7 +- src/server/services/user.service.spec.ts | 8 +- src/server/services/user.service.ts | 1 + src/server/services/webhook.service.ts | 5 +- tsc_output.txt | Bin 0 -> 43874 bytes tsc_output_utf8.txt | 127 ++++++++++++++++++ 16 files changed, 419 insertions(+), 61 deletions(-) create mode 100644 git_status.txt create mode 100644 git_status_after_resolve.txt create mode 100644 git_status_utf8.txt create mode 100644 git_status_v2.txt create mode 100644 lint_output_utf8.txt create mode 100644 lint_results_v2.txt create mode 100644 tsc_output.txt create mode 100644 tsc_output_utf8.txt diff --git a/git_status.txt b/git_status.txt new file mode 100644 index 0000000000000000000000000000000000000000..068353886da812eda37c446bc490950b969942d1 GIT binary patch literal 9620 zcmd6tO>f&q5Qg`f0{ss}E@=;v&>n-Jm;8X9dMJvb(3fRJGAZc8R{P`IzR!%-ONo-L zC`%?FY>Cuz-kI6?+};2F{WX-K2&1qLLwy^Ed)?iIsqVI6p#Q5d554dhe$xMyMosnW zJnX~w;k)pAsKQRqPT!x0jh^i^z7Um4|0hOos&7wvduu4~^vhazGyRpqx(4-K=o@lE zFGOo7{-C`Rodf9B)7E5uZ+yKGpOxg8o2=;Kz24;NYg8$H@wpdY57N<6-$AIOrrBdkqXT33Z zeNp(NUtfNIBYln!_OyDotAW`SRyH=8%pbEb&}b|RiTd^=FZD+uB2Bc)_VKVu=Hl<{ zvZ1AWQTp4B3UOP5gG{q!Pn_yhBdaK$o%}^KhHI*vLc|c0%4KOMOn4k7ch9K1_D-J3QH~ zE%g*w#!&H;4!M6al6yR5oEiJuK*L6^TQiCJLyPCM`FhlBYBnc2d6BgQ@8hWEns{x@ zBIgu6FrFjqWeQmx<=IqOF@O-LjYTEUY2NCn1w9W_> zJUp&D#udQV?CrQS=oxD447!oO-n(}nL9R#7XM5**q;7QmN%OmXp(5*&i?557Z#H9J zK_==(re@XG#LXvP%IWk<9#4F4%45<-k%!t7t7d;9MjWpYxX+*WE9rVAJFa)!-5{q` zcXl+qhixL~(>~f{1=V%jL~293vA_7*X!*2AceIcu%Y)6_9W1A-I(4hK%gO_LMyxWj zPSMnEK6#^`HZi{3UWjwl8g#pHzuNt9WpMzDy}iFUrtZ~!PZZa>&>B0-x9X`*yIqO{ zi-uHd`%L`Sv&?0Ed|owJ*N4}bzKi)FONDpVaDArhI=>x*8Kg$VDw{dscDnJ5LtQVu z!#l};M?&c#Bb+%J&Ybxof1 zlLl{WhlNhL5GUu)4#XPXJM8FfX&2wUV<776efdi8r8PD7bFM>+`5WyFYz>6H0^jvM zzXEY9?TD1t5>of;`vu5U3cDG{QVht4eqCMO2J(|^aU@-{i@9h?TGoDMx|ml#CBRu6 zKT{HAioW5yVypxBxt`cvZq#2g=1aT-EsxD3h%tplO3o>..." to include in what will be committed) + git_status.txt + git_status_after_resolve.txt + git_status_utf8.txt + tsc_output.txt + diff --git a/git_status_utf8.txt b/git_status_utf8.txt new file mode 100644 index 00000000..0f22c2f9 --- /dev/null +++ b/git_status_utf8.txt @@ -0,0 +1,97 @@ +On branch feat/api-v1-prefix +Your branch is up to date with 'origin/feat/api-v1-prefix'. + +You have unmerged paths. + (fix conflicts and run "git commit") + (use "git merge --abort" to abort the merge) + +Changes to be committed: + modified: .env.example + modified: eslint.config.mjs + modified: src/app/api/v1/accounts/route.ts + modified: src/app/api/v1/accounts/validate/route.ts + modified: src/app/api/v1/accounts/verify/route.ts + modified: src/app/api/v1/auth/2fa/disable/route.ts + modified: src/app/api/v1/auth/2fa/regenerate-backup-codes/route.ts + modified: src/app/api/v1/auth/2fa/setup/route.ts + modified: src/app/api/v1/auth/2fa/status/route.ts + modified: src/app/api/v1/auth/2fa/verify-setup/route.ts + modified: src/app/api/v1/auth/2fa/verify/route.ts + modified: src/app/api/v1/auth/apple/route.ts + modified: src/app/api/v1/auth/change-password/route.ts + modified: src/app/api/v1/auth/google/route.ts + modified: src/app/api/v1/company/profile/route.ts + modified: src/app/api/v1/dashboard/attention/route.ts + modified: src/app/api/v1/dashboard/onboarding/route.ts + modified: src/app/api/v1/dashboard/user-summary/route.ts + modified: src/app/api/v1/finance/transactions/[id]/route.ts + modified: src/app/api/v1/finance/transactions/route.ts + modified: src/app/api/v1/finance/wallet/route.ts + modified: src/app/api/v1/invitations/accept/route.ts + modified: src/app/api/v1/invitations/delete/route.ts + modified: src/app/api/v1/invitations/resend/route.ts + modified: src/app/api/v1/invitations/route.ts + modified: src/app/api/v1/kyb/status/route.ts + modified: src/app/api/v1/kyb/submit/route.ts + modified: src/app/api/v1/organizations/logo-upload-url/route.ts + modified: src/app/api/v1/organizations/logo/route.ts + modified: src/app/api/v1/team/employees/[id]/route.ts + modified: src/app/api/v1/team/employees/bank-verification/route.ts + modified: src/app/api/v1/team/employees/route.ts + modified: src/app/api/v1/team/expenses/[id]/status/route.ts + modified: src/app/api/v1/team/milestones/route.ts + modified: src/app/api/v1/team/time-off/[id]/status/route.ts + modified: src/app/api/v1/team/time-off/route.ts + modified: src/app/api/v1/team/timesheets/[id]/status/route.ts + modified: src/app/api/v1/team/timesheets/route.ts + modified: src/middleware.ts + modified: src/server/services/apple-oauth.service.spec.ts + modified: src/server/services/auth.service.spec.ts + modified: src/server/services/auth.service.ts + modified: src/server/services/bank-verification.service.ts + modified: src/server/services/blockchain.service.spec.ts + modified: src/server/services/blockchain.service.ts + modified: src/server/services/email-verification.service.spec.ts + modified: src/server/services/fiat/flutterwave.provider.ts + new file: src/server/services/finance-wallet.service.spec.ts + modified: src/server/services/google-oauth.service.spec.ts + modified: src/server/services/google-oauth.service.ts + modified: src/server/services/invitation.service.spec.ts + modified: src/server/services/jwt-verification.service.spec.ts + modified: src/server/services/jwt.service.spec.ts + modified: src/server/services/jwt.service.ts + modified: src/server/services/logout.service.spec.ts + modified: src/server/services/milestone.service.ts + modified: src/server/services/oauth-user-provisioning.service.spec.ts + modified: src/server/services/otp-resend.service.spec.ts + modified: src/server/services/rate-limit.service.ts + modified: src/server/services/session.service.spec.ts + modified: src/server/services/team.service.spec.ts + modified: src/server/services/team.service.ts + modified: src/server/services/token-refresh.service.spec.ts + modified: src/server/services/two-factor.service.ts + modified: src/server/services/user.service.spec.ts + modified: src/server/utils/api-response.ts + modified: src/server/utils/auth-errors.ts + modified: src/server/utils/errors.ts + modified: src/server/utils/errors/index.ts + modified: src/server/utils/logout-errors.ts + new file: src/server/utils/problem-details.ts + new file: src/server/utils/role.spec.ts + new file: src/server/utils/with-error-handler.ts + +Unmerged paths: + (use "git add ..." to mark resolution) + both modified: src/app/api/v1/auth/logout/route.ts + both modified: src/server/health/route.ts + both modified: src/server/services/bank-account.service.ts + both modified: src/server/services/invitation.service.ts + both modified: src/server/services/webhook.service.ts + both modified: src/server/swagger-config.ts + +Untracked files: + (use "git add ..." to include in what will be committed) + git_status.txt + git_status_utf8.txt + tsc_output.txt + diff --git a/git_status_v2.txt b/git_status_v2.txt new file mode 100644 index 0000000000000000000000000000000000000000..457f2d4c518e33809ed71db5fe99fb037f5ae30f GIT binary patch literal 2080 zcmchYO-{ow5QS$85_gEY0Tu=2Vlj5Rg@HnfFB{KYn9HntJ7$}SlTORaA&cEwmAHDWilm7UnJJy~uwYn$g& zD_N_N573e`CQjdoza^(DhjPl!g4u+gfwd3nsa-f^q2HrLHczlgcp3UCz9nPiRug7p zhZ5jfZqGEK51fvG-7~B#IUiWh*`f0h*6QS~^K{Pc9jIzOR=q#+t?iR6(OG#nr->11 zZKGDKBzC?NTl~s?F{AUlY)HT=L5Pr*y}G+LA7WJ&N!QGAJYlX=(j7M_uZ=}VaFe@T z-LEViAgxv+A9FwAX+b1t{Fce>2 zyr3@f;h6R>Qat`g9DR>=IFFXFHzZ1P+H8wopGbNckKAiT6h$t!^$wN8lvNZ{zt3EX zqZn;bGQ=t$lVVa4zoPp$uFPK9x}vgz_IB3Tsk;iXc2l#~;|;y*{%n)jjx6?6&AK_= j*Crnoz7n-7%&vN-+NJ7{kf~J>+7)kIM<2OfQx!e{L!xP> literal 0 HcmV?d00001 diff --git a/lint_output_utf8.txt b/lint_output_utf8.txt new file mode 100644 index 00000000..fb1b0524 --- /dev/null +++ b/lint_output_utf8.txt @@ -0,0 +1,29 @@ + +> vestroll-frontend@0.1.0 lint C:\Users\USER\Desktop\drips\vestroll +> eslint + + +C:\Users\USER\Desktop\drips\vestroll\src\server\services\blockchain.service.ts + 544:45 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + 623:28 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + 641:42 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + 645:37 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +C:\Users\USER\Desktop\drips\vestroll\src\server\services\fiat\monnify.provider.test.ts + 136:60 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +C:\Users\USER\Desktop\drips\vestroll\src\server\services\rate-limit.service.ts + 106:40 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + 108:44 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +C:\Users\USER\Desktop\drips\vestroll\src\server\services\user.service.ts + 61:24 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + 61:29 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + 61:34 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +C:\Users\USER\Desktop\drips\vestroll\src\server\services\webhook.service.ts + 4:39 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +Γ£û 11 problems (11 errors, 0 warnings) + +ΓÇëELIFECYCLEΓÇë Command failed with exit code 1. diff --git a/lint_results_v2.txt b/lint_results_v2.txt new file mode 100644 index 0000000000000000000000000000000000000000..0112790e9f43ffcfafafcb2b11b3d19f1c5cf2e7 GIT binary patch literal 5824 zcmdUz&2G~`6orq}9TM;0B}n`vb|6U?s8Rt5sVk~3kg^GhLoM2*a@>|aM<0QQ;bC|L zq{4T`Hmz87fk0Q*B=d7Ub8>R$&Ykn~=AjtZ)K-mZZFIu(RI5~9(^qP(rS>$? zGYu&N>`zqD+fduru4X#o?UFaPseY(8dQa_jn)New8=cW!Vo_1E)t@W&NQ|SO-Fo zY8}B>sW07|W_3yboZc(u$#mF_R4{*)MlvkiT47sLTD}`by}DQ2WBs| z!{@xWNqt`1%q}RlYS+F+KLUST^kcmMea@$uiN^gjF!wpw^RS+iYouvl^pnKBe?>gM zFNl?+u-uP`_Kx@QxOdF|_vfiIhKqFe%sGE5PNns|&(@4RYOcpBlAp(m@I!okmq_VY z8E>Z!;eFiUj~HppzVicTXycIlPm=%lxn(r}&lB%)`On$46Z|JG|D%LIs{iNgTE~CK z&xjd*k>Y+_|1ZeX`uI5w7(bn9w&z#O8=~*#&Xn!{(azuhlWm@Ns?B}Xn~eDATWr+) z%(oGrPZ6^l@5v%8`l8Ei;&JbtdA;*hKZxi#zLTL4TCx0yPFU)c9hB)&H*%|}ZS{nH ztH76Nh&8u3-|RkN{rI<9d%g2b-%)`N^iFU2-q#1p0kw9-y77h;PLm30C3HotO6$6H ZneK_l)U26WuguUbHz=Z3KCMC<`!8BA@~Qv; literal 0 HcmV?d00001 diff --git a/src/server/services/auth.service.spec.ts b/src/server/services/auth.service.spec.ts index 0546d39c..90c47a9e 100644 --- a/src/server/services/auth.service.spec.ts +++ b/src/server/services/auth.service.spec.ts @@ -107,7 +107,7 @@ describe("AuthService – registration + email-verification funnel (integration) describe("register", () => { it("should create a new user and persist a verification hash inside a transaction", async () => { // No pre-existing user - vi.mocked(UserService.findByEmail).mockResolvedValue(null); + vi.mocked(UserService.findByEmail).mockResolvedValue(null as any); const result = await AuthService.register(VALID_REGISTER_INPUT); @@ -138,7 +138,7 @@ describe("AuthService – registration + email-verification funnel (integration) }); it("should persist an organization row when companyName is supplied", async () => { - vi.mocked(UserService.findByEmail).mockResolvedValue(null); + vi.mocked(UserService.findByEmail).mockResolvedValue(null as any); await AuthService.register(VALID_REGISTER_INPUT); @@ -172,7 +172,7 @@ describe("AuthService – registration + email-verification funnel (integration) }; vi.mocked(UserService.findByEmail).mockResolvedValue( - existingUser as UserLookupResult, + existingUser as any, ); await expect( @@ -188,7 +188,7 @@ describe("AuthService – registration + email-verification funnel (integration) }); it("should not write an organization row when companyName is absent", async () => { - vi.mocked(UserService.findByEmail).mockResolvedValue(null); + vi.mocked(UserService.findByEmail).mockResolvedValue(null as any); const inputWithoutCompany: RegisterInput = { ...VALID_REGISTER_INPUT, @@ -209,7 +209,7 @@ describe("AuthService – registration + email-verification funnel (integration) describe("EmailVerificationService.getVerificationStatus – hash read-back", () => { it("should return the persisted verification record for the newly created user", async () => { - vi.mocked(UserService.findByEmail).mockResolvedValue(null); + vi.mocked(UserService.findByEmail).mockResolvedValue(null as any); const { userId } = await AuthService.register(VALID_REGISTER_INPUT); @@ -246,7 +246,7 @@ describe("AuthService – registration + email-verification funnel (integration) it("should mark the verification record as verified and activate the user", async () => { // ── Arrange ────────────────────────────────────────────────────────── - vi.mocked(UserService.findByEmail).mockResolvedValue(null); + vi.mocked(UserService.findByEmail).mockResolvedValue(null as any); // 1. Run registration so the store contains a user + verification row. const { userId, email } = await AuthService.register( @@ -279,7 +279,7 @@ describe("AuthService – registration + email-verification funnel (integration) updatedAt: new Date(), }; vi.mocked(UserService.findByEmail).mockResolvedValue( - pendingUser as UserLookupResult, + pendingUser as any, ); // 3. Wire db.select to return the unverified verification row. @@ -361,7 +361,7 @@ describe("AuthService – registration + email-verification funnel (integration) describe("post-verification state – restriction cleared", () => { it("should reflect no remaining pending verification constraint after successful verify", async () => { // ── Register ───────────────────────────────────────────────────────── - vi.mocked(UserService.findByEmail).mockResolvedValue(null); + vi.mocked(UserService.findByEmail).mockResolvedValue(null as any); const { userId, email } = await AuthService.register( VALID_REGISTER_INPUT, @@ -397,7 +397,7 @@ describe("AuthService – registration + email-verification funnel (integration) updatedAt: new Date(), }; vi.mocked(UserService.findByEmail).mockResolvedValue( - pendingUser as UserLookupResult, + pendingUser as any, ); mockDb.select = vi.fn().mockReturnValue({ diff --git a/src/server/services/blockchain.service.ts b/src/server/services/blockchain.service.ts index e0072987..b68100cb 100644 --- a/src/server/services/blockchain.service.ts +++ b/src/server/services/blockchain.service.ts @@ -541,8 +541,9 @@ export class BlockchainService { ); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any const data = (await response.json()) as any; - const ledgerRecord = data._embedded ? data._embedded.records?.[0] : data; + const ledgerRecord = data._embedded ? (data._embedded as any).records?.[0] : data; if (!ledgerRecord?.closed_at || ledgerRecord.sequence == null) { throw new Error(params.missingDataMessage); @@ -620,6 +621,7 @@ export class BlockchainService { params: GetContractEventsParams, ): Promise { try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const requestParams: any = { filters: params.contractId ? [ @@ -636,12 +638,15 @@ export class BlockchainService { requestParams.startLedger = params.fromLedger; } - const response = await this.rpcServer.getEvents(requestParams); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const response = await (this.rpcServer as any).getEvents(requestParams); - return response.events.map((event: any) => ({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (response as any).events.map((event: any) => ({ id: event.id, ledger: event.ledger, contractId: typeof event.contractId === "string" ? event.contractId : event.contractId?.toString() || "", + // eslint-disable-next-line @typescript-eslint/no-explicit-any topics: event.topic.map((t: any) => scValToNative(t)), value: scValToNative(event.value), })); diff --git a/src/server/services/fiat/monnify.provider.test.ts b/src/server/services/fiat/monnify.provider.test.ts index baa0408d..6a5fb7a4 100644 --- a/src/server/services/fiat/monnify.provider.test.ts +++ b/src/server/services/fiat/monnify.provider.test.ts @@ -133,8 +133,8 @@ describe("MonnifyProvider", () => { }); // auth was called only once (first fetch call) - const authCalls = fetchMock.mock.calls.filter((args: any[]) => - args[0].includes("/auth/login") + const authCalls = fetchMock.mock.calls.filter((args: unknown[]) => + (args[0] as string).includes("/auth/login"), ); expect(authCalls).toHaveLength(1); }); diff --git a/src/server/services/invitation.service.spec.ts b/src/server/services/invitation.service.spec.ts index 94ee0718..825ee875 100644 --- a/src/server/services/invitation.service.spec.ts +++ b/src/server/services/invitation.service.spec.ts @@ -15,6 +15,7 @@ describe("InvitationService", () => { .insert(organizations) .values({ name: "Test Organization", + slug: "test-organization", industry: "Technology", }) .returning(); @@ -66,9 +67,9 @@ describe("InvitationService", () => { expect(invitation.invitedBy.email).toBe(testUser.email); // Add to cleanup - cleanup.push(() => - db.delete(organizationInvitations).where(eq(organizationInvitations.id, invitation.id)) - ); + cleanup.push(async () => { + await db.delete(organizationInvitations).where(eq(organizationInvitations.id, invitation.id)); + }); }); it("should throw error if user already exists in organization", async () => { @@ -85,9 +86,9 @@ describe("InvitationService", () => { }) .returning(); - cleanup.push(() => - db.delete(users).where(eq(users.id, existingUser.id)) - ); + cleanup.push(async () => { + await (db.delete(users).where(eq(users.id, existingUser.id)) as any); + }); const invitationData = { organizationId: testOrganization.id, @@ -111,9 +112,9 @@ describe("InvitationService", () => { // Create first invitation const firstInvitation = await invitationService.createInvitation(invitationData); - cleanup.push(() => - db.delete(organizationInvitations).where(eq(organizationInvitations.id, firstInvitation.id)) - ); + cleanup.push(async () => { + await (db.delete(organizationInvitations).where(eq(organizationInvitations.id, firstInvitation.id)) as any); + }); // Try to create duplicate invitation await expect(invitationService.createInvitation(invitationData)).rejects.toThrow( @@ -132,9 +133,9 @@ describe("InvitationService", () => { }; const createdInvitation = await invitationService.createInvitation(invitationData); - cleanup.push(() => - db.delete(organizationInvitations).where(eq(organizationInvitations.id, createdInvitation.id)) - ); + cleanup.push(async () => { + await (db.delete(organizationInvitations).where(eq(organizationInvitations.id, createdInvitation.id)) as any); + }); const retrievedInvitation = await invitationService.getInvitationByToken(createdInvitation.token); @@ -160,8 +161,8 @@ describe("InvitationService", () => { }; const invitation = await invitationService.createInvitation(invitationData); - cleanup.push(() => - db.delete(organizationInvitations).where(eq(organizationInvitations.id, invitation.id)) + cleanup.push(async () => + await (db.delete(organizationInvitations).where(eq(organizationInvitations.id, invitation.id)) as any) ); // Create a new user to accept the invitation @@ -175,9 +176,9 @@ describe("InvitationService", () => { status: "pending_verification", }) .returning(); - cleanup.push(() => - db.delete(users).where(eq(users.id, newUser.id)) - ); + cleanup.push(async () => { + await (db.delete(users).where(eq(users.id, newUser.id)) as any); + }); await invitationService.acceptInvitation(invitation.token, newUser.id); @@ -202,8 +203,8 @@ describe("InvitationService", () => { }; const invitation = await invitationService.createInvitation(invitationData); - cleanup.push(() => - db.delete(organizationInvitations).where(eq(organizationInvitations.id, invitation.id)) + cleanup.push(async () => + await (db.delete(organizationInvitations).where(eq(organizationInvitations.id, invitation.id)) as any) ); // Manually set invitation to expired @@ -223,9 +224,9 @@ describe("InvitationService", () => { passwordHash: "hashedpassword", }) .returning(); - cleanup.push(() => - db.delete(users).where(eq(users.id, newUser.id)) - ); + cleanup.push(async () => { + await (db.delete(users).where(eq(users.id, newUser.id)) as any); + }); await expect( invitationService.acceptInvitation(invitation.token, newUser.id) @@ -243,8 +244,8 @@ describe("InvitationService", () => { }; const invitation = await invitationService.createInvitation(invitationData); - cleanup.push(() => - db.delete(organizationInvitations).where(eq(organizationInvitations.id, invitation.id)) + cleanup.push(async () => + await (db.delete(organizationInvitations).where(eq(organizationInvitations.id, invitation.id)) as any) ); await invitationService.declineInvitation(invitation.token, "Not interested"); @@ -265,8 +266,8 @@ describe("InvitationService", () => { }; const originalInvitation = await invitationService.createInvitation(invitationData); - cleanup.push(() => - db.delete(organizationInvitations).where(eq(organizationInvitations.id, originalInvitation.id)) + cleanup.push(async () => + await (db.delete(organizationInvitations).where(eq(organizationInvitations.id, originalInvitation.id)) as any) ); const originalToken = originalInvitation.token; @@ -307,8 +308,8 @@ describe("InvitationService", () => { }; const invitation = await invitationService.createInvitation(invitationData); - cleanup.push(() => - db.delete(organizationInvitations).where(eq(organizationInvitations.id, invitation.id)) + cleanup.push(async () => + await (db.delete(organizationInvitations).where(eq(organizationInvitations.id, invitation.id)) as any) ); // Manually set invitation to accepted @@ -340,8 +341,8 @@ describe("InvitationService", () => { const invitation = await invitationService.createInvitation(invitationData); invitations.push(invitation); - cleanup.push(() => - db.delete(organizationInvitations).where(eq(organizationInvitations.id, invitation.id)) + cleanup.push(async () => + await (db.delete(organizationInvitations).where(eq(organizationInvitations.id, invitation.id)) as any) ); } @@ -362,9 +363,9 @@ describe("InvitationService", () => { email: "pending@example.com", role: "employee" as const, }); - cleanup.push(() => - db.delete(organizationInvitations).where(eq(organizationInvitations.id, pendingInvitation.id)) - ); + cleanup.push(async () => { + await (db.delete(organizationInvitations).where(eq(organizationInvitations.organizationId, testOrganization.id)) as any); + }); // Manually create an accepted invitation const [acceptedInvitation] = await db @@ -380,9 +381,9 @@ describe("InvitationService", () => { expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), }) .returning(); - cleanup.push(() => - db.delete(organizationInvitations).where(eq(organizationInvitations.id, acceptedInvitation.id)) - ); + cleanup.push(async () => { + await db.delete(organizationInvitations).where(eq(organizationInvitations.id, acceptedInvitation.id)); + }); const result = await invitationService.listInvitations(testOrganization.id, { status: "pending", @@ -409,9 +410,9 @@ describe("InvitationService", () => { expiresAt: new Date(Date.now() - 1000), // 1 second ago }) .returning(); - cleanup.push(() => - db.delete(organizationInvitations).where(eq(organizationInvitations.id, expiredInvitation.id)) - ); + cleanup.push(async () => { + await (db.delete(organizationInvitations).where(eq(organizationInvitations.id, expiredInvitation.id)) as any); + }); await invitationService.expireInvitations(); diff --git a/src/server/services/rate-limit.service.ts b/src/server/services/rate-limit.service.ts index 096b7ec1..78409bda 100644 --- a/src/server/services/rate-limit.service.ts +++ b/src/server/services/rate-limit.service.ts @@ -103,10 +103,11 @@ export class RateLimitService { } export function withKybRateLimit( - handler: (req: NextRequest, ...args: any[]) => Promise + handler: (req: NextRequest, ...args: unknown[]) => Promise, ) { - return async (req: NextRequest, ...args: any[]) => { - let identifier = req.headers.get("x-forwarded-for") || req.ip; + return async (req: NextRequest, ...args: unknown[]) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let identifier = req.headers.get("x-forwarded-for") || (req as any).ip; // Fallback if IP is not available if (!identifier) { diff --git a/src/server/services/user.service.spec.ts b/src/server/services/user.service.spec.ts index d41d5d90..70b51679 100644 --- a/src/server/services/user.service.spec.ts +++ b/src/server/services/user.service.spec.ts @@ -79,7 +79,7 @@ describe("UserService", () => { vi.mocked(db.insert).mockReturnValue({ values: mockValues, - } as InsertChain); + } as any); const result = await UserService.create(userData); @@ -137,7 +137,7 @@ describe("UserService", () => { }), }; - const result = await UserService.create(userData, mockTx as UserCreateTx); + const result = await UserService.create(userData, mockTx as any); expect(mockTx.insert).toHaveBeenCalledWith(users); expect(mockValues).toHaveBeenCalledWith({ @@ -250,7 +250,7 @@ describe("UserService", () => { vi.mocked(db.transaction).mockImplementation(transactionCallback as never); - const result = await UserService.create(userData, mockTx as UserCreateTx); + const result = await UserService.create(userData, mockTx as any); expect(result).toEqual(mockUser); expect(mockTx.insert).toHaveBeenCalledWith(users); @@ -304,7 +304,7 @@ describe("UserService", () => { }), }; - const result = await UserService.create(userData, mockTx as UserCreateTx); + const result = await UserService.create(userData, mockTx as any); expect(result).toEqual(mockUser); expect(mockTx.insert).toHaveBeenCalledWith(users); diff --git a/src/server/services/user.service.ts b/src/server/services/user.service.ts index bb2a47aa..85779b8a 100644 --- a/src/server/services/user.service.ts +++ b/src/server/services/user.service.ts @@ -58,6 +58,7 @@ export class UserService { lastName: string; email: string; }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any tx?: PgTransaction, ) { const normalizedEmail = data.email.toLowerCase().trim(); diff --git a/src/server/services/webhook.service.ts b/src/server/services/webhook.service.ts index bb2288c6..4255c364 100644 --- a/src/server/services/webhook.service.ts +++ b/src/server/services/webhook.service.ts @@ -1,7 +1,10 @@ import { Logger } from "./logger.service"; export class WebhookService { - static async handleWebhook(payload: any, signature: string) { + static async handleWebhook( + payload: Record, + signature: string, + ) { // Mock webhook verification if (!signature) { throw new Error("Missing signature"); diff --git a/tsc_output.txt b/tsc_output.txt new file mode 100644 index 0000000000000000000000000000000000000000..9c34a6ee5b31ae8318f4df09f1c56ca0cd05575a GIT binary patch literal 43874 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'. +src/app/api/v1/auth/apple/route.test.ts(45,64): error TS2345: Argument of type 'string' is not assignable to parameter of type 'Promise'. +src/app/api/v1/auth/google/route.test.ts(40,63): error TS2345: Argument of type 'string' is not assignable to parameter of type 'Promise'. +src/app/api/v1/auth/google/route.test.ts(41,64): error TS2345: Argument of type 'string' is not assignable to parameter of type 'Promise'. +src/server/services/apple-oauth.service.spec.ts(69,7): error TS2554: Expected 2-4 arguments, but got 1. +src/server/services/auth.service.spec.ts(110,60): error TS2345: Argument of type 'null' is not assignable to parameter of type '{ id: string; firstName: string; lastName: string; email: string; passwordHash: string | null; avatarUrl: string | null; role: string | null; organizationName: string | null; status: "pending_verification" | "active" | "suspended"; ... 16 more ...; updatedAt: Date; }'. +src/server/services/auth.service.spec.ts(141,60): error TS2345: Argument of type 'null' is not assignable to parameter of type '{ id: string; firstName: string; lastName: string; email: string; passwordHash: string | null; avatarUrl: string | null; role: string | null; organizationName: string | null; status: "pending_verification" | "active" | "suspended"; ... 16 more ...; updatedAt: Date; }'. +src/server/services/auth.service.spec.ts(191,60): error TS2345: Argument of type 'null' is not assignable to parameter of type '{ id: string; firstName: string; lastName: string; email: string; passwordHash: string | null; avatarUrl: string | null; role: string | null; organizationName: string | null; status: "pending_verification" | "active" | "suspended"; ... 16 more ...; updatedAt: Date; }'. +src/server/services/auth.service.spec.ts(212,60): error TS2345: Argument of type 'null' is not assignable to parameter of type '{ id: string; firstName: string; lastName: string; email: string; passwordHash: string | null; avatarUrl: string | null; role: string | null; organizationName: string | null; status: "pending_verification" | "active" | "suspended"; ... 16 more ...; updatedAt: Date; }'. +src/server/services/auth.service.spec.ts(249,60): error TS2345: Argument of type 'null' is not assignable to parameter of type '{ id: string; firstName: string; lastName: string; email: string; passwordHash: string | null; avatarUrl: string | null; role: string | null; organizationName: string | null; status: "pending_verification" | "active" | "suspended"; ... 16 more ...; updatedAt: Date; }'. +src/server/services/auth.service.spec.ts(364,60): error TS2345: Argument of type 'null' is not assignable to parameter of type '{ id: string; firstName: string; lastName: string; email: string; passwordHash: string | null; avatarUrl: string | null; role: string | null; organizationName: string | null; status: "pending_verification" | "active" | "suspended"; ... 16 more ...; updatedAt: Date; }'. +src/server/services/email-verification.service.spec.ts(52,60): error TS2345: Argument of type 'null' is not assignable to parameter of type '{ id: string; firstName: string; lastName: string; email: string; passwordHash: string | null; avatarUrl: string | null; role: string | null; organizationName: string | null; status: "pending_verification" | "active" | "suspended"; ... 16 more ...; updatedAt: Date; }'. +src/server/services/fiat/flutterwave.provider.ts(197,7): error TS2322: Type 'string' is not assignable to type '"NGN"'. +src/server/services/google-oauth.service.ts(92,7): error TS2322: Type 'string | undefined' is not assignable to type 'string'. + Type 'undefined' is not assignable to type 'string'. +src/server/services/invitation.service.spec.ts(16,8): error TS2769: No overload matches this call. + Overload 1 of 2, '(value: { name: string | SQL | Placeholder; slug: string | SQL | Placeholder; id?: string | SQL | Placeholder | undefined; ... 14 more ...; deletedAt?: Date | ... 3 more ... | undefined; }): PgInsertBase<...>', gave the following error. + Argument of type '{ name: string; industry: string; }' is not assignable to parameter of type '{ name: string | SQL | Placeholder; slug: string | SQL | Placeholder; id?: string | SQL | Placeholder | undefined; ... 14 more ...; deletedAt?: Date | ... 3 more ... | undefined; }'. + Property 'slug' is missing in type '{ name: string; industry: string; }' but required in type '{ name: string | SQL | Placeholder; slug: string | SQL | Placeholder; id?: string | SQL | Placeholder | undefined; ... 14 more ...; deletedAt?: Date | ... 3 more ... | undefined; }'. + Overload 2 of 2, '(values: { name: string | SQL | Placeholder; slug: string | SQL | Placeholder; id?: string | SQL | Placeholder | undefined; ... 14 more ...; deletedAt?: Date | ... 3 more ... | undefined; }[]): PgInsertBase<...>', gave the following error. + Object literal may only specify known properties, and 'name' does not exist in type '{ name: string | SQL | Placeholder; slug: string | SQL | Placeholder; id?: string | SQL | Placeholder | undefined; ... 14 more ...; deletedAt?: Date | ... 3 more ... | undefined; }[]'. +src/server/services/invitation.service.spec.ts(70,9): error TS2322: Type 'Omit; ... 11 more ...; upda...' is not assignable to type 'Promise'. + Types of property 'then' are incompatible. + Type ', TResult2 = never>(onFulfilled?: ((value: QueryResult) => TResult1 | PromiseLike) | null | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ... | undefined) => Promise<...>' is not assignable to type '(onfulfilled?: ((value: void) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>'. + Types of parameters 'onFulfilled' and 'onfulfilled' are incompatible. + Types of parameters 'value' and 'value' are incompatible. + Type 'QueryResult' is not assignable to type 'void'. +src/server/services/invitation.service.spec.ts(89,9): error TS2322: Type 'Omit; ... 24 more ...; updatedAt...' is not assignable to type 'Promise'. + Types of property 'then' are incompatible. + Type ', TResult2 = never>(onFulfilled?: ((value: QueryResult) => TResult1 | PromiseLike) | null | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ... | undefined) => Promise<...>' is not assignable to type '(onfulfilled?: ((value: void) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>'. + Types of parameters 'onFulfilled' and 'onfulfilled' are incompatible. + Types of parameters 'value' and 'value' are incompatible. + Type 'QueryResult' is not assignable to type 'void'. +src/server/services/invitation.service.spec.ts(115,9): error TS2322: Type 'Omit; ... 11 more ...; upda...' is not assignable to type 'Promise'. + Types of property 'then' are incompatible. + Type ', TResult2 = never>(onFulfilled?: ((value: QueryResult) => TResult1 | PromiseLike) | null | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ... | undefined) => Promise<...>' is not assignable to type '(onfulfilled?: ((value: void) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>'. + Types of parameters 'onFulfilled' and 'onfulfilled' are incompatible. + Types of parameters 'value' and 'value' are incompatible. + Type 'QueryResult' is not assignable to type 'void'. +src/server/services/invitation.service.spec.ts(136,9): error TS2322: Type 'Omit; ... 11 more ...; upda...' is not assignable to type 'Promise'. + Types of property 'then' are incompatible. + Type ', TResult2 = never>(onFulfilled?: ((value: QueryResult) => TResult1 | PromiseLike) | null | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ... | undefined) => Promise<...>' is not assignable to type '(onfulfilled?: ((value: void) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>'. + Types of parameters 'onFulfilled' and 'onfulfilled' are incompatible. + Types of parameters 'value' and 'value' are incompatible. + Type 'QueryResult' is not assignable to type 'void'. +src/server/services/invitation.service.spec.ts(164,9): error TS2322: Type 'Omit; ... 11 more ...; upda...' is not assignable to type 'Promise'. + Types of property 'then' are incompatible. + Type ', TResult2 = never>(onFulfilled?: ((value: QueryResult) => TResult1 | PromiseLike) | null | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ... | undefined) => Promise<...>' is not assignable to type '(onfulfilled?: ((value: void) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>'. + Types of parameters 'onFulfilled' and 'onfulfilled' are incompatible. + Types of parameters 'value' and 'value' are incompatible. + Type 'QueryResult' is not assignable to type 'void'. +src/server/services/invitation.service.spec.ts(179,9): error TS2322: Type 'Omit; ... 24 more ...; updatedAt...' is not assignable to type 'Promise'. + Types of property 'then' are incompatible. + Type ', TResult2 = never>(onFulfilled?: ((value: QueryResult) => TResult1 | PromiseLike) | null | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ... | undefined) => Promise<...>' is not assignable to type '(onfulfilled?: ((value: void) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>'. + Types of parameters 'onFulfilled' and 'onfulfilled' are incompatible. + Types of parameters 'value' and 'value' are incompatible. + Type 'QueryResult' is not assignable to type 'void'. +src/server/services/invitation.service.spec.ts(206,9): error TS2322: Type 'Omit; ... 11 more ...; upda...' is not assignable to type 'Promise'. + Types of property 'then' are incompatible. + Type ', TResult2 = never>(onFulfilled?: ((value: QueryResult) => TResult1 | PromiseLike) | null | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ... | undefined) => Promise<...>' is not assignable to type '(onfulfilled?: ((value: void) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>'. + Types of parameters 'onFulfilled' and 'onfulfilled' are incompatible. + Types of parameters 'value' and 'value' are incompatible. + Type 'QueryResult' is not assignable to type 'void'. +src/server/services/invitation.service.spec.ts(227,9): error TS2322: Type 'Omit; ... 24 more ...; updatedAt...' is not assignable to type 'Promise'. + Types of property 'then' are incompatible. + Type ', TResult2 = never>(onFulfilled?: ((value: QueryResult) => TResult1 | PromiseLike) | null | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ... | undefined) => Promise<...>' is not assignable to type '(onfulfilled?: ((value: void) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>'. + Types of parameters 'onFulfilled' and 'onfulfilled' are incompatible. + Types of parameters 'value' and 'value' are incompatible. + Type 'QueryResult' is not assignable to type 'void'. +src/server/services/invitation.service.spec.ts(247,9): error TS2322: Type 'Omit; ... 11 more ...; upda...' is not assignable to type 'Promise'. + Types of property 'then' are incompatible. + Type ', TResult2 = never>(onFulfilled?: ((value: QueryResult) => TResult1 | PromiseLike) | null | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ... | undefined) => Promise<...>' is not assignable to type '(onfulfilled?: ((value: void) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>'. + Types of parameters 'onFulfilled' and 'onfulfilled' are incompatible. + Types of parameters 'value' and 'value' are incompatible. + Type 'QueryResult' is not assignable to type 'void'. +src/server/services/invitation.service.spec.ts(269,9): error TS2322: Type 'Omit; ... 11 more ...; upda...' is not assignable to type 'Promise'. + Types of property 'then' are incompatible. + Type ', TResult2 = never>(onFulfilled?: ((value: QueryResult) => TResult1 | PromiseLike) | null | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ... | undefined) => Promise<...>' is not assignable to type '(onfulfilled?: ((value: void) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>'. + Types of parameters 'onFulfilled' and 'onfulfilled' are incompatible. + Types of parameters 'value' and 'value' are incompatible. + Type 'QueryResult' is not assignable to type 'void'. +src/server/services/invitation.service.spec.ts(311,9): error TS2322: Type 'Omit; ... 11 more ...; upda...' is not assignable to type 'Promise'. + Types of property 'then' are incompatible. + Type ', TResult2 = never>(onFulfilled?: ((value: QueryResult) => TResult1 | PromiseLike) | null | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ... | undefined) => Promise<...>' is not assignable to type '(onfulfilled?: ((value: void) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>'. + Types of parameters 'onFulfilled' and 'onfulfilled' are incompatible. + Types of parameters 'value' and 'value' are incompatible. + Type 'QueryResult' is not assignable to type 'void'. +src/server/services/invitation.service.spec.ts(344,11): error TS2322: Type 'Omit; ... 11 more ...; upda...' is not assignable to type 'Promise'. + Types of property 'then' are incompatible. + Type ', TResult2 = never>(onFulfilled?: ((value: QueryResult) => TResult1 | PromiseLike) | null | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ... | undefined) => Promise<...>' is not assignable to type '(onfulfilled?: ((value: void) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>'. + Types of parameters 'onFulfilled' and 'onfulfilled' are incompatible. + Types of parameters 'value' and 'value' are incompatible. + Type 'QueryResult' is not assignable to type 'void'. +src/server/services/invitation.service.spec.ts(366,9): error TS2322: Type 'Omit; ... 11 more ...; upda...' is not assignable to type 'Promise'. + Types of property 'then' are incompatible. + Type ', TResult2 = never>(onFulfilled?: ((value: QueryResult) => TResult1 | PromiseLike) | null | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ... | undefined) => Promise<...>' is not assignable to type '(onfulfilled?: ((value: void) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>'. + Types of parameters 'onFulfilled' and 'onfulfilled' are incompatible. + Types of parameters 'value' and 'value' are incompatible. + Type 'QueryResult' is not assignable to type 'void'. +src/server/services/invitation.service.spec.ts(384,9): error TS2322: Type 'Omit; ... 11 more ...; upda...' is not assignable to type 'Promise'. + Types of property 'then' are incompatible. + Type ', TResult2 = never>(onFulfilled?: ((value: QueryResult) => TResult1 | PromiseLike) | null | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ... | undefined) => Promise<...>' is not assignable to type '(onfulfilled?: ((value: void) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>'. + Types of parameters 'onFulfilled' and 'onfulfilled' are incompatible. + Types of parameters 'value' and 'value' are incompatible. + Type 'QueryResult' is not assignable to type 'void'. +src/server/services/invitation.service.spec.ts(413,9): error TS2322: Type 'Omit; ... 11 more ...; upda...' is not assignable to type 'Promise'. + Types of property 'then' are incompatible. + Type ', TResult2 = never>(onFulfilled?: ((value: QueryResult) => TResult1 | PromiseLike) | null | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<...>) | ... 1 more ... | undefined) => Promise<...>' is not assignable to type '(onfulfilled?: ((value: void) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>'. + Types of parameters 'onFulfilled' and 'onfulfilled' are incompatible. + Types of parameters 'value' and 'value' are incompatible. + Type 'QueryResult' is not assignable to type 'void'. +src/server/services/user.service.spec.ts(80,44): error TS2345: Argument of type 'InsertChain' is not assignable to parameter of type 'PgInsertBuilder, NodePgQueryResultHKT, false>'. + Type 'InsertChain' is missing the following properties from type 'PgInsertBuilder, NodePgQueryResultHKT, false>': table, session, dialect, overridingSystemValue, select +src/server/services/user.service.spec.ts(140,57): error TS2352: Conversion of type '{ insert: Mock; }' to type 'PgTransaction' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. + Type '{ insert: Mock; }' is missing the following properties from type 'PgTransaction': schema, nestedIndex, rollback, setTransaction, and 14 more. +src/server/services/user.service.spec.ts(189,44): error TS2345: Argument of type 'InsertChain' is not assignable to parameter of type 'PgInsertBuilder, NodePgQueryResultHKT, false>'. + Type 'InsertChain' is missing the following properties from type 'PgInsertBuilder, NodePgQueryResultHKT, false>': table, session, dialect, overridingSystemValue, select +src/server/services/user.service.spec.ts(253,57): error TS2352: Conversion of type '{ insert: Mock; }' to type 'PgTransaction' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. + Type '{ insert: Mock; }' is missing the following properties from type 'PgTransaction': schema, nestedIndex, rollback, setTransaction, and 14 more. +src/server/services/user.service.spec.ts(307,57): error TS2352: Conversion of type '{ insert: Mock; update: Mock; }' to type 'PgTransaction' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. + Type '{ insert: Mock; update: Mock; }' is missing the following properties from type 'PgTransaction': schema, nestedIndex, rollback, setTransaction, and 13 more. +src/server/services/user.service.spec.ts(357,46): error TS2345: Argument of type 'InsertChain' is not assignable to parameter of type 'PgInsertBuilder, NodePgQueryResultHKT, false>'. + Type 'InsertChain' is missing the following properties from type 'PgInsertBuilder, NodePgQueryResultHKT, false>': table, session, dialect, overridingSystemValue, select +src/server/services/user.service.spec.ts(409,44): error TS2345: Argument of type 'SelectChain' is not assignable to parameter of type 'PgSelectBuilder'. + Type 'SelectChain' is missing the following properties from type 'PgSelectBuilder': fields, session, dialect, withList, distinct +src/server/services/user.service.spec.ts(431,44): error TS2345: Argument of type 'SelectChain' is not assignable to parameter of type 'PgSelectBuilder'. + Type 'SelectChain' is missing the following properties from type 'PgSelectBuilder': fields, session, dialect, withList, distinct From b9683c5ba23cdc543465f931bc554eafb5f19610 Mon Sep 17 00:00:00 2001 From: Agbasimere Date: Sun, 29 Mar 2026 20:06:40 +0100 Subject: [PATCH 5/6] fix: add eslint-disable for no-explicit-any in test files and blockchain service --- conflicts.txt | 3 --- src/server/services/auth.service.spec.ts | 1 + src/server/services/blockchain.service.ts | 1 + src/server/services/invitation.service.spec.ts | 1 + src/server/services/user.service.spec.ts | 1 + test_results.txt | Bin 67510 -> 0 bytes 6 files changed, 4 insertions(+), 3 deletions(-) delete mode 100644 conflicts.txt delete mode 100644 test_results.txt diff --git a/conflicts.txt b/conflicts.txt deleted file mode 100644 index 11cdd2b4..00000000 --- a/conflicts.txt +++ /dev/null @@ -1,3 +0,0 @@ -src/app/api/v1/auth/logout/route.ts -src/server/services/user.service.ts -src/server/swagger-config.ts diff --git a/src/server/services/auth.service.spec.ts b/src/server/services/auth.service.spec.ts index 90c47a9e..6d70ef83 100644 --- a/src/server/services/auth.service.spec.ts +++ b/src/server/services/auth.service.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { AuthService } from "./auth.service"; import { EmailVerificationService } from "./email-verification.service"; diff --git a/src/server/services/blockchain.service.ts b/src/server/services/blockchain.service.ts index b68100cb..88821b23 100644 --- a/src/server/services/blockchain.service.ts +++ b/src/server/services/blockchain.service.ts @@ -543,6 +543,7 @@ export class BlockchainService { // eslint-disable-next-line @typescript-eslint/no-explicit-any const data = (await response.json()) as any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any const ledgerRecord = data._embedded ? (data._embedded as any).records?.[0] : data; if (!ledgerRecord?.closed_at || ledgerRecord.sequence == null) { diff --git a/src/server/services/invitation.service.spec.ts b/src/server/services/invitation.service.spec.ts index 825ee875..5fe25909 100644 --- a/src/server/services/invitation.service.spec.ts +++ b/src/server/services/invitation.service.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { invitationService } from "./invitation.service"; import { db } from "../db"; diff --git a/src/server/services/user.service.spec.ts b/src/server/services/user.service.spec.ts index 70b51679..69233719 100644 --- a/src/server/services/user.service.spec.ts +++ b/src/server/services/user.service.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { describe, it, expect, beforeEach, vi } from "vitest"; import { UserService } from "./user.service"; import { db, users } from "../db"; diff --git a/test_results.txt b/test_results.txt deleted file mode 100644 index 445be10c011d8fe3d0c357aa72396871f40c9431..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67510 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 Jr-CW3{|BFefD-@! From 6b2a39f8598baaf69bffd6c58878dbca6d38a06a Mon Sep 17 00:00:00 2001 From: Agbasimere Date: Sun, 29 Mar 2026 20:37:02 +0100 Subject: [PATCH 6/6] fix: resolve all type-check errors in tests and services --- src/app/api/v1/auth/apple/route.test.ts | 5 +++-- src/app/api/v1/auth/google/route.test.ts | 5 +++-- .../services/apple-oauth.service.spec.ts | 18 +++++++++++---- .../email-verification.service.spec.ts | 22 +++++++++---------- .../services/fiat/flutterwave.provider.ts | 2 +- src/server/services/google-oauth.service.ts | 4 ++-- src/server/services/user.service.spec.ts | 8 +++---- 7 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src/app/api/v1/auth/apple/route.test.ts b/src/app/api/v1/auth/apple/route.test.ts index 9966ba59..aab95de8 100644 --- a/src/app/api/v1/auth/apple/route.test.ts +++ b/src/app/api/v1/auth/apple/route.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { describe, it, expect, beforeEach, vi } from "vitest"; import { POST } from "./route"; import { NextRequest } from "next/server"; @@ -41,8 +42,8 @@ describe("POST /api/v1/auth/apple", () => { vi.mocked(OAuthUserProvisioningService.provisionUser).mockResolvedValue( mockUser as any, ); - vi.mocked(JWTService.generateAccessToken).mockReturnValue("access-token"); - vi.mocked(JWTService.generateRefreshToken).mockReturnValue("refresh-token"); + vi.mocked(JWTService.generateAccessToken).mockResolvedValue("access-token"); + vi.mocked(JWTService.generateRefreshToken).mockResolvedValue("refresh-token"); vi.mocked(SessionService.createSession).mockResolvedValue({ id: "sess-1", userId: "u1", diff --git a/src/app/api/v1/auth/google/route.test.ts b/src/app/api/v1/auth/google/route.test.ts index 05201022..1cd465b2 100644 --- a/src/app/api/v1/auth/google/route.test.ts +++ b/src/app/api/v1/auth/google/route.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { describe, it, expect, beforeEach, vi } from "vitest"; import { POST } from "./route"; import { NextRequest } from "next/server"; @@ -37,8 +38,8 @@ describe("POST /api/v1/auth/google", () => { vi.mocked(OAuthUserProvisioningService.provisionUser).mockResolvedValue( mockUser as any, ); - vi.mocked(JWTService.generateAccessToken).mockReturnValue("access-token"); - vi.mocked(JWTService.generateRefreshToken).mockReturnValue("refresh-token"); + vi.mocked(JWTService.generateAccessToken).mockResolvedValue("access-token"); + vi.mocked(JWTService.generateRefreshToken).mockResolvedValue("refresh-token"); vi.mocked(SessionService.createSession).mockResolvedValue({ id: "sess-1", userId: "u1", diff --git a/src/server/services/apple-oauth.service.spec.ts b/src/server/services/apple-oauth.service.spec.ts index d70b2d7c..9554b730 100644 --- a/src/server/services/apple-oauth.service.spec.ts +++ b/src/server/services/apple-oauth.service.spec.ts @@ -13,15 +13,25 @@ vi.mock("jose", () => ({ jwtVerify: vi.fn(), errors: { JWTExpired: class extends Error { - constructor(message: string) { + payload: unknown; + claim: string; + reason: string; + constructor(message: string, payload: unknown, claim?: string, reason?: string) { super(message); + this.payload = payload; + this.claim = claim || ''; + this.reason = reason || ''; } }, JWTClaimValidationFailed: class extends Error { + payload: unknown; claim: string; - constructor(message: string, _payload: unknown, claim: string) { + reason: string; + constructor(message: string, payload: unknown, claim?: string, reason?: string) { super(message); - this.claim = claim; + this.payload = payload; + this.claim = claim || ''; + this.reason = reason || ''; } }, }, @@ -66,7 +76,7 @@ describe("AppleOAuthService", () => { it("should throw TokenExpiredError when token is expired", async () => { vi.mocked(jose.jwtVerify).mockRejectedValue( - new jose.errors.JWTExpired("Token has expired"), + new jose.errors.JWTExpired("Token has expired", {}), ); await expect(AppleOAuthService.verifyIdToken(mockIdToken)).rejects.toThrow( diff --git a/src/server/services/email-verification.service.spec.ts b/src/server/services/email-verification.service.spec.ts index 5ad1c6c6..52fa6e0f 100644 --- a/src/server/services/email-verification.service.spec.ts +++ b/src/server/services/email-verification.service.spec.ts @@ -1,10 +1,10 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { EmailVerificationService } from "./email-verification.service"; import { UserService } from "./user.service"; import { OTPService } from "./otp.service"; import { db, emailVerifications, users } from "../db"; import { NotFoundError, BadRequestError, ForbiddenError } from "../utils/errors"; -type UserLookupResult = Awaited>; vi.mock("./user.service"); vi.mock("./otp.service"); @@ -49,7 +49,7 @@ describe("EmailVerificationService", () => { describe("verifyEmail", () => { it("should throw NotFoundError if user does not exist", async () => { - vi.mocked(UserService.findByEmail).mockResolvedValue(null); + vi.mocked(UserService.findByEmail).mockResolvedValue(null as any); const promise = EmailVerificationService.verifyEmail("nonexistent@example.com", "123456"); await expect(promise).rejects.toThrowError(NotFoundError); await expect(promise).rejects.toThrowError("User not found"); @@ -58,7 +58,7 @@ describe("EmailVerificationService", () => { it("should throw BadRequestError if user is already verified", async () => { const activeUser = { ...mockUser, status: "active" as const }; vi.mocked(UserService.findByEmail).mockResolvedValue( - activeUser as UserLookupResult, + activeUser as any, ); await expect( @@ -71,7 +71,7 @@ describe("EmailVerificationService", () => { it("should throw NotFoundError if no verification record exists", async () => { vi.mocked(UserService.findByEmail).mockResolvedValue( - mockUser as UserLookupResult, + mockUser as any, ); const mockSelect = vi.fn().mockReturnValue({ @@ -95,7 +95,7 @@ describe("EmailVerificationService", () => { it("should throw ForbiddenError if max attempts exceeded", async () => { vi.mocked(UserService.findByEmail).mockResolvedValue( - mockUser as UserLookupResult, + mockUser as any, ); const lockedRecord = { ...mockVerificationRecord, attempts: 5 }; @@ -120,7 +120,7 @@ describe("EmailVerificationService", () => { it("should throw BadRequestError if OTP is expired", async () => { vi.mocked(UserService.findByEmail).mockResolvedValue( - mockUser as UserLookupResult, + mockUser as any, ); const expiredRecord = { @@ -148,7 +148,7 @@ describe("EmailVerificationService", () => { it("should increment attempts and throw BadRequestError for invalid OTP", async () => { vi.mocked(UserService.findByEmail).mockResolvedValue( - mockUser as UserLookupResult, + mockUser as any, ); vi.mocked(OTPService.verifyOTP).mockResolvedValue(false); @@ -182,7 +182,7 @@ describe("EmailVerificationService", () => { it("should throw ForbiddenError on 5th failed attempt", async () => { vi.mocked(UserService.findByEmail).mockResolvedValue( - mockUser as UserLookupResult, + mockUser as any, ); vi.mocked(OTPService.verifyOTP).mockResolvedValue(false); @@ -215,7 +215,7 @@ describe("EmailVerificationService", () => { it("should successfully verify email with valid OTP", async () => { vi.mocked(UserService.findByEmail).mockResolvedValue( - mockUser as UserLookupResult, + mockUser as any, ); vi.mocked(OTPService.verifyOTP).mockResolvedValue(true); @@ -259,7 +259,7 @@ describe("EmailVerificationService", () => { it("should call OTPService.verifyOTP with correct parameters", async () => { vi.mocked(UserService.findByEmail).mockResolvedValue( - mockUser as UserLookupResult, + mockUser as any, ); vi.mocked(OTPService.verifyOTP).mockResolvedValue(true); @@ -295,7 +295,7 @@ describe("EmailVerificationService", () => { it("should show singular 'attempt' when 1 attempt remaining", async () => { vi.mocked(UserService.findByEmail).mockResolvedValue( - mockUser as UserLookupResult, + mockUser as any, ); vi.mocked(OTPService.verifyOTP).mockResolvedValue(false); diff --git a/src/server/services/fiat/flutterwave.provider.ts b/src/server/services/fiat/flutterwave.provider.ts index fd73c1df..94a37368 100644 --- a/src/server/services/fiat/flutterwave.provider.ts +++ b/src/server/services/fiat/flutterwave.provider.ts @@ -194,7 +194,7 @@ export class FlutterwaveProvider implements PaymentProvider { providerReference: String(data.data.id ?? reference), status: FlutterwaveProvider.mapVerificationStatus(data.data.status), amount: Number(data.data.amount ?? 0), - currency: data.data.currency ?? "NGN", + currency: (data.data.currency ?? "NGN") as "NGN", paidAt: data.data.created_at, raw: data.data, }; diff --git a/src/server/services/google-oauth.service.ts b/src/server/services/google-oauth.service.ts index f7a17409..1a9678f0 100644 --- a/src/server/services/google-oauth.service.ts +++ b/src/server/services/google-oauth.service.ts @@ -89,10 +89,10 @@ export class GoogleOAuthService { const lastName = nameParts.slice(1).join(" ") || payload.family_name || ""; return { - email: payload.email, + email: payload.email!, firstName: firstName || "User", lastName: lastName || "", - oauthId: payload.sub, + oauthId: payload.sub!, }; } } diff --git a/src/server/services/user.service.spec.ts b/src/server/services/user.service.spec.ts index 69233719..d0f41809 100644 --- a/src/server/services/user.service.spec.ts +++ b/src/server/services/user.service.spec.ts @@ -189,7 +189,7 @@ describe("UserService", () => { vi.mocked(db.insert).mockReturnValue({ values: mockValues, - } as InsertChain); + } as any); const result = await UserService.create(userData); @@ -357,7 +357,7 @@ describe("UserService", () => { vi.mocked(db.insert).mockReturnValue({ values: mockValues, - } as InsertChain); + } as any); await UserService.create({ firstName: "Test", @@ -409,7 +409,7 @@ describe("UserService", () => { vi.mocked(db.select).mockReturnValue({ from: mockFrom, - } as SelectChain); + } as any); const result = await UserService.findByEmail("TEST@EXAMPLE.COM "); @@ -431,7 +431,7 @@ describe("UserService", () => { vi.mocked(db.select).mockReturnValue({ from: mockFrom, - } as SelectChain); + } as any); const result = await UserService.findByEmail("nonexistent@example.com");