From d5b65a22d789ba45e53561f460c3a5a6017dd84e Mon Sep 17 00:00:00 2001 From: JaiPannu-IITI Date: Wed, 26 Feb 2025 21:57:15 +0530 Subject: [PATCH 1/3] Preserved Admin --- scripts/dbManagement/addSampleData.ts | 17 +---- scripts/dbManagement/helpers.ts | 74 +++++-------------- scripts/dbManagement/resetData.ts | 11 +-- .../dbManagement/addSampleData.test.ts | 19 ----- test/scripts/dbManagement/resetDB.test.ts | 7 -- 5 files changed, 22 insertions(+), 106 deletions(-) diff --git a/scripts/dbManagement/addSampleData.ts b/scripts/dbManagement/addSampleData.ts index 6389ac7ca97..899b9cb8219 100644 --- a/scripts/dbManagement/addSampleData.ts +++ b/scripts/dbManagement/addSampleData.ts @@ -1,11 +1,6 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; -import { - disconnect, - ensureAdministratorExists, - insertCollections, - pingDB, -} from "./helpers"; +import { disconnect, insertCollections, pingDB } from "./helpers"; type Collection = | "users" @@ -35,16 +30,6 @@ export async function main(): Promise { } catch (error: unknown) { throw new Error(`Database connection failed: ${error}`); } - try { - await ensureAdministratorExists(); - console.log("\x1b[32mSuccess:\x1b[0m Administrator setup complete\n"); - } catch (error: unknown) { - console.error("\nError: Administrator creation failed", error); - throw new Error( - "\n\x1b[31mAdministrator access may be lost, try reimporting sample DB to restore access\x1b[0m\n", - ); - } - try { await insertCollections(collections); console.log("\n\x1b[32mSuccess:\x1b[0m Sample Data added to the database"); diff --git a/scripts/dbManagement/helpers.ts b/scripts/dbManagement/helpers.ts index 83fdd26605e..731f2e21023 100644 --- a/scripts/dbManagement/helpers.ts +++ b/scripts/dbManagement/helpers.ts @@ -68,21 +68,34 @@ export async function askUserToContinue(question: string): Promise { * Clears all tables in the database. */ export async function formatDatabase(): Promise { + const adminEmail = envConfig.API_ADMINISTRATOR_USER_EMAIL_ADDRESS; + type TableRow = { tablename: string }; try { await db.transaction(async (tx) => { const tables: TableRow[] = await tx.execute(sql` - SELECT tablename FROM pg_catalog.pg_tables - WHERE schemaname = 'public' - `); - const tableNames = tables.map((row) => sql.identifier(row.tablename)); + SELECT tablename FROM pg_catalog.pg_tables + WHERE schemaname = 'public' + `); + const tableNames = tables + .map((row) => row.tablename) + .filter((name) => name !== "users"); if (tableNames.length > 0) { - await tx.execute( - sql`TRUNCATE TABLE ${sql.join(tableNames, sql`, `)} RESTART IDENTITY CASCADE;`, - ); + await tx.execute(sql` + TRUNCATE TABLE ${sql.join( + tableNames.map((table) => sql.identifier(table)), + sql`, `, + )} + RESTART IDENTITY CASCADE; + `); } + + await tx.execute(sql` + DELETE FROM "users" + WHERE "email_address" != ${adminEmail}; + `); }); return true; @@ -91,53 +104,6 @@ export async function formatDatabase(): Promise { } } -export async function ensureAdministratorExists(): Promise { - const email = envConfig.API_ADMINISTRATOR_USER_EMAIL_ADDRESS; - - if (!email) { - throw new Error("API_ADMINISTRATOR_USER_EMAIL_ADDRESS is not defined."); - } - - const existingUser = await db.query.usersTable.findFirst({ - columns: { id: true, role: true }, - where: (fields, operators) => operators.eq(fields.emailAddress, email), - }); - - if (existingUser) { - if (existingUser.role !== "administrator") { - await db - .update(schema.usersTable) - .set({ role: "administrator" }) - .where(sql`email_address = ${email}`); - console.log( - "\x1b[33mRole Change: Updated user role to administrator\x1b[0m\n", - ); - } else { - console.log("\x1b[32mFound:\x1b[0m Administrator user already exists"); - } - return true; - } - - const userId = uuidv7(); - const password = envConfig.API_ADMINISTRATOR_USER_PASSWORD; - if (!password) { - throw new Error("API_ADMINISTRATOR_USER_PASSWORD is not defined."); - } - const passwordHash = await hash(password); - - await db.insert(schema.usersTable).values({ - id: userId, - emailAddress: email, - name: envConfig.API_ADMINISTRATOR_USER_NAME || "", - passwordHash, - role: "administrator", - isEmailAddressVerified: true, - creatorId: userId, - }); - - return true; -} - export async function emptyMinioBucket(): Promise { try { // List all objects in the bucket. diff --git a/scripts/dbManagement/resetData.ts b/scripts/dbManagement/resetData.ts index 5cba46be687..47c4a8bb9bc 100644 --- a/scripts/dbManagement/resetData.ts +++ b/scripts/dbManagement/resetData.ts @@ -4,7 +4,6 @@ import { askUserToContinue, disconnect, emptyMinioBucket, - ensureAdministratorExists, formatDatabase, pingDB, } from "./helpers"; @@ -26,6 +25,7 @@ export async function main(): Promise { try { await formatDatabase(); console.log("\n\x1b[32mSuccess:\x1b[0m Database formatted successfully"); + console.log("\x1b[32mSuccess:\x1b[0m Administrator access preserved\n"); } catch (error: unknown) { console.error( "\n\x1b[31mError: Database formatting failed\n\x1b[0m", @@ -43,15 +43,6 @@ export async function main(): Promise { error, ); } - try { - await ensureAdministratorExists(); - console.log("\x1b[32mSuccess:\x1b[0m Administrator access restored\n"); - } catch (error: unknown) { - console.error("\nError: Administrator creation failed", error); - console.error( - "\n\x1b[31mAdministrator access may be lost, try reformatting DB to restore access\x1b[0m\n", - ); - } } else { console.log("Operation cancelled"); } diff --git a/test/scripts/dbManagement/addSampleData.test.ts b/test/scripts/dbManagement/addSampleData.test.ts index bfbf2521bc3..9609ddf25c3 100644 --- a/test/scripts/dbManagement/addSampleData.test.ts +++ b/test/scripts/dbManagement/addSampleData.test.ts @@ -10,9 +10,6 @@ suite("addSampleData main function tests", () => { test("should execute all operations successfully", async () => { // Arrange: simulate successful operations. const pingDBSpy = vi.spyOn(helpers, "pingDB").mockResolvedValue(true); - const ensureAdminSpy = vi - .spyOn(helpers, "ensureAdministratorExists") - .mockResolvedValue(true); const insertCollectionsSpy = vi .spyOn(helpers, "insertCollections") .mockResolvedValue(true); @@ -25,7 +22,6 @@ suite("addSampleData main function tests", () => { // Assert: verify that each helper was called as expected. expect(pingDBSpy).toHaveBeenCalled(); - expect(ensureAdminSpy).toHaveBeenCalled(); expect(insertCollectionsSpy).toHaveBeenCalledWith([ "users", "organizations", @@ -60,24 +56,9 @@ suite("addSampleData main function tests", () => { ); }); - test("should throw an error when ensureAdministratorExists fails", async () => { - // Arrange: pingDB succeeds, but ensureAdministratorExists fails. - vi.spyOn(helpers, "pingDB").mockResolvedValue(true); - const errorMsg = "admin error"; - vi.spyOn(helpers, "ensureAdministratorExists").mockRejectedValue( - new Error(errorMsg), - ); - - // Act & Assert: main() should throw the specific error message. - await expect(mainModule.main()).rejects.toThrow( - "\n\x1b[31mAdministrator access may be lost, try reimporting sample DB to restore access\x1b[0m\n", - ); - }); - test("should throw an error when insertCollections fails", async () => { // Arrange: pingDB and ensureAdministratorExists succeed, but insertCollections fails. vi.spyOn(helpers, "pingDB").mockResolvedValue(true); - vi.spyOn(helpers, "ensureAdministratorExists").mockResolvedValue(true); const errorMsg = "insert error"; vi.spyOn(helpers, "insertCollections").mockRejectedValue( new Error(errorMsg), diff --git a/test/scripts/dbManagement/resetDB.test.ts b/test/scripts/dbManagement/resetDB.test.ts index 4c4ca99e09a..8ac66c747c5 100644 --- a/test/scripts/dbManagement/resetDB.test.ts +++ b/test/scripts/dbManagement/resetDB.test.ts @@ -43,9 +43,6 @@ suite("resetData main function tests", () => { vi.spyOn(helpers, "emptyMinioBucket").mockRejectedValue( new Error("minio error"), ); - vi.spyOn(helpers, "ensureAdministratorExists").mockRejectedValue( - new Error("admin error"), - ); const consoleErrorSpy = vi .spyOn(console, "error") @@ -92,7 +89,6 @@ suite("resetData main function tests", () => { vi.spyOn(helpers, "pingDB").mockResolvedValue(true); vi.spyOn(helpers, "formatDatabase").mockResolvedValue(true); vi.spyOn(helpers, "emptyMinioBucket").mockResolvedValue(true); - vi.spyOn(helpers, "ensureAdministratorExists").mockResolvedValue(true); const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {}); @@ -109,8 +105,5 @@ suite("resetData main function tests", () => { expect(consoleLogSpy).toHaveBeenCalledWith( expect.stringContaining("Bucket formatted successfully"), ); - expect(consoleLogSpy).toHaveBeenCalledWith( - expect.stringContaining("Administrator access restored"), - ); }); }); From b08ad21f410c31889c4bb5599c2e20286301324b Mon Sep 17 00:00:00 2001 From: JaiPannu-IITI Date: Wed, 26 Feb 2025 21:57:53 +0530 Subject: [PATCH 2/3] clean --- scripts/dbManagement/helpers.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/dbManagement/helpers.ts b/scripts/dbManagement/helpers.ts index 731f2e21023..8dcd157c7b9 100644 --- a/scripts/dbManagement/helpers.ts +++ b/scripts/dbManagement/helpers.ts @@ -2,7 +2,6 @@ import fs from "node:fs/promises"; import path from "node:path"; import readline from "node:readline"; import { fileURLToPath } from "node:url"; -import { hash } from "@node-rs/argon2"; import { sql } from "drizzle-orm"; import type { AnyPgColumn, PgTable } from "drizzle-orm/pg-core"; import { drizzle } from "drizzle-orm/postgres-js"; @@ -15,7 +14,6 @@ import { envConfigSchema, envSchemaAjv, } from "src/envConfigSchema"; -import { uuidv7 } from "uuidv7"; const envConfig = envSchema({ ajv: envSchemaAjv, From 13d618fb60d80a7ff59d40929ca702b53abd3dc0 Mon Sep 17 00:00:00 2001 From: JaiPannu-IITI Date: Wed, 26 Feb 2025 16:55:35 +0000 Subject: [PATCH 3/3] final --- scripts/dbManagement/helpers.ts | 16 +++++++++++----- scripts/dbManagement/resetData.ts | 3 +-- test/scripts/dbManagement/addSampleData.test.ts | 5 +---- test/scripts/dbManagement/resetDB.test.ts | 14 +------------- 4 files changed, 14 insertions(+), 24 deletions(-) diff --git a/scripts/dbManagement/helpers.ts b/scripts/dbManagement/helpers.ts index 8dcd157c7b9..523414a15fb 100644 --- a/scripts/dbManagement/helpers.ts +++ b/scripts/dbManagement/helpers.ts @@ -68,8 +68,14 @@ export async function askUserToContinue(question: string): Promise { export async function formatDatabase(): Promise { const adminEmail = envConfig.API_ADMINISTRATOR_USER_EMAIL_ADDRESS; - type TableRow = { tablename: string }; + if (!adminEmail) { + throw new Error( + "Missing adminEmail environment variable. Aborting to prevent accidental deletion of all users.", + ); + } + type TableRow = { tablename: string }; + const USERS_TABLE = "users"; try { await db.transaction(async (tx) => { const tables: TableRow[] = await tx.execute(sql` @@ -78,7 +84,7 @@ export async function formatDatabase(): Promise { `); const tableNames = tables .map((row) => row.tablename) - .filter((name) => name !== "users"); + .filter((name) => name !== USERS_TABLE); if (tableNames.length > 0) { await tx.execute(sql` @@ -91,9 +97,9 @@ export async function formatDatabase(): Promise { } await tx.execute(sql` - DELETE FROM "users" - WHERE "email_address" != ${adminEmail}; - `); + DELETE FROM ${sql.identifier(USERS_TABLE)} + WHERE email != ${adminEmail}; + `); }); return true; diff --git a/scripts/dbManagement/resetData.ts b/scripts/dbManagement/resetData.ts index 47c4a8bb9bc..65e1f5f3882 100644 --- a/scripts/dbManagement/resetData.ts +++ b/scripts/dbManagement/resetData.ts @@ -25,14 +25,13 @@ export async function main(): Promise { try { await formatDatabase(); console.log("\n\x1b[32mSuccess:\x1b[0m Database formatted successfully"); - console.log("\x1b[32mSuccess:\x1b[0m Administrator access preserved\n"); + console.log("\x1b[32mSuccess:\x1b[0m Administrator preserved\n"); } catch (error: unknown) { console.error( "\n\x1b[31mError: Database formatting failed\n\x1b[0m", error, ); console.error("\n\x1b[33mRolled back to previous state\x1b[0m"); - console.error("\n\x1b[33mPreserving administrator access\x1b[0m"); } try { await emptyMinioBucket(); diff --git a/test/scripts/dbManagement/addSampleData.test.ts b/test/scripts/dbManagement/addSampleData.test.ts index 9609ddf25c3..791961886a3 100644 --- a/test/scripts/dbManagement/addSampleData.test.ts +++ b/test/scripts/dbManagement/addSampleData.test.ts @@ -37,9 +37,6 @@ suite("addSampleData main function tests", () => { expect(consoleLogSpy).toHaveBeenCalledWith( expect.stringContaining("Database connected successfully"), ); - expect(consoleLogSpy).toHaveBeenCalledWith( - expect.stringContaining("Administrator setup complete"), - ); expect(consoleLogSpy).toHaveBeenCalledWith( expect.stringContaining("Sample Data added to the database"), ); @@ -57,7 +54,7 @@ suite("addSampleData main function tests", () => { }); test("should throw an error when insertCollections fails", async () => { - // Arrange: pingDB and ensureAdministratorExists succeed, but insertCollections fails. + // Arrange: pingDB succeed, but insertCollections fails. vi.spyOn(helpers, "pingDB").mockResolvedValue(true); const errorMsg = "insert error"; vi.spyOn(helpers, "insertCollections").mockRejectedValue( diff --git a/test/scripts/dbManagement/resetDB.test.ts b/test/scripts/dbManagement/resetDB.test.ts index 8ac66c747c5..7f45202657d 100644 --- a/test/scripts/dbManagement/resetDB.test.ts +++ b/test/scripts/dbManagement/resetDB.test.ts @@ -32,7 +32,7 @@ suite("resetData main function tests", () => { ); }); - test("should log errors for failing formatDatabase, emptyMinioBucket, and ensureAdministratorExists, but not throw", async () => { + test("should log errors for failing formatDatabase, emptyMinioBucket but not throw", async () => { // Arrange: simulate user confirming and pingDB succeeding. vi.spyOn(helpers, "askUserToContinue").mockResolvedValue(true); vi.spyOn(helpers, "pingDB").mockResolvedValue(true); @@ -65,22 +65,10 @@ suite("resetData main function tests", () => { expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringContaining("Rolled back to previous state"), ); - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining("Preserving administrator access"), - ); expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringContaining("Error: Bucket formatting failed"), expect.any(Error), ); - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining("Error: Administrator creation failed"), - expect.any(Error), - ); - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining( - "Administrator access may be lost, try reformatting DB to restore access", - ), - ); }); test("should log success messages when all operations succeed", async () => {