From 4368c3cbbceabe9de8dadede7f8adf6dcabd3737 Mon Sep 17 00:00:00 2001 From: Daniel Pan <57362494+daniel-panhead@users.noreply.github.com> Date: Sat, 10 Feb 2024 22:54:04 -0800 Subject: [PATCH 01/11] Finish implementing ItemIndividualQuery-specific functions --- src/db/queries/ItemIndividualQuery.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/db/queries/ItemIndividualQuery.ts b/src/db/queries/ItemIndividualQuery.ts index b5e3801..925a278 100644 --- a/src/db/queries/ItemIndividualQuery.ts +++ b/src/db/queries/ItemIndividualQuery.ts @@ -74,8 +74,12 @@ const itemIndividualQueries = { * @param category Category to search within * @returns Promise resolving to all items in the table with the specified category */ - async readAllFromCategory(category: Category): Promise { - throw new Error("Method not implemented."); + async readAllFromCategory(category: Category): Promise { + const queryResponse = await DB.query( + "SELECT * FROM item_individual WHERE category=$1", + [category] + ); + return queryResponse.rows; } }; From 3a869e78726677f06e3d298e2ac491aff861d125 Mon Sep 17 00:00:00 2001 From: Daniel Pan <57362494+daniel-panhead@users.noreply.github.com> Date: Sat, 10 Feb 2024 23:10:59 -0800 Subject: [PATCH 02/11] Implement all ReimbursementItemBoxQuery functions --- src/db/queries/ReimbursementItemBoxQuery.ts | 69 ++++++++++++++++--- test/db/queries/CompositeCrudQueryable.ts | 2 +- .../queries/ReimbursementItemBoxQuery.spec.ts | 2 +- 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/db/queries/ReimbursementItemBoxQuery.ts b/src/db/queries/ReimbursementItemBoxQuery.ts index bebc475..f268a94 100644 --- a/src/db/queries/ReimbursementItemBoxQuery.ts +++ b/src/db/queries/ReimbursementItemBoxQuery.ts @@ -3,6 +3,7 @@ import {ReimbursementId} from "../../types/db/public/Reimbursement"; import ReimbursementItemBox, {ReimbursementItemBoxInitializer, ReimbursementItemBoxMutator} from "../../types/db/public/ReimbursementItemBox"; import {CompositeCrudQueryable} from "../Queryable"; +import * as DB from "../../db/DB"; const compositeCrudQueries: CompositeCrudQueryable = { async create(object: ReimbursementItemBoxInitializer): Promise { - throw new Error("Method not implemented."); + // Object.keys and Object.values return things in the same order so this is safe + const keys = Object.keys(object); + const values = Object.values(object); + + const queryResponse = await DB.query( + `INSERT INTO reimbursement_item_box (${keys.join(",")})` + + `VALUES (${keys.map((prop, i) => `$${i + 1}`).join(",")})` + + "RETURNING *", + values + ); + if (queryResponse.rows.length === 1) { + return queryResponse.rows[0]; + } else { + return null; + } }, async read(reimbursementId: ReimbursementId, itemBoxId: ItemBoxId): Promise { - throw new Error("Method not implemented."); + const queryResponse = await DB.query( + "SELECT * FROM reimbursement_item_box WHERE reimbursement_id=$1 AND item_box_id=$2", + [reimbursementId, itemBoxId] + ); + if (queryResponse.rows.length === 1) { + return queryResponse.rows[0]; + } else { + return null; + } }, async readAll(): Promise { - throw new Error("Method not implemented."); + const queryResponse = await DB.query("SELECT * FROM reimbursement_item_box"); + return queryResponse.rows; }, async update( reimbursementId: ReimbursementId, - pk2: ItemBoxId, mutateObject: ReimbursementItemBoxMutator + itemBoxId: ItemBoxId, mutateObject: ReimbursementItemBoxMutator ): Promise { - throw new Error("Method not implemented."); + if (Object.keys(mutateObject).length === 0) { + return null; + } + + // Use i+3 for parameter so that $1 and $2 are reserved for the PKs + const keys = Object.keys(mutateObject).map((prop, i) => `${prop}=$${i + 3}`); + const queryResponse = await DB.query( + `UPDATE reimbursement_item_box SET ${keys.join(",")}` + + " WHERE reimbursement_id=$1 AND item_box_id=$2 RETURNING *", + [reimbursementId, itemBoxId, ...Object.values(mutateObject)] + ); + if (queryResponse.rows.length === 1) { + return queryResponse.rows[0]; + } else { + return null; + } }, - async delete(reimbursementId: ReimbursementId, pk2: ItemBoxId): Promise { - throw new Error("Method not implemented."); + async delete(reimbursementId: ReimbursementId, itemBoxId: ItemBoxId): Promise { + const queryResponse = await DB.query( + "DELETE FROM reimbursement_item_box WHERE reimbursement_id=$1 AND item_box_id=$2", + [reimbursementId, itemBoxId] + ); + return queryResponse.rowCount === 1; } }; @@ -42,7 +85,11 @@ const reimbursementItemBoxQueries = { * @returns Promise resolving to array of ReimbursementItemBoxes linked to the given itemBoxId */ async readAllFromItemBox(itemBoxId: ItemBoxId): Promise { - throw new Error("Method not implemented."); + const queryResponse = await DB.query( + "SELECT * FROM reimbursement_item_box WHERE item_box_id=$1", + [itemBoxId] + ); + return queryResponse.rows; }, /** @@ -51,7 +98,11 @@ const reimbursementItemBoxQueries = { * @returns Promise resolving to array of ReimbursementItemBoxes linked to the given reimbursementId */ async readAllFromReimbursement(reimbursementId: ReimbursementId): Promise { - throw new Error("Method not implemented."); + const queryResponse = await DB.query( + "SELECT * FROM reimbursement_item_box WHERE reimbursement_id=$1", + [reimbursementId] + ); + return queryResponse.rows; } }; diff --git a/test/db/queries/CompositeCrudQueryable.ts b/test/db/queries/CompositeCrudQueryable.ts index 354981b..7a3bdb9 100644 --- a/test/db/queries/CompositeCrudQueryable.ts +++ b/test/db/queries/CompositeCrudQueryable.ts @@ -36,7 +36,7 @@ export const testRead = ( const {testId1, testId2, testQueryable} = testProps; describe("read()", () => { it("reads existing queryable", async () => { - expect(await Queryable.read(testId1, testId2)).to.equal(testQueryable); + expect(await Queryable.read(testId1, testId2)).to.deep.equal(testQueryable); }); }); }; diff --git a/test/db/queries/ReimbursementItemBoxQuery.spec.ts b/test/db/queries/ReimbursementItemBoxQuery.spec.ts index 1ec1f0c..faf5a29 100644 --- a/test/db/queries/ReimbursementItemBoxQuery.spec.ts +++ b/test/db/queries/ReimbursementItemBoxQuery.spec.ts @@ -13,7 +13,7 @@ const testRIBInitializer: ReimbursementItemBoxInitializer = { item_quantity: 1 }; -describe("ReimbursementItemBox Query Tests", () => { +describe.only("ReimbursementItemBox Query Tests", () => { testCreate(ReimbursementItemBoxQuery, { testInitializer: testRIBInitializer, getId1: (q) => q.reimbursement_id, From 2fcf2afb855233c2c06f63e9e0e670e694ddd810 Mon Sep 17 00:00:00 2001 From: Daniel Pan <57362494+daniel-panhead@users.noreply.github.com> Date: Sat, 10 Feb 2024 23:13:05 -0800 Subject: [PATCH 03/11] Add nonexistent read check to CompositeCrudQueryable tests --- test/db/queries/CompositeCrudQueryable.ts | 7 ++++++- test/db/queries/ReimbursementItemBoxQuery.spec.ts | 2 ++ test/db/queries/TransactionItemQuery.spec.ts | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/test/db/queries/CompositeCrudQueryable.ts b/test/db/queries/CompositeCrudQueryable.ts index 7a3bdb9..831b684 100644 --- a/test/db/queries/CompositeCrudQueryable.ts +++ b/test/db/queries/CompositeCrudQueryable.ts @@ -30,14 +30,19 @@ export const testRead = ( testProps: { testId1: PK1, testId2: PK2, + nonexistentId1: PK1, + nonexistentId2: PK2, testQueryable: T } ) => { - const {testId1, testId2, testQueryable} = testProps; + const {testId1, testId2, nonexistentId1, nonexistentId2, testQueryable} = testProps; describe("read()", () => { it("reads existing queryable", async () => { expect(await Queryable.read(testId1, testId2)).to.deep.equal(testQueryable); }); + it("returns null when reading nonexistent queryable", async () => { + expect(await Queryable.read(nonexistentId1, nonexistentId2)).to.be.null; + }); }); }; diff --git a/test/db/queries/ReimbursementItemBoxQuery.spec.ts b/test/db/queries/ReimbursementItemBoxQuery.spec.ts index faf5a29..a7bca77 100644 --- a/test/db/queries/ReimbursementItemBoxQuery.spec.ts +++ b/test/db/queries/ReimbursementItemBoxQuery.spec.ts @@ -23,6 +23,8 @@ describe.only("ReimbursementItemBox Query Tests", () => { testRead(ReimbursementItemBoxQuery, { testId1: TestItems.reimbursementOneClifBar.reimbursement_id, testId2: TestItems.reimbursementOneClifBar.item_box_id, + nonexistentId1: -1, + nonexistentId2: -2, testQueryable: TestItems.reimbursementOneClifBar }); diff --git a/test/db/queries/TransactionItemQuery.spec.ts b/test/db/queries/TransactionItemQuery.spec.ts index 496c769..0e2066a 100644 --- a/test/db/queries/TransactionItemQuery.spec.ts +++ b/test/db/queries/TransactionItemQuery.spec.ts @@ -20,6 +20,8 @@ describe("TransactionItem Query Tests", () => { testRead(TransactionItemQuery, { testId1: TestItems.transactionOneRamen.transaction_id, testId2: TestItems.transactionOneRamen.item_id, + nonexistentId1: -1, + nonexistentId2: -2, testQueryable: TestItems.transactionOneRamen }); From cfba06105ac2cb3f87958b7a9025dee4bea5713c Mon Sep 17 00:00:00 2001 From: Daniel Pan <57362494+daniel-panhead@users.noreply.github.com> Date: Wed, 21 Feb 2024 21:00:40 -0800 Subject: [PATCH 04/11] Refactor crud queryable interfaces out to separate files --- ...Queryable.ts => CompositeCrudQueryable.ts} | 37 ------------------ src/db/SimpleCrudQueryable.ts | 38 +++++++++++++++++++ src/db/queries/CsssUserQuery.ts | 2 +- src/db/queries/ItemBoxQuery.ts | 2 +- src/db/queries/ItemIndividualQuery.ts | 2 +- src/db/queries/ReimbursementItemBoxQuery.ts | 2 +- src/db/queries/ReimbursementQuery.ts | 2 +- src/db/queries/TransactionItemQuery.ts | 2 +- src/db/queries/TransactionQuery.ts | 2 +- src/db/queries/ValidCategoryQuery.ts | 2 +- test/db/queries/CompositeCrudQueryable.ts | 2 +- .../queries/ReimbursementItemBoxQuery.spec.ts | 2 +- test/db/queries/SimpleCrudQueryable.ts | 2 +- 13 files changed, 49 insertions(+), 48 deletions(-) rename src/db/{Queryable.ts => CompositeCrudQueryable.ts} (55%) create mode 100644 src/db/SimpleCrudQueryable.ts diff --git a/src/db/Queryable.ts b/src/db/CompositeCrudQueryable.ts similarity index 55% rename from src/db/Queryable.ts rename to src/db/CompositeCrudQueryable.ts index c20a39e..5535a43 100644 --- a/src/db/Queryable.ts +++ b/src/db/CompositeCrudQueryable.ts @@ -1,40 +1,3 @@ -export interface SimpleCrudQueryable { - /** - * Creates a new entry for the given object in the database. - * @param object Initializer of the object - * @returns Promise resolving to the given initialized object if successful - */ - create(object: TInit): Promise; - - /** - * Reads the database and returns the object with the given primary key. - * @param primaryKey Primary key of the object - * @returns Promise resolving to object with given primary key, or null if no object is found - */ - read(primaryKey: PK): Promise; - - /** - * Reads the database for all entries of the object. - * @returns Promise resolving to all entries of the object in its table in the database - */ - readAll(): Promise; - - /** - * Updates an existing object with the given primary key. - * @param primaryKey Primary key of the object - * @param mutateProps Mutator of the object containing desired new properties - * @returns Promise resolving to the updated object, or null if no object is found - */ - update(primaryKey: PK, mutateProps: TMut): Promise; - - /** - * Deletes the object in the database with the given primary key. - * @param primaryKey Primary key of the object - * @returns Promise resolving to boolean indicating whether any rows were deleted - */ - delete(primaryKey: PK): Promise; -} - export interface CompositeCrudQueryable { /** * Creates a new entry for the given object in the database. diff --git a/src/db/SimpleCrudQueryable.ts b/src/db/SimpleCrudQueryable.ts new file mode 100644 index 0000000..a3c4867 --- /dev/null +++ b/src/db/SimpleCrudQueryable.ts @@ -0,0 +1,38 @@ +export interface SimpleCrudQueryable { + /** + * Creates a new entry for the given object in the database. + * @param object Initializer of the object + * @returns Promise resolving to the given initialized object if successful + */ + create(object: TInit): Promise; + + /** + * Reads the database and returns the object with the given primary key. + * @param primaryKey Primary key of the object + * @returns Promise resolving to object with given primary key, or null if no object is found + */ + read(primaryKey: PK): Promise; + + /** + * Reads the database for all entries of the object. + * @returns Promise resolving to all entries of the object in its table in the database + */ + readAll(): Promise; + + /** + * Updates an existing object with the given primary key. + * @param primaryKey Primary key of the object + * @param mutateProps Mutator of the object containing desired new properties + * @returns Promise resolving to the updated object, or null if no object is found + */ + update(primaryKey: PK, mutateProps: TMut): Promise; + + /** + * Deletes the object in the database with the given primary key. + * @param primaryKey Primary key of the object + * @returns Promise resolving to boolean indicating whether any rows were deleted + */ + delete(primaryKey: PK): Promise; +} + + diff --git a/src/db/queries/CsssUserQuery.ts b/src/db/queries/CsssUserQuery.ts index 4d85ebb..39b4c9c 100644 --- a/src/db/queries/CsssUserQuery.ts +++ b/src/db/queries/CsssUserQuery.ts @@ -1,7 +1,7 @@ import CsssUser, {UserId, CsssUserInitializer, CsssUserMutator} from "../../types/db/public/CsssUser"; -import {SimpleCrudQueryable} from "../Queryable"; +import {SimpleCrudQueryable} from "../SimpleCrudQueryable"; const simpleCrudQueries: SimpleCrudQueryable = { diff --git a/src/db/queries/ItemBoxQuery.ts b/src/db/queries/ItemBoxQuery.ts index 4564e50..99e7610 100644 --- a/src/db/queries/ItemBoxQuery.ts +++ b/src/db/queries/ItemBoxQuery.ts @@ -1,6 +1,6 @@ import ItemBox, {ItemBoxId, ItemBoxInitializer, ItemBoxMutator} from "../../types/db/public/ItemBox"; -import {SimpleCrudQueryable} from "../Queryable"; +import {SimpleCrudQueryable} from "../SimpleCrudQueryable"; const simpleCrudQueries: SimpleCrudQueryable = { diff --git a/src/db/queries/ItemIndividualQuery.ts b/src/db/queries/ItemIndividualQuery.ts index 925a278..93f1840 100644 --- a/src/db/queries/ItemIndividualQuery.ts +++ b/src/db/queries/ItemIndividualQuery.ts @@ -1,7 +1,7 @@ import ItemIndividual, {ItemId, ItemIndividualInitializer, ItemIndividualMutator} from "../../types/db/public/ItemIndividual"; import {Category} from "../../types/db/public/ValidCategory"; -import {SimpleCrudQueryable} from "../Queryable"; +import {SimpleCrudQueryable} from "../SimpleCrudQueryable"; import * as DB from "../../db/DB"; const simpleCrudQueries: diff --git a/src/db/queries/ReimbursementItemBoxQuery.ts b/src/db/queries/ReimbursementItemBoxQuery.ts index f268a94..8e987b5 100644 --- a/src/db/queries/ReimbursementItemBoxQuery.ts +++ b/src/db/queries/ReimbursementItemBoxQuery.ts @@ -2,7 +2,7 @@ import {ItemBoxId} from "../../types/db/public/ItemBox"; import {ReimbursementId} from "../../types/db/public/Reimbursement"; import ReimbursementItemBox, {ReimbursementItemBoxInitializer, ReimbursementItemBoxMutator} from "../../types/db/public/ReimbursementItemBox"; -import {CompositeCrudQueryable} from "../Queryable"; +import {CompositeCrudQueryable} from "../CompositeCrudQueryable"; import * as DB from "../../db/DB"; const compositeCrudQueries: diff --git a/src/db/queries/ReimbursementQuery.ts b/src/db/queries/ReimbursementQuery.ts index a6937ef..edff884 100644 --- a/src/db/queries/ReimbursementQuery.ts +++ b/src/db/queries/ReimbursementQuery.ts @@ -1,7 +1,7 @@ import Reimbursement, {ReimbursementId, ReimbursementInitializer, ReimbursementMutator} from "../../types/db/public/Reimbursement"; -import {SimpleCrudQueryable} from "../Queryable"; +import {SimpleCrudQueryable} from "../SimpleCrudQueryable"; const simpleCrudQueries: SimpleCrudQueryable = { diff --git a/src/db/queries/TransactionItemQuery.ts b/src/db/queries/TransactionItemQuery.ts index 28d28a1..76ff3e2 100644 --- a/src/db/queries/TransactionItemQuery.ts +++ b/src/db/queries/TransactionItemQuery.ts @@ -2,7 +2,7 @@ import {ItemId} from "../../types/db/public/ItemIndividual"; import {TransactionId} from "../../types/db/public/Transaction"; import TransactionItem, {TransactionItemInitializer, TransactionItemMutator} from "../../types/db/public/TransactionItem"; -import {CompositeCrudQueryable} from "../Queryable"; +import {CompositeCrudQueryable} from "../CompositeCrudQueryable"; const compositeCrudQueries: CompositeCrudQueryable = { diff --git a/src/db/queries/ValidCategoryQuery.ts b/src/db/queries/ValidCategoryQuery.ts index db21bd6..6cc0b68 100644 --- a/src/db/queries/ValidCategoryQuery.ts +++ b/src/db/queries/ValidCategoryQuery.ts @@ -1,7 +1,7 @@ import ValidCategory, {Category, ValidCategoryInitializer, ValidCategoryMutator} from "../../types/db/public/ValidCategory"; -import {SimpleCrudQueryable} from "../Queryable"; +import {SimpleCrudQueryable} from "../SimpleCrudQueryable"; const simpleCrudQueries: SimpleCrudQueryable = { diff --git a/test/db/queries/CompositeCrudQueryable.ts b/test/db/queries/CompositeCrudQueryable.ts index 831b684..1fa7db2 100644 --- a/test/db/queries/CompositeCrudQueryable.ts +++ b/test/db/queries/CompositeCrudQueryable.ts @@ -1,6 +1,6 @@ /* eslint-disable no-unused-expressions */ import {expect} from "chai"; -import {CompositeCrudQueryable} from "../../../src/db/Queryable"; +import {CompositeCrudQueryable} from "../../../src/db/CompositeCrudQueryable"; export const testCreate = ( Queryable: CompositeCrudQueryable, diff --git a/test/db/queries/ReimbursementItemBoxQuery.spec.ts b/test/db/queries/ReimbursementItemBoxQuery.spec.ts index a7bca77..45f1798 100644 --- a/test/db/queries/ReimbursementItemBoxQuery.spec.ts +++ b/test/db/queries/ReimbursementItemBoxQuery.spec.ts @@ -13,7 +13,7 @@ const testRIBInitializer: ReimbursementItemBoxInitializer = { item_quantity: 1 }; -describe.only("ReimbursementItemBox Query Tests", () => { +describe("ReimbursementItemBox Query Tests", () => { testCreate(ReimbursementItemBoxQuery, { testInitializer: testRIBInitializer, getId1: (q) => q.reimbursement_id, diff --git a/test/db/queries/SimpleCrudQueryable.ts b/test/db/queries/SimpleCrudQueryable.ts index 2021ab2..0df3f4c 100644 --- a/test/db/queries/SimpleCrudQueryable.ts +++ b/test/db/queries/SimpleCrudQueryable.ts @@ -1,6 +1,6 @@ /* eslint-disable no-unused-expressions */ import {expect} from "chai"; -import {SimpleCrudQueryable} from "../../../src/db/Queryable"; +import {SimpleCrudQueryable} from "../../../src/db/SimpleCrudQueryable"; export const testCreate = ( Queryable: SimpleCrudQueryable, From 09d40b0589b0f7c399af71ffd16c6bece3a9e613 Mon Sep 17 00:00:00 2001 From: Daniel Pan <57362494+daniel-panhead@users.noreply.github.com> Date: Wed, 21 Feb 2024 21:27:32 -0800 Subject: [PATCH 05/11] Factor simple crud queries out into generic functions --- src/db/SimpleCrudQueryable.ts | 133 +++++++++++++++++++------- src/db/queries/ItemIndividualQuery.ts | 69 +++++-------- 2 files changed, 124 insertions(+), 78 deletions(-) diff --git a/src/db/SimpleCrudQueryable.ts b/src/db/SimpleCrudQueryable.ts index a3c4867..a64f8dc 100644 --- a/src/db/SimpleCrudQueryable.ts +++ b/src/db/SimpleCrudQueryable.ts @@ -1,38 +1,103 @@ +import * as DB from "../db/DB"; + export interface SimpleCrudQueryable { - /** - * Creates a new entry for the given object in the database. - * @param object Initializer of the object - * @returns Promise resolving to the given initialized object if successful - */ - create(object: TInit): Promise; - - /** - * Reads the database and returns the object with the given primary key. - * @param primaryKey Primary key of the object - * @returns Promise resolving to object with given primary key, or null if no object is found - */ - read(primaryKey: PK): Promise; - - /** - * Reads the database for all entries of the object. - * @returns Promise resolving to all entries of the object in its table in the database - */ - readAll(): Promise; - - /** - * Updates an existing object with the given primary key. - * @param primaryKey Primary key of the object - * @param mutateProps Mutator of the object containing desired new properties - * @returns Promise resolving to the updated object, or null if no object is found - */ - update(primaryKey: PK, mutateProps: TMut): Promise; - - /** - * Deletes the object in the database with the given primary key. - * @param primaryKey Primary key of the object - * @returns Promise resolving to boolean indicating whether any rows were deleted - */ - delete(primaryKey: PK): Promise; + /** + * Creates a new entry for the given object in the database. + * @param object Initializer of the object + * @returns Promise resolving to the given initialized object if successful + */ + create(object: TInit): Promise; + + /** + * Reads the database and returns the object with the given primary key. + * @param primaryKey Primary key of the object + * @returns Promise resolving to object with given primary key, or null if no object is found + */ + read(primaryKey: PK): Promise; + + /** + * Reads the database for all entries of the object. + * @returns Promise resolving to all entries of the object in its table in the database + */ + readAll(): Promise; + + /** + * Updates an existing object with the given primary key. + * @param primaryKey Primary key of the object + * @param mutateProps Mutator of the object containing desired new properties + * @returns Promise resolving to the updated object, or null if no object is found + */ + update(primaryKey: PK, mutateProps: TMut): Promise; + + /** + * Deletes the object in the database with the given primary key. + * @param primaryKey Primary key of the object + * @returns Promise resolving to boolean indicating whether any rows were deleted + */ + delete(primaryKey: PK): Promise; } +export const simpleCreate = async(object: TInit, table: string): Promise => { + // Object.keys and Object.values return things in the same order so this is safe + const keys = Object.keys(object); + const values = Object.values(object); + + const queryResponse = await DB.query( + `INSERT INTO ${table} (${keys.join(",")})` + + `VALUES (${keys.map((prop, i) => `$${i + 1}`).join(",")})` + + "RETURNING *", + values + ); + if (queryResponse.rows.length === 1) { + return queryResponse.rows[0]; + } else { + return null; + } +}; + +export const simpleRead = async(pk: PK, table: string): Promise => { + const queryResponse = await DB.query( + `SELECT * FROM ${table} WHERE item_id=$1`, + [pk] + ); + if(queryResponse.rows.length === 1) { + return queryResponse.rows[0]; + } else { + return null; + } +}; + +export const simpleReadAll = async (table: string): Promise => { + const queryResponse = await DB.query(`SELECT * FROM ${table}`); + return queryResponse.rows; +}; + +export const simpleUpdate = async ( + pk: PK, mutateObject: TMut, + table: string, + pkName: string +): Promise => { + if(Object.keys(mutateObject).length === 0) { + return null; + } + + // Use i+2 for parameter so that $1 is reserved for the item id + const keys = Object.keys(mutateObject).map((prop, i) => `${prop}=$${i + 2}`); + const queryResponse = await DB.query( + `UPDATE ${table} SET ${keys.join(",")} WHERE ${pkName}=$1 RETURNING *`, + [pk, ...Object.values(mutateObject)] + ); + if (queryResponse.rows.length === 1) { + return queryResponse.rows[0]; + } else { + return null; + } +}; +export const simpleDelete = async (pk: PK, table: string, pkName: string): Promise => { + const queryResponse = await DB.query( + `DELETE FROM ${table} WHERE ${pkName}=$1`, + [pk] + ); + return queryResponse.rowCount === 1; +}; diff --git a/src/db/queries/ItemIndividualQuery.ts b/src/db/queries/ItemIndividualQuery.ts index 93f1840..49eb968 100644 --- a/src/db/queries/ItemIndividualQuery.ts +++ b/src/db/queries/ItemIndividualQuery.ts @@ -1,70 +1,51 @@ import ItemIndividual, {ItemId, ItemIndividualInitializer, ItemIndividualMutator} from "../../types/db/public/ItemIndividual"; import {Category} from "../../types/db/public/ValidCategory"; -import {SimpleCrudQueryable} from "../SimpleCrudQueryable"; +import { + SimpleCrudQueryable, + simpleCreate, + simpleRead, + simpleReadAll, + simpleUpdate, + simpleDelete +} from "../SimpleCrudQueryable"; import * as DB from "../../db/DB"; +const tableName = "item_individual"; +const pkName = "item_id"; + const simpleCrudQueries: SimpleCrudQueryable = { async create(object: ItemIndividualInitializer): Promise { - // Object.keys and Object.values return things in the same order so this is safe - const keys = Object.keys(object); - const values = Object.values(object); - - const queryResponse = await DB.query( - `INSERT INTO item_individual (${keys.join(",")})` + - `VALUES (${keys.map((prop, i) => `$${i + 1}`).join(",")})` + - "RETURNING *", - values + return simpleCreate( + object, + tableName ); - if (queryResponse.rows.length === 1) { - return queryResponse.rows[0]; - } else { - return null; - } }, async read(itemId: ItemId): Promise { - const queryResponse = await DB.query( - "SELECT * FROM item_individual WHERE item_id=$1", - [itemId] - ); - if (queryResponse.rows.length === 1) { - return queryResponse.rows[0]; - } else { - return null; - } + return simpleRead(itemId, tableName); }, async readAll(): Promise { - const queryResponse = await DB.query("SELECT * FROM item_individual"); - return queryResponse.rows; + return simpleReadAll(tableName); }, async update(itemId: ItemId, mutateObject: ItemIndividualMutator): Promise { - if (Object.keys(mutateObject).length === 0) { - return null; - } - - // Use i+2 for parameter so that $1 is reserved for the item id - const keys = Object.keys(mutateObject).map((prop, i) => `${prop}=$${i + 2}`); - const queryResponse = await DB.query( - `UPDATE item_individual SET ${keys.join(",")} WHERE item_id=$1 RETURNING *`, - [itemId, ...Object.values(mutateObject)] + return simpleUpdate( + itemId, + mutateObject, + tableName, + pkName ); - if (queryResponse.rows.length === 1) { - return queryResponse.rows[0]; - } else { - return null; - } }, async delete(itemId: ItemId): Promise { - const queryResponse = await DB.query( - "DELETE FROM item_individual WHERE item_id=$1", - [itemId] + return simpleDelete( + itemId, + tableName, + pkName ); - return queryResponse.rowCount === 1; } }; From cf6d3c6a98118062fffc38b9446e83a0fc21d5e6 Mon Sep 17 00:00:00 2001 From: Daniel Pan <57362494+daniel-panhead@users.noreply.github.com> Date: Wed, 21 Feb 2024 21:39:47 -0800 Subject: [PATCH 06/11] Factor composiste crud queries out into generic functions --- src/db/CompositeCrudQueryable.ts | 157 +++++++++++++++++++++++-------- 1 file changed, 120 insertions(+), 37 deletions(-) diff --git a/src/db/CompositeCrudQueryable.ts b/src/db/CompositeCrudQueryable.ts index 5535a43..d4351e5 100644 --- a/src/db/CompositeCrudQueryable.ts +++ b/src/db/CompositeCrudQueryable.ts @@ -1,39 +1,122 @@ +import * as DB from "../db/DB"; + export interface CompositeCrudQueryable { - /** - * Creates a new entry for the given object in the database. - * @param object Initializer of the object - * @returns Promise resolving to the given initialized object if successful - */ - create(object: TInit): Promise; - - /** - * Reads the database and returns the object with the given composite key. - * @param pk1 First foreign key of the object that forms the composite key - * @param pk2 Second foreign key of the object that forms the composite key - * @returns Promise resolving to object with given composite key, or null if no object is found - */ - read(pk1: PK1, pk2: PK2): Promise; - - /** - * Reads the database for all entries of the object. - * @returns Promise resolving to all entries of the object in its table in the database - */ - readAll(): Promise; - - /** - * Updates an existing object with the given composite key. - * @param pk1 First foreign key of the object that forms the composite key - * @param pk2 Second foreign key of the object that forms the composite key - * @param mutateProps Mutator of the object containing desired new properties - * @returns Promise resolving to the updated object, or null if no object is found - */ - update(pk1: PK1, pk2: PK2, mutateObject: TMut): Promise; - - /** - * Deletes the object in the database with the given composite key. - * @param pk1 First foreign key of the object that forms the composite key - * @param pk2 Second foreign key of the object that forms the composite key - * @returns Promise resolving to boolean indicating whether any rows were deleted - */ - delete(pk1: PK1, pk2: PK2): Promise; + /** + * Creates a new entry for the given object in the database. + * @param object Initializer of the object + * @returns Promise resolving to the given initialized object if successful + */ + create(object: TInit): Promise; + + /** + * Reads the database and returns the object with the given composite key. + * @param pk1 First foreign key of the object that forms the composite key + * @param pk2 Second foreign key of the object that forms the composite key + * @returns Promise resolving to object with given composite key, or null if no object is found + */ + read(pk1: PK1, pk2: PK2): Promise; + + /** + * Reads the database for all entries of the object. + * @returns Promise resolving to all entries of the object in its table in the database + */ + readAll(): Promise; + + /** + * Updates an existing object with the given composite key. + * @param pk1 First foreign key of the object that forms the composite key + * @param pk2 Second foreign key of the object that forms the composite key + * @param mutateProps Mutator of the object containing desired new properties + * @returns Promise resolving to the updated object, or null if no object is found + */ + update(pk1: PK1, pk2: PK2, mutateObject: TMut): Promise; + + /** + * Deletes the object in the database with the given composite key. + * @param pk1 First foreign key of the object that forms the composite key + * @param pk2 Second foreign key of the object that forms the composite key + * @returns Promise resolving to boolean indicating whether any rows were deleted + */ + delete(pk1: PK1, pk2: PK2): Promise; } + +const compositeCreate = async (object: TInit, table: string): Promise => { + // Object.keys and Object.values return things in the same order so this is safe + const keys = Object.keys(object); + const values = Object.values(object); + + const queryResponse = await DB.query( + `INSERT INTO ${table} (${keys.join(",")})` + + `VALUES (${keys.map((prop, i) => `$${i + 1}`).join(",")})` + + "RETURNING *", + values + ); + if (queryResponse.rows.length === 1) { + return queryResponse.rows[0]; + } else { + return null; + } +}; + +const compositeRead = async ( + pk1: PK1, + pk2: PK2, + table: string, + pk1Name: string, + pk2Name: string +): Promise => { + const queryResponse = await DB.query( + `SELECT * FROM ${table} WHERE ${pk1Name}=$1 AND ${pk2Name}=$2`, + [pk1, pk2] + ); + if (queryResponse.rows.length === 1) { + return queryResponse.rows[0]; + } else { + return null; + } +}; + +const compositeReadAll = async (table: string): Promise => { + const queryResponse = await DB.query(`SELECT * FROM ${table}`); + return queryResponse.rows; +}; + +const compositeUpdate = async ( + pk1: PK1, + pk2: PK2, + mutateObject: TMut, + table: string, + pk1Name: string, + pk2Name: string +): Promise => { + if (Object.keys(mutateObject).length === 0) { + return null; + } + + // Use i+3 for parameter so that $1 and $2 are reserved for the PKs + const keys = Object.keys(mutateObject).map((prop, i) => `${prop}=$${i + 3}`); + const queryResponse = await DB.query( + `UPDATE ${table} SET ${keys.join(",")}` + + ` WHERE ${pk1Name}=$1 AND ${pk2Name}=$2 RETURNING *`, + [pk1, pk2, ...Object.values(mutateObject)] + ); + if (queryResponse.rows.length === 1) { + return queryResponse.rows[0]; + } else { + return null; + } +}; + +const compositeDelete = async ( + pk1: PK1, + pk2: PK2, + table: string, + pk1Name: string, + pk2Name: string +): Promise => { + const queryResponse = await DB.query( + `DELETE FROM ${table} WHERE ${pk1Name}=$1 AND ${pk2Name}=$2`, + [pk1, pk2] + ); + return queryResponse.rowCount === 1; +}; From 6e449ce71c8c5239e878ff3198a2e050d27e69e9 Mon Sep 17 00:00:00 2001 From: Daniel Pan <57362494+daniel-panhead@users.noreply.github.com> Date: Wed, 21 Feb 2024 22:21:09 -0800 Subject: [PATCH 07/11] Convert crud queryables to be classes and inherit default crud functions --- src/db/CompositeCrudQueryable.ts | 155 +++++++++----------- src/db/SimpleCrudQueryable.ts | 136 ++++++++--------- src/db/queries/CsssUserQuery.ts | 38 ++--- src/db/queries/ItemBoxQuery.ts | 30 +--- src/db/queries/ItemIndividualQuery.ts | 63 ++------ src/db/queries/ReimbursementItemBoxQuery.ts | 100 +++---------- src/db/queries/ReimbursementQuery.ts | 35 ++--- src/db/queries/TransactionItemQuery.ts | 59 +++----- src/db/queries/TransactionQuery.ts | 35 ++--- src/db/queries/ValidCategoryQuery.ts | 35 ++--- 10 files changed, 237 insertions(+), 449 deletions(-) diff --git a/src/db/CompositeCrudQueryable.ts b/src/db/CompositeCrudQueryable.ts index d4351e5..362be76 100644 --- a/src/db/CompositeCrudQueryable.ts +++ b/src/db/CompositeCrudQueryable.ts @@ -1,12 +1,38 @@ import * as DB from "../db/DB"; -export interface CompositeCrudQueryable { +export class CompositeCrudQueryable { + #tableName: string; + #pk1Name: string; + #pk2Name: string; + + constructor(tableName: string, pk1Name: string, pk2Name: string) { + this.#tableName = tableName; + this.#pk1Name = pk1Name; + this.#pk2Name = pk2Name; + } + /** * Creates a new entry for the given object in the database. * @param object Initializer of the object * @returns Promise resolving to the given initialized object if successful */ - create(object: TInit): Promise; + public create = async (object: TInit): Promise => { + // Object.keys and Object.values return things in the same order so this is safe + const keys = Object.keys(object); + const values = Object.values(object); + + const queryResponse = await DB.query( + `INSERT INTO ${this.#tableName} (${keys.join(",")})` + + `VALUES (${keys.map((prop, i) => `$${i + 1}`).join(",")})` + + "RETURNING *", + values + ); + if (queryResponse.rows.length === 1) { + return queryResponse.rows[0]; + } else { + return null; + } + }; /** * Reads the database and returns the object with the given composite key. @@ -14,13 +40,26 @@ export interface CompositeCrudQueryable { * @param pk2 Second foreign key of the object that forms the composite key * @returns Promise resolving to object with given composite key, or null if no object is found */ - read(pk1: PK1, pk2: PK2): Promise; + public read = async (pk1: PK1, pk2: PK2): Promise => { + const queryResponse = await DB.query( + `SELECT * FROM ${this.#tableName} WHERE ${this.#pk1Name}=$1 AND ${this.#pk2Name}=$2`, + [pk1, pk2] + ); + if (queryResponse.rows.length === 1) { + return queryResponse.rows[0]; + } else { + return null; + } + }; /** * Reads the database for all entries of the object. * @returns Promise resolving to all entries of the object in its table in the database */ - readAll(): Promise; + public readAll = async (): Promise => { + const queryResponse = await DB.query(`SELECT * FROM ${this.#tableName}`); + return queryResponse.rows; + }; /** * Updates an existing object with the given composite key. @@ -29,7 +68,24 @@ export interface CompositeCrudQueryable { * @param mutateProps Mutator of the object containing desired new properties * @returns Promise resolving to the updated object, or null if no object is found */ - update(pk1: PK1, pk2: PK2, mutateObject: TMut): Promise; + public update = async (pk1: PK1, pk2: PK2, mutateObject: TMut): Promise => { + if (Object.keys(mutateObject).length === 0) { + return null; + } + + // Use i+3 for parameter so that $1 and $2 are reserved for the PKs + const keys = Object.keys(mutateObject).map((prop, i) => `${prop}=$${i + 3}`); + const queryResponse = await DB.query( + `UPDATE ${this.#tableName} SET ${keys.join(",")}` + + ` WHERE ${this.#pk1Name}=$1 AND ${this.#pk2Name}=$2 RETURNING *`, + [pk1, pk2, ...Object.values(mutateObject)] + ); + if (queryResponse.rows.length === 1) { + return queryResponse.rows[0]; + } else { + return null; + } + }; /** * Deletes the object in the database with the given composite key. @@ -37,86 +93,11 @@ export interface CompositeCrudQueryable { * @param pk2 Second foreign key of the object that forms the composite key * @returns Promise resolving to boolean indicating whether any rows were deleted */ - delete(pk1: PK1, pk2: PK2): Promise; + public delete = async (pk1: PK1, pk2: PK2): Promise => { + const queryResponse = await DB.query( + `DELETE FROM ${this.#tableName} WHERE ${this.#pk1Name}=$1 AND ${this.#pk2Name}=$2`, + [pk1, pk2] + ); + return queryResponse.rowCount === 1; + }; } - -const compositeCreate = async (object: TInit, table: string): Promise => { - // Object.keys and Object.values return things in the same order so this is safe - const keys = Object.keys(object); - const values = Object.values(object); - - const queryResponse = await DB.query( - `INSERT INTO ${table} (${keys.join(",")})` + - `VALUES (${keys.map((prop, i) => `$${i + 1}`).join(",")})` + - "RETURNING *", - values - ); - if (queryResponse.rows.length === 1) { - return queryResponse.rows[0]; - } else { - return null; - } -}; - -const compositeRead = async ( - pk1: PK1, - pk2: PK2, - table: string, - pk1Name: string, - pk2Name: string -): Promise => { - const queryResponse = await DB.query( - `SELECT * FROM ${table} WHERE ${pk1Name}=$1 AND ${pk2Name}=$2`, - [pk1, pk2] - ); - if (queryResponse.rows.length === 1) { - return queryResponse.rows[0]; - } else { - return null; - } -}; - -const compositeReadAll = async (table: string): Promise => { - const queryResponse = await DB.query(`SELECT * FROM ${table}`); - return queryResponse.rows; -}; - -const compositeUpdate = async ( - pk1: PK1, - pk2: PK2, - mutateObject: TMut, - table: string, - pk1Name: string, - pk2Name: string -): Promise => { - if (Object.keys(mutateObject).length === 0) { - return null; - } - - // Use i+3 for parameter so that $1 and $2 are reserved for the PKs - const keys = Object.keys(mutateObject).map((prop, i) => `${prop}=$${i + 3}`); - const queryResponse = await DB.query( - `UPDATE ${table} SET ${keys.join(",")}` + - ` WHERE ${pk1Name}=$1 AND ${pk2Name}=$2 RETURNING *`, - [pk1, pk2, ...Object.values(mutateObject)] - ); - if (queryResponse.rows.length === 1) { - return queryResponse.rows[0]; - } else { - return null; - } -}; - -const compositeDelete = async ( - pk1: PK1, - pk2: PK2, - table: string, - pk1Name: string, - pk2Name: string -): Promise => { - const queryResponse = await DB.query( - `DELETE FROM ${table} WHERE ${pk1Name}=$1 AND ${pk2Name}=$2`, - [pk1, pk2] - ); - return queryResponse.rowCount === 1; -}; diff --git a/src/db/SimpleCrudQueryable.ts b/src/db/SimpleCrudQueryable.ts index a64f8dc..7485583 100644 --- a/src/db/SimpleCrudQueryable.ts +++ b/src/db/SimpleCrudQueryable.ts @@ -1,25 +1,62 @@ import * as DB from "../db/DB"; -export interface SimpleCrudQueryable { +export class SimpleCrudQueryable { + #tableName: string; + #pkName: string; + + constructor(tableName: string, pkName: string) { + this.#tableName = tableName; + this.#pkName = pkName; + } + /** * Creates a new entry for the given object in the database. * @param object Initializer of the object * @returns Promise resolving to the given initialized object if successful */ - create(object: TInit): Promise; + public create = async (object: TInit): Promise => { + // Object.keys and Object.values return things in the same order so this is safe + const keys = Object.keys(object); + const values = Object.values(object); + + const queryResponse = await DB.query( + `INSERT INTO ${this.#tableName} (${keys.join(",")})` + + `VALUES (${keys.map((prop, i) => `$${i + 1}`).join(",")})` + + "RETURNING *", + values + ); + if (queryResponse.rows.length === 1) { + return queryResponse.rows[0]; + } else { + return null; + } + }; /** * Reads the database and returns the object with the given primary key. * @param primaryKey Primary key of the object * @returns Promise resolving to object with given primary key, or null if no object is found */ - read(primaryKey: PK): Promise; + public read = async (primaryKey: PK): Promise => { + const queryResponse = await DB.query( + `SELECT * FROM ${this.#tableName} WHERE ${this.#pkName}=$1`, + [primaryKey] + ); + if (queryResponse.rows.length === 1) { + return queryResponse.rows[0]; + } else { + return null; + } + }; /** * Reads the database for all entries of the object. * @returns Promise resolving to all entries of the object in its table in the database */ - readAll(): Promise; + public readAll = async (): Promise => { + const queryResponse = await DB.query(`SELECT * FROM ${this.#tableName}`); + return queryResponse.rows; + }; /** * Updates an existing object with the given primary key. @@ -27,77 +64,34 @@ export interface SimpleCrudQueryable { * @param mutateProps Mutator of the object containing desired new properties * @returns Promise resolving to the updated object, or null if no object is found */ - update(primaryKey: PK, mutateProps: TMut): Promise; + public update = async (primaryKey: PK, mutateObject: TMut): Promise => { + if (Object.keys(mutateObject).length === 0) { + return null; + } + + // Use i+2 for parameter so that $1 is reserved for the item id + const keys = Object.keys(mutateObject).map((prop, i) => `${prop}=$${i + 2}`); + const queryResponse = await DB.query( + `UPDATE ${this.#tableName} SET ${keys.join(",")} WHERE ${this.#pkName}=$1 RETURNING *`, + [primaryKey, ...Object.values(mutateObject)] + ); + if (queryResponse.rows.length === 1) { + return queryResponse.rows[0]; + } else { + return null; + } + }; /** * Deletes the object in the database with the given primary key. * @param primaryKey Primary key of the object * @returns Promise resolving to boolean indicating whether any rows were deleted */ - delete(primaryKey: PK): Promise; + public delete = async (primaryKey: PK): Promise => { + const queryResponse = await DB.query( + `DELETE FROM ${this.#tableName} WHERE ${this.#pkName}=$1`, + [primaryKey] + ); + return queryResponse.rowCount === 1; + }; } - -export const simpleCreate = async(object: TInit, table: string): Promise => { - // Object.keys and Object.values return things in the same order so this is safe - const keys = Object.keys(object); - const values = Object.values(object); - - const queryResponse = await DB.query( - `INSERT INTO ${table} (${keys.join(",")})` + - `VALUES (${keys.map((prop, i) => `$${i + 1}`).join(",")})` + - "RETURNING *", - values - ); - if (queryResponse.rows.length === 1) { - return queryResponse.rows[0]; - } else { - return null; - } -}; - -export const simpleRead = async(pk: PK, table: string): Promise => { - const queryResponse = await DB.query( - `SELECT * FROM ${table} WHERE item_id=$1`, - [pk] - ); - if(queryResponse.rows.length === 1) { - return queryResponse.rows[0]; - } else { - return null; - } -}; - -export const simpleReadAll = async (table: string): Promise => { - const queryResponse = await DB.query(`SELECT * FROM ${table}`); - return queryResponse.rows; -}; - -export const simpleUpdate = async ( - pk: PK, mutateObject: TMut, - table: string, - pkName: string -): Promise => { - if(Object.keys(mutateObject).length === 0) { - return null; - } - - // Use i+2 for parameter so that $1 is reserved for the item id - const keys = Object.keys(mutateObject).map((prop, i) => `${prop}=$${i + 2}`); - const queryResponse = await DB.query( - `UPDATE ${table} SET ${keys.join(",")} WHERE ${pkName}=$1 RETURNING *`, - [pk, ...Object.values(mutateObject)] - ); - if (queryResponse.rows.length === 1) { - return queryResponse.rows[0]; - } else { - return null; - } -}; - -export const simpleDelete = async (pk: PK, table: string, pkName: string): Promise => { - const queryResponse = await DB.query( - `DELETE FROM ${table} WHERE ${pkName}=$1`, - [pk] - ); - return queryResponse.rowCount === 1; -}; diff --git a/src/db/queries/CsssUserQuery.ts b/src/db/queries/CsssUserQuery.ts index 39b4c9c..29f834b 100644 --- a/src/db/queries/CsssUserQuery.ts +++ b/src/db/queries/CsssUserQuery.ts @@ -3,43 +3,23 @@ import CsssUser, {UserId, CsssUserInitializer, CsssUserMutator} from "../../types/db/public/CsssUser"; import {SimpleCrudQueryable} from "../SimpleCrudQueryable"; -const simpleCrudQueries: -SimpleCrudQueryable = { - async create(object: CsssUserInitializer): Promise { - throw new Error("Method not implemented."); - }, - - async read(userId: UserId): Promise { - throw new Error("Method not implemented."); - }, - - // should we allow? - async readAll(): Promise { - throw new Error("Method not implemented."); - }, +const tableName = "csss_user"; +const pkName = "user_id"; - async update(userId: UserId, mutateObject: CsssUserMutator): Promise { - throw new Error("Method not implemented."); - }, - - async delete(userId: UserId): Promise { - throw new Error("Method not implemented."); +class CsssUserQuery extends SimpleCrudQueryable { + constructor() { + super(tableName, pkName); } -}; -const csssUserQueries = { /** * Tries to authenticate the user with the given credentials. * @param email Email of the given user * @param password Password hash of the given user * @returns Promise resolving to the CsssUser with the given credentials, or null if no user found */ - async authenticateUser(email: string, password: string): Promise { + public authenticateUser = async (email: string, password: string): Promise => { throw new Error("Method not implemented."); - } -}; + }; +} -export default { - ...simpleCrudQueries, - ...csssUserQueries -}; +export default new CsssUserQuery(); diff --git a/src/db/queries/ItemBoxQuery.ts b/src/db/queries/ItemBoxQuery.ts index 99e7610..b0b0c47 100644 --- a/src/db/queries/ItemBoxQuery.ts +++ b/src/db/queries/ItemBoxQuery.ts @@ -2,29 +2,13 @@ import ItemBox, {ItemBoxId, ItemBoxInitializer, ItemBoxMutator} from "../../types/db/public/ItemBox"; import {SimpleCrudQueryable} from "../SimpleCrudQueryable"; -const simpleCrudQueries: -SimpleCrudQueryable = { - async create(object: ItemBoxInitializer): Promise { - throw new Error("Method not implemented."); - }, +const tableName = "item_box"; +const pkName = "item_box_id"; - async read(itemBoxId: ItemBoxId): Promise { - throw new Error("Method not implemented."); - }, - - async readAll(): Promise { - throw new Error("Method not implemented."); - }, - - async update(itemBoxId: ItemBoxId, mutateObject: ItemBoxMutator): Promise { - throw new Error("Method not implemented."); - }, - - async delete(itemBoxId: ItemBoxId): Promise { - throw new Error("Method not implemented."); +class ItemBoxQuery extends SimpleCrudQueryable { + constructor() { + super(tableName, pkName); } -}; +} -export default { - ...simpleCrudQueries -}; +export default new ItemBoxQuery(); diff --git a/src/db/queries/ItemIndividualQuery.ts b/src/db/queries/ItemIndividualQuery.ts index 49eb968..945a976 100644 --- a/src/db/queries/ItemIndividualQuery.ts +++ b/src/db/queries/ItemIndividualQuery.ts @@ -1,70 +1,35 @@ import ItemIndividual, {ItemId, ItemIndividualInitializer, ItemIndividualMutator} from "../../types/db/public/ItemIndividual"; import {Category} from "../../types/db/public/ValidCategory"; -import { - SimpleCrudQueryable, - simpleCreate, - simpleRead, - simpleReadAll, - simpleUpdate, - simpleDelete -} from "../SimpleCrudQueryable"; +import {SimpleCrudQueryable} from "../SimpleCrudQueryable"; import * as DB from "../../db/DB"; const tableName = "item_individual"; const pkName = "item_id"; -const simpleCrudQueries: -SimpleCrudQueryable = { - async create(object: ItemIndividualInitializer): Promise { - return simpleCreate( - object, - tableName - ); - }, - - async read(itemId: ItemId): Promise { - return simpleRead(itemId, tableName); - }, - - async readAll(): Promise { - return simpleReadAll(tableName); - }, - - async update(itemId: ItemId, mutateObject: ItemIndividualMutator): Promise { - return simpleUpdate( - itemId, - mutateObject, - tableName, - pkName - ); - }, - - async delete(itemId: ItemId): Promise { - return simpleDelete( - itemId, - tableName, - pkName - ); +class ItemIndividualQuery extends SimpleCrudQueryable< + ItemIndividual, + ItemIndividualInitializer, + ItemIndividualMutator, + ItemId +> { + constructor() { + super(tableName, pkName); } -}; -const itemIndividualQueries = { /** * Searches for all items that have the specified category. * @param category Category to search within * @returns Promise resolving to all items in the table with the specified category */ - async readAllFromCategory(category: Category): Promise { + public readAllFromCategory = async (category: Category): Promise => { const queryResponse = await DB.query( "SELECT * FROM item_individual WHERE category=$1", [category] ); return queryResponse.rows; - } -}; + }; + +} -export default { - ...simpleCrudQueries, - ...itemIndividualQueries -}; +export default new ItemIndividualQuery(); diff --git a/src/db/queries/ReimbursementItemBoxQuery.ts b/src/db/queries/ReimbursementItemBoxQuery.ts index 8e987b5..32bc946 100644 --- a/src/db/queries/ReimbursementItemBoxQuery.ts +++ b/src/db/queries/ReimbursementItemBoxQuery.ts @@ -5,108 +5,46 @@ import ReimbursementItemBox, import {CompositeCrudQueryable} from "../CompositeCrudQueryable"; import * as DB from "../../db/DB"; -const compositeCrudQueries: - CompositeCrudQueryable -= { - async create(object: ReimbursementItemBoxInitializer): Promise { - // Object.keys and Object.values return things in the same order so this is safe - const keys = Object.keys(object); - const values = Object.values(object); - - const queryResponse = await DB.query( - `INSERT INTO reimbursement_item_box (${keys.join(",")})` + - `VALUES (${keys.map((prop, i) => `$${i + 1}`).join(",")})` + - "RETURNING *", - values - ); - if (queryResponse.rows.length === 1) { - return queryResponse.rows[0]; - } else { - return null; - } - }, - - async read(reimbursementId: ReimbursementId, itemBoxId: ItemBoxId): Promise { - const queryResponse = await DB.query( - "SELECT * FROM reimbursement_item_box WHERE reimbursement_id=$1 AND item_box_id=$2", - [reimbursementId, itemBoxId] - ); - if (queryResponse.rows.length === 1) { - return queryResponse.rows[0]; - } else { - return null; - } - }, - - async readAll(): Promise { - const queryResponse = await DB.query("SELECT * FROM reimbursement_item_box"); - return queryResponse.rows; - }, - - async update( - reimbursementId: ReimbursementId, - itemBoxId: ItemBoxId, mutateObject: ReimbursementItemBoxMutator - ): Promise { - if (Object.keys(mutateObject).length === 0) { - return null; - } - - // Use i+3 for parameter so that $1 and $2 are reserved for the PKs - const keys = Object.keys(mutateObject).map((prop, i) => `${prop}=$${i + 3}`); - const queryResponse = await DB.query( - `UPDATE reimbursement_item_box SET ${keys.join(",")}` + - " WHERE reimbursement_id=$1 AND item_box_id=$2 RETURNING *", - [reimbursementId, itemBoxId, ...Object.values(mutateObject)] - ); - if (queryResponse.rows.length === 1) { - return queryResponse.rows[0]; - } else { - return null; - } - }, - - async delete(reimbursementId: ReimbursementId, itemBoxId: ItemBoxId): Promise { - const queryResponse = await DB.query( - "DELETE FROM reimbursement_item_box WHERE reimbursement_id=$1 AND item_box_id=$2", - [reimbursementId, itemBoxId] - ); - return queryResponse.rowCount === 1; +const tableName = "reimbursement_item_box"; +const pk1Name = "reimbursement_id"; +const pk2Name = "item_box_id"; + +class ReimbursementItemBoxQuery extends CompositeCrudQueryable< + ReimbursementItemBox, + ReimbursementItemBoxInitializer, + ReimbursementItemBoxMutator, + ReimbursementId, + ItemBoxId +> { + constructor() { + super(tableName, pk1Name, pk2Name); } -}; -const reimbursementItemBoxQueries = { /** * Searches in the table for all ReimbursementItemBoxes that are linked to a particular item box. * @param itemBoxId Foreign key of item box to search for * @returns Promise resolving to array of ReimbursementItemBoxes linked to the given itemBoxId */ - async readAllFromItemBox(itemBoxId: ItemBoxId): Promise { + public readAllFromItemBox = async (itemBoxId: ItemBoxId): Promise => { const queryResponse = await DB.query( "SELECT * FROM reimbursement_item_box WHERE item_box_id=$1", [itemBoxId] ); return queryResponse.rows; - }, + }; /** * Searches in the table for all ReimbursementItemBoxes that are linked to a particular reimbursement. * @param reimbursementId Foreign key of reimbursement to search for * @returns Promise resolving to array of ReimbursementItemBoxes linked to the given reimbursementId */ - async readAllFromReimbursement(reimbursementId: ReimbursementId): Promise { + public readAllFromReimbursement = async (reimbursementId: ReimbursementId): Promise => { const queryResponse = await DB.query( "SELECT * FROM reimbursement_item_box WHERE reimbursement_id=$1", [reimbursementId] ); return queryResponse.rows; - } -}; + }; +} -export default { - ...compositeCrudQueries, - ...reimbursementItemBoxQueries -}; +export default new ReimbursementItemBoxQuery(); diff --git a/src/db/queries/ReimbursementQuery.ts b/src/db/queries/ReimbursementQuery.ts index edff884..8cc5102 100644 --- a/src/db/queries/ReimbursementQuery.ts +++ b/src/db/queries/ReimbursementQuery.ts @@ -3,29 +3,18 @@ import Reimbursement, {ReimbursementId, ReimbursementInitializer, ReimbursementMutator} from "../../types/db/public/Reimbursement"; import {SimpleCrudQueryable} from "../SimpleCrudQueryable"; -const simpleCrudQueries: -SimpleCrudQueryable = { - async create(object: ReimbursementInitializer): Promise { - throw new Error("Method not implemented."); - }, +const tableName = "reimbursement"; +const pkName = "reimbursement_id"; - async read(reimbursementId: ReimbursementId): Promise { - throw new Error("Method not implemented."); - }, - - async readAll(): Promise { - throw new Error("Method not implemented."); - }, - - async update(reimbursementId: ReimbursementId, mutateObject: ReimbursementMutator): Promise { - throw new Error("Method not implemented."); - }, - - async delete(reimbursementId: ReimbursementId): Promise { - throw new Error("Method not implemented."); +class ReimbursementQuery extends SimpleCrudQueryable< + Reimbursement, + ReimbursementInitializer, + ReimbursementMutator, + ReimbursementId +> { + constructor() { + super(tableName, pkName); } -}; +} -export default { - ...simpleCrudQueries -}; +export default new ReimbursementQuery(); diff --git a/src/db/queries/TransactionItemQuery.ts b/src/db/queries/TransactionItemQuery.ts index 76ff3e2..5ba5e4e 100644 --- a/src/db/queries/TransactionItemQuery.ts +++ b/src/db/queries/TransactionItemQuery.ts @@ -4,59 +4,38 @@ import TransactionItem, {TransactionItemInitializer, TransactionItemMutator} from "../../types/db/public/TransactionItem"; import {CompositeCrudQueryable} from "../CompositeCrudQueryable"; -const compositeCrudQueries: - CompositeCrudQueryable -= { - async create(object: TransactionItemInitializer): Promise { - throw new Error("Method not implemented."); - }, - - async read(transactionId: TransactionId, itemId: ItemId): Promise { - throw new Error("Method not implemented."); - }, - - async readAll(): Promise { - throw new Error("Method not implemented."); - }, - - async update( - transactionId: TransactionId, - itemId: ItemId, - mutateObject: TransactionItemMutator - ): Promise { - throw new Error("Method not implemented."); - }, - - async delete(transactionId: TransactionId, itemId: ItemId): Promise { - throw new Error("Method not implemented."); +const tableName = "transaction_item"; +const pk1Name = "transaction_id"; +const pk2Name = "item_id"; + +class TransactionItemQuery extends CompositeCrudQueryable< + TransactionItem, + TransactionItemInitializer, + TransactionItemMutator, + TransactionId, + ItemId +> { + constructor() { + super(tableName, pk1Name, pk2Name); } -}; -const transactionItemQueries = { /** * Searches in the table for all TransactionItems that are linked to a particular item. * @param itemId Foreign key of item to search for * @returns Promise resolving to array of TransactionItems linked to the given itemId */ - async readAllFromItem(itemId: ItemId): Promise { + public readAllFromItem = async (itemId: ItemId): Promise => { throw new Error("Method not implemented."); - }, + }; /** * Searches in the table for all TransactionItems that are linked to a particular transaction. * @param transactionId Foreign key of transaction to search for * @returns Promise resolving to array of TransactionItems linked to the given transactionId */ - async readAllFromTransaction(transactionId: TransactionId): Promise { + public readAllFromTransaction = async (transactionId: TransactionId): Promise => { throw new Error("Method not implemented."); - } -}; + }; +} -export default { - ...compositeCrudQueries, - ...transactionItemQueries -}; +export default new TransactionItemQuery(); diff --git a/src/db/queries/TransactionQuery.ts b/src/db/queries/TransactionQuery.ts index 0e85fe3..21a23a8 100644 --- a/src/db/queries/TransactionQuery.ts +++ b/src/db/queries/TransactionQuery.ts @@ -3,29 +3,18 @@ import Transaction, {TransactionId, TransactionInitializer, TransactionMutator} from "../../types/db/public/Transaction"; import {SimpleCrudQueryable} from "../SimpleCrudQueryable"; -const simpleCrudQueries: -SimpleCrudQueryable = { - async create(object: TransactionInitializer): Promise { - throw new Error("Method not implemented."); - }, +const tableName = "transaction"; +const pkName = "transaction_id"; - async read(transactionId: TransactionId): Promise { - throw new Error("Method not implemented."); - }, - - async readAll(): Promise { - throw new Error("Method not implemented."); - }, - - async update(transactionId: TransactionId, mutateObject: TransactionMutator): Promise { - throw new Error("Method not implemented."); - }, - - async delete(transactionId: TransactionId): Promise { - throw new Error("Method not implemented."); +class TransactionQuery extends SimpleCrudQueryable< + Transaction, + TransactionInitializer, + TransactionMutator, + TransactionId +> { + constructor() { + super(tableName, pkName); } -}; +} -export default { - ...simpleCrudQueries -}; +export default new TransactionQuery(); diff --git a/src/db/queries/ValidCategoryQuery.ts b/src/db/queries/ValidCategoryQuery.ts index 6cc0b68..71196f8 100644 --- a/src/db/queries/ValidCategoryQuery.ts +++ b/src/db/queries/ValidCategoryQuery.ts @@ -3,29 +3,18 @@ import ValidCategory, {Category, ValidCategoryInitializer, ValidCategoryMutator} from "../../types/db/public/ValidCategory"; import {SimpleCrudQueryable} from "../SimpleCrudQueryable"; -const simpleCrudQueries: -SimpleCrudQueryable = { - async create(object: ValidCategoryInitializer): Promise { - throw new Error("Method not implemented."); - }, +const tableName = "valid_category"; +const pkName = "category"; - async read(category: Category): Promise { - throw new Error("Method not implemented."); - }, - - async readAll(): Promise { - throw new Error("Method not implemented."); - }, - - async update(category: Category, mutateObject: ValidCategoryMutator): Promise { - throw new Error("Method not implemented."); - }, - - async delete(category: Category): Promise { - throw new Error("Method not implemented."); +class ValidCategoryQuery extends SimpleCrudQueryable< + ValidCategory, + ValidCategoryInitializer, + ValidCategoryMutator, + Category +> { + constructor() { + super(tableName, pkName); } -}; +} -export default { - ...simpleCrudQueries -}; +export default new ValidCategoryQuery(); From aff358a4aa2cde8cc67bb48e9fb907b87dbe41dd Mon Sep 17 00:00:00 2001 From: Daniel Pan <57362494+daniel-panhead@users.noreply.github.com> Date: Wed, 21 Feb 2024 22:33:10 -0800 Subject: [PATCH 08/11] Fix ReimbursementQuery test datetimes; ValidCategoryQuery updating should fail since PKs cannot be updated --- src/db/queries/ValidCategoryQuery.ts | 5 +++++ test/db/queries/ReimbursementQuery.spec.ts | 4 ++-- test/db/queries/ValidCategory.spec.ts | 24 ++++++++++++++++------ 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/db/queries/ValidCategoryQuery.ts b/src/db/queries/ValidCategoryQuery.ts index 71196f8..4bd03cc 100644 --- a/src/db/queries/ValidCategoryQuery.ts +++ b/src/db/queries/ValidCategoryQuery.ts @@ -15,6 +15,11 @@ class ValidCategoryQuery extends SimpleCrudQueryable< constructor() { super(tableName, pkName); } + + // Do not allow updating since category is a primary key + public update = async (primaryKey: Category, mutateObject: ValidCategoryMutator): Promise => { + return null; + }; } export default new ValidCategoryQuery(); diff --git a/test/db/queries/ReimbursementQuery.spec.ts b/test/db/queries/ReimbursementQuery.spec.ts index 0f3b13d..57dbfd5 100644 --- a/test/db/queries/ReimbursementQuery.spec.ts +++ b/test/db/queries/ReimbursementQuery.spec.ts @@ -6,7 +6,7 @@ import * as TestItems from "../test_objs/Reimbursement"; const testReimbursementInitializer: ReimbursementInitializer = { receipt_img_url: "url3", purchase_total: BigInt(600), - purchase_date: new Date("2024-02-02"), + purchase_date: new Date("2024-02-02T08:00:00.000Z"), user_id: 3 }; @@ -25,7 +25,7 @@ describe("Reimbursement Query Tests", () => { testReadAll(ReimbursementQuery, Object.values(TestItems)); const reimbursementMutator: ReimbursementMutator = { - purchase_date: new Date("2024-01-01"), + purchase_date: new Date("2024-01-01T08:00:00.000Z"), reimbursed: true }; diff --git a/test/db/queries/ValidCategory.spec.ts b/test/db/queries/ValidCategory.spec.ts index d7fa3d8..4ee852c 100644 --- a/test/db/queries/ValidCategory.spec.ts +++ b/test/db/queries/ValidCategory.spec.ts @@ -1,8 +1,9 @@ import * as TestItems from "../test_objs/ValidCategory"; -import {testCreate, testDelete, testRead, testReadAll, testUpdate} from "./SimpleCrudQueryable"; +import {testCreate, testDelete, testRead, testReadAll} from "./SimpleCrudQueryable"; import ValidCategoryQuery from "../../../src/db/queries/ValidCategoryQuery"; import {ValidCategoryInitializer, ValidCategoryMutator} from "../../../src/types/db/public/ValidCategory"; +import {expect} from "chai"; const testValidCategoryInitializer: ValidCategoryInitializer = { category: "category" @@ -26,11 +27,22 @@ describe("ValidCategory Query Tests", () => { const validCategoryMutator: ValidCategoryMutator = { category: "wow" }; - testUpdate(ValidCategoryQuery, { - testInitializer: testValidCategoryInitializer, - testMutator: validCategoryMutator, - nonexistentId: "fake", - getId: (q) => q.category + describe("update()", () => { + it("cannot update existing category", async () => { + expect(await ValidCategoryQuery.update(TestItems.categoryDrink.category, validCategoryMutator)).to.be.null; + }); + it("returns null when updating nonexistent queryable", async () => { + expect(await ValidCategoryQuery.update("fake", validCategoryMutator)).to.be.null; + }); + it("returns null if given empty mutator", async () => { + const createdItem = await ValidCategoryQuery.create(testValidCategoryInitializer); + try { + expect(await ValidCategoryQuery.update(createdItem.category, {})).to.be.null; + } finally { + // cleanup + await ValidCategoryQuery.delete(createdItem.category); + } + }); }); testDelete(ValidCategoryQuery, { From 0dc5d21affcb311bd266d9dc7969af746afd4763 Mon Sep 17 00:00:00 2001 From: Daniel Pan <57362494+daniel-panhead@users.noreply.github.com> Date: Wed, 21 Feb 2024 22:36:41 -0800 Subject: [PATCH 09/11] Change crud queryable fields to be protected instead of private --- src/db/CompositeCrudQueryable.ts | 24 ++++++++++----------- src/db/SimpleCrudQueryable.ts | 18 ++++++++-------- src/db/queries/ReimbursementItemBoxQuery.ts | 4 ++-- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/db/CompositeCrudQueryable.ts b/src/db/CompositeCrudQueryable.ts index 362be76..d5250f7 100644 --- a/src/db/CompositeCrudQueryable.ts +++ b/src/db/CompositeCrudQueryable.ts @@ -1,14 +1,14 @@ import * as DB from "../db/DB"; export class CompositeCrudQueryable { - #tableName: string; - #pk1Name: string; - #pk2Name: string; + protected tableName: string; + protected pk1Name: string; + protected pk2Name: string; constructor(tableName: string, pk1Name: string, pk2Name: string) { - this.#tableName = tableName; - this.#pk1Name = pk1Name; - this.#pk2Name = pk2Name; + this.tableName = tableName; + this.pk1Name = pk1Name; + this.pk2Name = pk2Name; } /** @@ -22,7 +22,7 @@ export class CompositeCrudQueryable { const values = Object.values(object); const queryResponse = await DB.query( - `INSERT INTO ${this.#tableName} (${keys.join(",")})` + + `INSERT INTO ${this.tableName} (${keys.join(",")})` + `VALUES (${keys.map((prop, i) => `$${i + 1}`).join(",")})` + "RETURNING *", values @@ -42,7 +42,7 @@ export class CompositeCrudQueryable { */ public read = async (pk1: PK1, pk2: PK2): Promise => { const queryResponse = await DB.query( - `SELECT * FROM ${this.#tableName} WHERE ${this.#pk1Name}=$1 AND ${this.#pk2Name}=$2`, + `SELECT * FROM ${this.tableName} WHERE ${this.pk1Name}=$1 AND ${this.pk2Name}=$2`, [pk1, pk2] ); if (queryResponse.rows.length === 1) { @@ -57,7 +57,7 @@ export class CompositeCrudQueryable { * @returns Promise resolving to all entries of the object in its table in the database */ public readAll = async (): Promise => { - const queryResponse = await DB.query(`SELECT * FROM ${this.#tableName}`); + const queryResponse = await DB.query(`SELECT * FROM ${this.tableName}`); return queryResponse.rows; }; @@ -76,8 +76,8 @@ export class CompositeCrudQueryable { // Use i+3 for parameter so that $1 and $2 are reserved for the PKs const keys = Object.keys(mutateObject).map((prop, i) => `${prop}=$${i + 3}`); const queryResponse = await DB.query( - `UPDATE ${this.#tableName} SET ${keys.join(",")}` + - ` WHERE ${this.#pk1Name}=$1 AND ${this.#pk2Name}=$2 RETURNING *`, + `UPDATE ${this.tableName} SET ${keys.join(",")}` + + ` WHERE ${this.pk1Name}=$1 AND ${this.pk2Name}=$2 RETURNING *`, [pk1, pk2, ...Object.values(mutateObject)] ); if (queryResponse.rows.length === 1) { @@ -95,7 +95,7 @@ export class CompositeCrudQueryable { */ public delete = async (pk1: PK1, pk2: PK2): Promise => { const queryResponse = await DB.query( - `DELETE FROM ${this.#tableName} WHERE ${this.#pk1Name}=$1 AND ${this.#pk2Name}=$2`, + `DELETE FROM ${this.tableName} WHERE ${this.pk1Name}=$1 AND ${this.pk2Name}=$2`, [pk1, pk2] ); return queryResponse.rowCount === 1; diff --git a/src/db/SimpleCrudQueryable.ts b/src/db/SimpleCrudQueryable.ts index 7485583..16b736a 100644 --- a/src/db/SimpleCrudQueryable.ts +++ b/src/db/SimpleCrudQueryable.ts @@ -1,12 +1,12 @@ import * as DB from "../db/DB"; export class SimpleCrudQueryable { - #tableName: string; - #pkName: string; + protected tableName: string; + protected pkName: string; constructor(tableName: string, pkName: string) { - this.#tableName = tableName; - this.#pkName = pkName; + this.tableName = tableName; + this.pkName = pkName; } /** @@ -20,7 +20,7 @@ export class SimpleCrudQueryable { const values = Object.values(object); const queryResponse = await DB.query( - `INSERT INTO ${this.#tableName} (${keys.join(",")})` + + `INSERT INTO ${this.tableName} (${keys.join(",")})` + `VALUES (${keys.map((prop, i) => `$${i + 1}`).join(",")})` + "RETURNING *", values @@ -39,7 +39,7 @@ export class SimpleCrudQueryable { */ public read = async (primaryKey: PK): Promise => { const queryResponse = await DB.query( - `SELECT * FROM ${this.#tableName} WHERE ${this.#pkName}=$1`, + `SELECT * FROM ${this.tableName} WHERE ${this.pkName}=$1`, [primaryKey] ); if (queryResponse.rows.length === 1) { @@ -54,7 +54,7 @@ export class SimpleCrudQueryable { * @returns Promise resolving to all entries of the object in its table in the database */ public readAll = async (): Promise => { - const queryResponse = await DB.query(`SELECT * FROM ${this.#tableName}`); + const queryResponse = await DB.query(`SELECT * FROM ${this.tableName}`); return queryResponse.rows; }; @@ -72,7 +72,7 @@ export class SimpleCrudQueryable { // Use i+2 for parameter so that $1 is reserved for the item id const keys = Object.keys(mutateObject).map((prop, i) => `${prop}=$${i + 2}`); const queryResponse = await DB.query( - `UPDATE ${this.#tableName} SET ${keys.join(",")} WHERE ${this.#pkName}=$1 RETURNING *`, + `UPDATE ${this.tableName} SET ${keys.join(",")} WHERE ${this.pkName}=$1 RETURNING *`, [primaryKey, ...Object.values(mutateObject)] ); if (queryResponse.rows.length === 1) { @@ -89,7 +89,7 @@ export class SimpleCrudQueryable { */ public delete = async (primaryKey: PK): Promise => { const queryResponse = await DB.query( - `DELETE FROM ${this.#tableName} WHERE ${this.#pkName}=$1`, + `DELETE FROM ${this.tableName} WHERE ${this.pkName}=$1`, [primaryKey] ); return queryResponse.rowCount === 1; diff --git a/src/db/queries/ReimbursementItemBoxQuery.ts b/src/db/queries/ReimbursementItemBoxQuery.ts index 32bc946..9883846 100644 --- a/src/db/queries/ReimbursementItemBoxQuery.ts +++ b/src/db/queries/ReimbursementItemBoxQuery.ts @@ -27,7 +27,7 @@ class ReimbursementItemBoxQuery extends CompositeCrudQueryable< */ public readAllFromItemBox = async (itemBoxId: ItemBoxId): Promise => { const queryResponse = await DB.query( - "SELECT * FROM reimbursement_item_box WHERE item_box_id=$1", + `SELECT * FROM ${this.tableName} WHERE ${this.pk2Name}=$1`, [itemBoxId] ); return queryResponse.rows; @@ -40,7 +40,7 @@ class ReimbursementItemBoxQuery extends CompositeCrudQueryable< */ public readAllFromReimbursement = async (reimbursementId: ReimbursementId): Promise => { const queryResponse = await DB.query( - "SELECT * FROM reimbursement_item_box WHERE reimbursement_id=$1", + `SELECT * FROM ${this.tableName} WHERE ${this.pk1Name}=$1`, [reimbursementId] ); return queryResponse.rows; From 097894f96395acaf0c3a3e7dec2b9161e212f255 Mon Sep 17 00:00:00 2001 From: Daniel Pan <57362494+daniel-panhead@users.noreply.github.com> Date: Wed, 21 Feb 2024 22:41:31 -0800 Subject: [PATCH 10/11] Finish implementing TransactionItemQuery and add guards to composite queryable tests to clean up created objects --- src/db/queries/TransactionItemQuery.ts | 13 +++++++++++-- test/db/queries/ReimbursementItemBoxQuery.spec.ts | 12 +++++++----- test/db/queries/TransactionItemQuery.spec.ts | 12 +++++++----- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/db/queries/TransactionItemQuery.ts b/src/db/queries/TransactionItemQuery.ts index 5ba5e4e..4ff9def 100644 --- a/src/db/queries/TransactionItemQuery.ts +++ b/src/db/queries/TransactionItemQuery.ts @@ -3,6 +3,7 @@ import {TransactionId} from "../../types/db/public/Transaction"; import TransactionItem, {TransactionItemInitializer, TransactionItemMutator} from "../../types/db/public/TransactionItem"; import {CompositeCrudQueryable} from "../CompositeCrudQueryable"; +import * as DB from "../../db/DB"; const tableName = "transaction_item"; const pk1Name = "transaction_id"; @@ -25,7 +26,11 @@ class TransactionItemQuery extends CompositeCrudQueryable< * @returns Promise resolving to array of TransactionItems linked to the given itemId */ public readAllFromItem = async (itemId: ItemId): Promise => { - throw new Error("Method not implemented."); + const queryResponse = await DB.query( + `SELECT * FROM ${this.tableName} WHERE ${this.pk2Name}=$1`, + [itemId] + ); + return queryResponse.rows; }; /** @@ -34,7 +39,11 @@ class TransactionItemQuery extends CompositeCrudQueryable< * @returns Promise resolving to array of TransactionItems linked to the given transactionId */ public readAllFromTransaction = async (transactionId: TransactionId): Promise => { - throw new Error("Method not implemented."); + const queryResponse = await DB.query( + `SELECT * FROM ${this.tableName} WHERE ${this.pk1Name}=$1`, + [transactionId] + ); + return queryResponse.rows; }; } diff --git a/test/db/queries/ReimbursementItemBoxQuery.spec.ts b/test/db/queries/ReimbursementItemBoxQuery.spec.ts index 45f1798..4933215 100644 --- a/test/db/queries/ReimbursementItemBoxQuery.spec.ts +++ b/test/db/queries/ReimbursementItemBoxQuery.spec.ts @@ -54,11 +54,13 @@ describe("ReimbursementItemBox Query Tests", () => { describe("readAllFromItemBox()", () => { it("returns all ReimbursementItemBoxes with the given itemBoxId", async () => { const testRIB = await ReimbursementItemBoxQuery.create(testRIBInitializer); - const ramenRIBs = await ReimbursementItemBoxQuery.readAllFromItemBox(2); - expect(ramenRIBs).to.have.deep.members([testRIB, TestItems.reimbursementTwoRamen]); - - // cleanup - await ReimbursementItemBoxQuery.delete(testRIB.reimbursement_id, testRIB.item_box_id); + try { + const ramenRIBs = await ReimbursementItemBoxQuery.readAllFromItemBox(2); + expect(ramenRIBs).to.have.deep.members([testRIB, TestItems.reimbursementTwoRamen]); + } finally { + // cleanup + await ReimbursementItemBoxQuery.delete(testRIB.reimbursement_id, testRIB.item_box_id); + } }); it("returns empty array if no ReimbursementItemBoxes match given itemBoxId", async () => { expect(await ReimbursementItemBoxQuery.readAllFromItemBox(-1)).to.be.empty; diff --git a/test/db/queries/TransactionItemQuery.spec.ts b/test/db/queries/TransactionItemQuery.spec.ts index 0e2066a..09619c9 100644 --- a/test/db/queries/TransactionItemQuery.spec.ts +++ b/test/db/queries/TransactionItemQuery.spec.ts @@ -51,11 +51,13 @@ describe("TransactionItem Query Tests", () => { describe("readAllFromItem()", () => { it("returns all ReimbursementItems with the given itemId", async () => { const testTI = await TransactionItemQuery.create(testTIInitializer); - const ramenRIBs = await TransactionItemQuery.readAllFromItem(3); - expect(ramenRIBs).to.have.deep.members([testTI, TestItems.transactionOneCola]); - - // cleanup - await TransactionItemQuery.delete(testTI.transaction_id, testTI.item_id); + try { + const ramenRIBs = await TransactionItemQuery.readAllFromItem(3); + expect(ramenRIBs).to.have.deep.members([testTI, TestItems.transactionOneCola]); + } finally { + // cleanup + await TransactionItemQuery.delete(testTI.transaction_id, testTI.item_id); + } }); it("returns empty array if no ReimbursementItems match given itemId", async () => { expect(await TransactionItemQuery.readAllFromItem(-1)).to.be.empty; From 6947312495b733f73c4d26fa53a6020aeefde39c Mon Sep 17 00:00:00 2001 From: Daniel Pan <57362494+daniel-panhead@users.noreply.github.com> Date: Wed, 21 Feb 2024 22:44:54 -0800 Subject: [PATCH 11/11] Finish implementing CsssUserQuery --- src/db/queries/CsssUserQuery.ts | 11 ++++++++++- test/db/queries/CsssUserQuery.spec.ts | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/db/queries/CsssUserQuery.ts b/src/db/queries/CsssUserQuery.ts index 29f834b..afbcb9f 100644 --- a/src/db/queries/CsssUserQuery.ts +++ b/src/db/queries/CsssUserQuery.ts @@ -2,6 +2,7 @@ import CsssUser, {UserId, CsssUserInitializer, CsssUserMutator} from "../../types/db/public/CsssUser"; import {SimpleCrudQueryable} from "../SimpleCrudQueryable"; +import * as DB from "../../db/DB"; const tableName = "csss_user"; const pkName = "user_id"; @@ -18,7 +19,15 @@ class CsssUserQuery extends SimpleCrudQueryable => { - throw new Error("Method not implemented."); + const queryResponse = await DB.query( + `SELECT * FROM ${this.tableName} WHERE email=$1 AND password=$2`, + [email, password] + ); + if (queryResponse.rows.length === 1) { + return queryResponse.rows[0]; + } else { + return null; + } }; } diff --git a/test/db/queries/CsssUserQuery.spec.ts b/test/db/queries/CsssUserQuery.spec.ts index 31f810d..4674522 100644 --- a/test/db/queries/CsssUserQuery.spec.ts +++ b/test/db/queries/CsssUserQuery.spec.ts @@ -49,7 +49,7 @@ describe("CsssUser Query Tests", () => { describe("authenticateUser()", () => { it("returns user with matching credentials", async () => { expect(await CsssUserQuery.authenticateUser("george@ubccsss.org", "hash2")) - .to.equal(TestItems.csssUserGeorge); + .to.deep.equal(TestItems.csssUserGeorge); }); it("returns null when username matches but password is incorrect", async () => { expect(await CsssUserQuery.authenticateUser("george@ubccsss.org", "wrong")).to.be.null;