Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finish implementing Queryable tests #15

Merged
merged 11 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions src/db/CompositeCrudQueryable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import * as DB from "../db/DB";

export class CompositeCrudQueryable<T, TInit, TMut, PK1, PK2> {
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;
}

/**
* 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
*/
public create = async (object: TInit): Promise<T> => {
// 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.
* @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
*/
public read = async (pk1: PK1, pk2: PK2): Promise<T> => {
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
*/
public readAll = async (): Promise<T[]> => {
const queryResponse = await DB.query(`SELECT * FROM ${this.tableName}`);
return queryResponse.rows;
};

/**
* 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
*/
public update = async (pk1: PK1, pk2: PK2, mutateObject: TMut): Promise<T> => {
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.
* @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
*/
public delete = async (pk1: PK1, pk2: PK2): Promise<boolean> => {
const queryResponse = await DB.query(
`DELETE FROM ${this.tableName} WHERE ${this.pk1Name}=$1 AND ${this.pk2Name}=$2`,
[pk1, pk2]
);
return queryResponse.rowCount === 1;
};
}
76 changes: 0 additions & 76 deletions src/db/Queryable.ts

This file was deleted.

97 changes: 97 additions & 0 deletions src/db/SimpleCrudQueryable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import * as DB from "../db/DB";

export class SimpleCrudQueryable<T, TInit, TMut, PK> {
protected tableName: string;
protected 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
*/
public create = async (object: TInit): Promise<T> => {
// 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
*/
public read = async (primaryKey: PK): Promise<T> => {
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
*/
public readAll = async (): Promise<T[]> => {
const queryResponse = await DB.query(`SELECT * FROM ${this.tableName}`);
return queryResponse.rows;
};

/**
* 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
*/
public update = async (primaryKey: PK, mutateObject: TMut): Promise<T> => {
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
*/
public delete = async (primaryKey: PK): Promise<boolean> => {
const queryResponse = await DB.query(
`DELETE FROM ${this.tableName} WHERE ${this.pkName}=$1`,
[primaryKey]
);
return queryResponse.rowCount === 1;
};
}
53 changes: 21 additions & 32 deletions src/db/queries/CsssUserQuery.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,34 @@

import CsssUser,
{UserId, CsssUserInitializer, CsssUserMutator} from "../../types/db/public/CsssUser";
import {SimpleCrudQueryable} from "../Queryable";
import {SimpleCrudQueryable} from "../SimpleCrudQueryable";
import * as DB from "../../db/DB";

const simpleCrudQueries:
SimpleCrudQueryable<CsssUser, CsssUserInitializer, CsssUserMutator, UserId> = {
async create(object: CsssUserInitializer): Promise<CsssUser> {
throw new Error("Method not implemented.");
},
const tableName = "csss_user";
const pkName = "user_id";

async read(userId: UserId): Promise<CsssUser> {
throw new Error("Method not implemented.");
},

// should we allow?
async readAll(): Promise<CsssUser[]> {
throw new Error("Method not implemented.");
},

async update(userId: UserId, mutateObject: CsssUserMutator): Promise<CsssUser> {
throw new Error("Method not implemented.");
},

async delete(userId: UserId): Promise<boolean> {
throw new Error("Method not implemented.");
class CsssUserQuery extends SimpleCrudQueryable<CsssUser, CsssUserInitializer, CsssUserMutator, UserId> {
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<CsssUser> {
throw new Error("Method not implemented.");
}
};

export default {
...simpleCrudQueries,
...csssUserQueries
};
public authenticateUser = async (email: string, password: string): Promise<CsssUser> => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
}
};
}

export default new CsssUserQuery();
32 changes: 8 additions & 24 deletions src/db/queries/ItemBoxQuery.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,14 @@

import ItemBox, {ItemBoxId, ItemBoxInitializer, ItemBoxMutator} from "../../types/db/public/ItemBox";
import {SimpleCrudQueryable} from "../Queryable";
import {SimpleCrudQueryable} from "../SimpleCrudQueryable";

const simpleCrudQueries:
SimpleCrudQueryable<ItemBox, ItemBoxInitializer, ItemBoxMutator, ItemBoxId> = {
async create(object: ItemBoxInitializer): Promise<ItemBox> {
throw new Error("Method not implemented.");
},
const tableName = "item_box";
const pkName = "item_box_id";

async read(itemBoxId: ItemBoxId): Promise<ItemBox> {
throw new Error("Method not implemented.");
},

async readAll(): Promise<ItemBox[]> {
throw new Error("Method not implemented.");
},

async update(itemBoxId: ItemBoxId, mutateObject: ItemBoxMutator): Promise<ItemBox> {
throw new Error("Method not implemented.");
},

async delete(itemBoxId: ItemBoxId): Promise<boolean> {
throw new Error("Method not implemented.");
class ItemBoxQuery extends SimpleCrudQueryable<ItemBox, ItemBoxInitializer, ItemBoxMutator, ItemBoxId> {
constructor() {
super(tableName, pkName);
}
};
}

export default {
...simpleCrudQueries
};
export default new ItemBoxQuery();
Loading
Loading