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

✨ Introduce new custom where condition construction #66

Merged
merged 10 commits into from
Dec 2, 2021
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@unocha/hpc-api-core",
"version": "0.10.0",
"version": "1.0.0",
"description": "Core libraries supporting HPC.Tools API Backend",
"license": "Apache-2.0",
"private": false,
Expand Down
39 changes: 22 additions & 17 deletions src/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
filterValidRoleStrings,
RolesGrant,
} from './roles';
import { Op } from '../db/util/conditions';

const randomBytes = promisify(crypto.randomBytes);

Expand Down Expand Up @@ -68,11 +69,11 @@ const activateInvitesForEmail = async (

const targets = new Map<AuthTargetId, AuthTarget>();
for (const target of await models.authTarget.find({
where: (builder) =>
builder.whereIn(
'id',
invites.map((i) => i.target)
),
where: {
id: {
[Op.IN]: invites.map((i) => i.target),
},
},
})) {
targets.set(target.id, target);
}
Expand Down Expand Up @@ -284,26 +285,30 @@ export const getRoleGrantsForUsers = async ({

// Get the grantees
const grantees = await models.authGrantee.find({
where: (builder) =>
builder.where('type', 'user').andWhere('granteeId', 'in', users),
where: {
type: 'user',
granteeId: {
[Op.IN]: users,
},
},
});
if (grantees.length === 0) {
return new Map();
}
const granteesById = organizeObjectsByUniqueProperty(grantees, 'id');
const grants = await models.authGrant.find({
where: (builder) =>
builder.whereIn(
'grantee',
grantees.map((g) => g.id)
),
where: {
grantee: {
[Op.IN]: grantees.map((g) => g.id),
},
},
});
const targets = await models.authTarget.find({
where: (builder) =>
builder.whereIn(
'id',
grants.map((g) => g.target)
),
where: {
id: {
[Op.IN]: grants.map((g) => g.target),
},
},
});
const targetsById = organizeObjectsByUniqueProperty(targets, 'id');

Expand Down
31 changes: 13 additions & 18 deletions src/db/fetching.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Table } from '.';
import { AnnotatedMap, organizeObjectsByUniqueValue } from '../util';
import { Database } from './type';
import { InstanceDataOfModel, Model } from './util/raw-model';
import {
InstanceOfVersionedModel,
Expand All @@ -10,7 +10,7 @@ import {
* Generic type that can be used to obtain the type of an instance of either a
* standard table, or a versioned table.
*/
type InstanceOf<T extends Database[keyof Database]> = T extends Model<any>
type InstanceOf<T extends Table> = T extends Model<any>
? InstanceDataOfModel<T>
: T extends VersionedModel<any, any, any>
? InstanceOfVersionedModel<T>
Expand All @@ -24,36 +24,31 @@ type InstanceOf<T extends Database[keyof Database]> = T extends Model<any>
* database, otherwise an error will be thrown
*/
export const findAndOrganizeObjectsByUniqueProperty = <
Table extends Database[keyof Database],
P extends keyof InstanceOf<Table>
T extends Table,
P extends keyof InstanceOf<T>
>(
table: Table,
fetch: (tbl: Table) => Promise<Iterable<InstanceOf<Table>>>,
table: T,
fetch: (tbl: T) => Promise<Iterable<InstanceOf<T>>>,
property: P
): Promise<
AnnotatedMap<Exclude<InstanceOf<Table>[P], null>, InstanceOf<Table>>
> => {
): Promise<AnnotatedMap<Exclude<InstanceOf<T>[P], null>, InstanceOf<T>>> => {
return findAndOrganizeObjectsByUniqueValue(table, fetch, (item) => {
const val = item[property];
if (val === null) {
throw new Error(`Unexpected null value in ${property} for ${item}`);
}
return val as Exclude<InstanceOf<Table>[P], null>;
return val as Exclude<InstanceOf<T>[P], null>;
});
};

/**
* Fetch a number of items of a particular type from the database,
* and organize them into an AnnotatedMap by a unique value.
*/
export const findAndOrganizeObjectsByUniqueValue = async <
Table extends Database[keyof Database],
V
>(
table: Table,
fetch: (tbl: Table) => Promise<Iterable<InstanceOf<Table>>>,
getValue: (i: InstanceOf<Table>) => Exclude<V, null>
): Promise<AnnotatedMap<Exclude<V, null>, InstanceOf<Table>>> => {
export const findAndOrganizeObjectsByUniqueValue = async <T extends Table, V>(
table: T,
fetch: (tbl: T) => Promise<Iterable<InstanceOf<T>>>,
getValue: (i: InstanceOf<T>) => Exclude<V, null>
): Promise<AnnotatedMap<Exclude<V, null>, InstanceOf<T>>> => {
const values = await fetch(table);
const tableName =
table._internals.type === 'single-table'
Expand Down
35 changes: 34 additions & 1 deletion src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,18 @@ import unitType from './models/unitType';
import usageYear from './models/usageYear';
import workflowStatusOption from './models/workflowStatusOption';
import workflowStatusOptionStep from './models/workflowStatusOptionStep';
import { Op, Cond } from './util/conditions';

export default (conn: Knex) => ({
/**
* Utilities that are frequently used, and should also be included as part of
* the model root for easy access.
*/
export const UTILS = {
Op,
Cond,
};

const initializeTables = (conn: Knex) => ({
attachment: attachment(conn),
attachmentPrototype: attachmentPrototype(conn),
attachmentVersion: attachmentVersion(conn),
Expand Down Expand Up @@ -194,3 +204,26 @@ export default (conn: Knex) => ({
workflowStatusOption: workflowStatusOption(conn),
workflowStatusOptionStep: workflowStatusOptionStep(conn),
});

export type Tables = ReturnType<typeof initializeTables>;

export type Table = Tables[keyof Tables];

const initializeRoot = (conn: Knex) => {
const _tables = initializeTables(conn);
return {
...UTILS,
..._tables,
/**
* Expose the tables grouped together under one object under the root to
* allow for easier iteration through each of them.
*
* (this is used in unit tests)
*/
_tables,
};
};

export type Database = ReturnType<typeof initializeRoot>;

export default initializeRoot;
4 changes: 1 addition & 3 deletions src/db/type.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
import type db from '.';

export type Database = ReturnType<typeof db>;
export type { Database } from '.';
Loading