🔨 Refactor ORM operations as helpers

This commit is contained in:
Iván Ovejero 2021-04-13 10:25:36 +02:00
parent cfc8aa75ac
commit 9249c6d622
2 changed files with 118 additions and 110 deletions

View file

@ -585,20 +585,7 @@ class App {
return undefined;
}
result.tags = await getConnection()
.createQueryBuilder()
.select('tag_entity.id', 'id')
.addSelect('tag_entity.name', 'name')
.from('tag_entity', 'tag_entity')
.where(qb => {
return "id IN " + qb.subQuery()
.select('tagId')
.from('workflow_entity', 'workflow_entity')
.leftJoin('workflows_tags', 'workflows_tags', 'workflows_tags.workflowId = workflow_entity.id')
.where("workflow_entity.id = :id", { id: Number(req.params.id) })
.getQuery();
})
.getRawMany();
result.tags = await TagHelpers.getWorkflowTags(req.params.id);
// Convert to response format in which the id is a string
(result as IWorkflowBase as IWorkflowResponse).id = result.id.toString();
@ -718,40 +705,6 @@ class App {
return true;
}));
// Adds a tag to a workflow
this.app.post(`/${this.restEndpoint}/workflows/:workflowId/tags/:tagId`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<{ workflowId: number, tagId: number }> => {
const workflowId = Number(req.params.workflowId);
const tagId = Number(req.params.tagId);
await TagHelpers.validateId(tagId);
await TagHelpers.validateNoRelation(workflowId, tagId);
await getConnection().createQueryBuilder()
.insert()
.into('workflows_tags')
.values([ { workflowId, tagId } ])
.execute();
return { workflowId, tagId };
}));
// Removes a tag from a workflow
this.app.delete(`/${this.restEndpoint}/workflows/:workflowId/tags/:tagId`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<boolean> => {
const workflowId = Number(req.params.workflowId);
const tagId = Number(req.params.tagId);
await TagHelpers.validateId(tagId);
await TagHelpers.validateRelation(workflowId, tagId);
await getConnection().createQueryBuilder()
.delete()
.from('workflows_tags')
.where('workflowId = :workflowId AND tagId = :tagId', { workflowId, tagId })
.execute();
return true;
}));
this.app.post(`/${this.restEndpoint}/workflows/run`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<IExecutionPushResponse> => {
const workflowData = req.body.workflowData;
const runData: IRunData | undefined = req.body.runData;
@ -801,26 +754,9 @@ class App {
// Retrieves all tags, with or without usage count
this.app.get(`/${this.restEndpoint}/tags`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<Array<{ id: number, name: string, usageCount?: number }>> => {
const withUsageCount = req.query.withUsageCount === 'true';
if (withUsageCount) {
return await getConnection().createQueryBuilder()
.select('tag_entity.id', 'id')
.addSelect('tag_entity.name', 'name')
.addSelect('COUNT(workflow_entity.id)', 'usageCount')
.from('tag_entity', 'tag_entity')
.leftJoin('workflows_tags', 'workflows_tags', 'workflows_tags.tagId = tag_entity.id')
.leftJoin('workflow_entity', 'workflow_entity', 'workflows_tags.workflowId = workflow_entity.id')
.groupBy('tag_entity.id')
.getRawMany();
}
return await getConnection().createQueryBuilder()
.select('tag_entity.id', 'id')
.addSelect('tag_entity.name', 'name')
.from('tag_entity', 'tag_entity')
.groupBy('tag_entity.id')
.getRawMany();
return req.query.withUsageCount === 'true'
? await TagHelpers.getAllTagsWithUsageCount()
: await TagHelpers.getAllTags();
}));
// Creates a tag
@ -828,8 +764,8 @@ class App {
TagHelpers.validateRequestBody(req.body);
const { name } = req.body;
await TagHelpers.validateName(name);
TagHelpers.validateLength(name);
await TagHelpers.validateName(name);
const newTag: ITagBase = {
name,
@ -855,8 +791,8 @@ class App {
TagHelpers.validateRequestBody(req.body);
const { name } = req.body;
await TagHelpers.validateName(name);
TagHelpers.validateLength(name);
await TagHelpers.validateName(name);
const id = Number(req.params.id);
await TagHelpers.validateId(id);

View file

@ -1,5 +1,4 @@
import {
FindManyOptions,
FindOneOptions,
getConnection,
In,
@ -10,17 +9,10 @@ import {
ResponseHelper,
} from ".";
/**
* Validate whether a tag name exists so that it cannot be used for a tag create or tag update operation.
*/
export async function validateName(name: string): Promise<void> | never {
const findQuery = { where: { name } } as FindOneOptions;
const tag = await Db.collections.Tag!.findOne(findQuery);
if (tag) {
throw new ResponseHelper.ResponseError('Tag name already exists.', undefined, 400);
}
}
// ----------------------------------
// validators
// ----------------------------------
/**
* Validate whether a tag ID exists so that it can be used for a workflow create or tag update operation.
@ -44,11 +36,14 @@ export function validateLength(name: string): void | never {
}
/**
* Validate whether the request body for a create/update operation has a `name` property.
* Validate whether a tag name exists so that it cannot be used for a tag create or tag update operation.
*/
export function validateRequestBody({ name }: { name: string }): void | never {
if (!name) {
throw new ResponseHelper.ResponseError(`Property 'name' missing from request body.`, undefined, 400);
export async function validateName(name: string): Promise<void> | never {
const findQuery = { where: { name } } as FindOneOptions;
const tag = await Db.collections.Tag!.findOne(findQuery);
if (tag) {
throw new ResponseHelper.ResponseError('Tag name already exists.', undefined, 400);
}
}
@ -56,7 +51,7 @@ export function validateRequestBody({ name }: { name: string }): void | never {
* Validate that a tag and a workflow are not related so that a link can be created.
*/
export async function validateNoRelation(workflowId: number, tagId: number): Promise<void> | never {
const result = await findRelation(workflowId, tagId);
const result = await findRelations(workflowId, tagId);
if (result.length) {
throw new ResponseHelper.ResponseError(`Workflow ID ${workflowId} and tag ID ${tagId} are already related.`, undefined, 400);
@ -67,7 +62,7 @@ export async function validateNoRelation(workflowId: number, tagId: number): Pro
* Validate that a tag and a workflow are related so that their link can be deleted.
*/
export async function validateRelation(workflowId: number, tagId: number): Promise<void> | never {
const result = await findRelation(workflowId, tagId);
const result = await findRelations(workflowId, tagId);
if (!result.length) {
throw new ResponseHelper.ResponseError(`Workflow ID ${workflowId} and tag ID ${tagId} are not related.`, undefined, 400);
@ -75,9 +70,70 @@ export async function validateRelation(workflowId: number, tagId: number): Promi
}
/**
* Find a relation between a workflow and a tag, if any.
* Validate whether the request body for a create/update operation has a `name` property.
*/
async function findRelation(workflowId: number, tagId: number): Promise<Array<{ workflowId: number, tagId: number }>> {
export function validateRequestBody({ name }: { name: string | undefined }): void | never {
if (!name) {
throw new ResponseHelper.ResponseError(`Property 'name' missing from request body.`, undefined, 400);
}
}
// ----------------------------------
// queries
// ----------------------------------
/**
* Retrieve all existing tags, whether linked to a workflow or not.
*/
export async function getAllTags(): Promise<Array<{ id: number; name: string }>> {
return await getConnection().createQueryBuilder()
.select('tag_entity.id', 'id')
.addSelect('tag_entity.name', 'name')
.from('tag_entity', 'tag_entity')
.groupBy('tag_entity.id')
.getRawMany();
}
/**
* Retrieve all existing tags, whether linked to a workflow or not,
* including how many workflows each tag is linked to.
*/
export async function getAllTagsWithUsageCount(): Promise<Array<{
id: number;
name: string;
usageCount: number
}>> {
return await getConnection().createQueryBuilder()
.select('tag_entity.id', 'id')
.addSelect('tag_entity.name', 'name')
.addSelect('COUNT(workflow_entity.id)', 'usageCount')
.from('tag_entity', 'tag_entity')
.leftJoin('workflows_tags', 'workflows_tags', 'workflows_tags.tagId = tag_entity.id')
.leftJoin('workflow_entity', 'workflow_entity', 'workflows_tags.workflowId = workflow_entity.id')
.groupBy('tag_entity.id')
.getRawMany();
}
/**
* Retrieve tag IDs and names, to be used in an API response.
*/
export async function getTagsForResponseData(
tagIds: number[]
): Promise<Array<{ id: number; name: string }>> {
return await Db.collections.Tag!.find({
select: ['id', 'name'],
where: { id: In(tagIds) },
});
}
/**
* Find if a workflow and a tag are related.
*/
async function findRelations(
workflowId: number,
tagId: number
): Promise<Array<{ workflowId: number, tagId: number }>> {
return await getConnection().createQueryBuilder()
.select()
.from('workflows_tags', 'workflows_tags')
@ -85,6 +141,43 @@ async function findRelation(workflowId: number, tagId: number): Promise<Array<{
.execute();
}
/**
* Retrieve the tags linked to a single workflow.
*/
export async function getWorkflowTags(
workflowId: string
): Promise<Array<{ id: number; name: string }>> {
return await getConnection().createQueryBuilder()
.select('tag_entity.id', 'id')
.addSelect('tag_entity.name', 'name')
.from('tag_entity', 'tag_entity')
.where(qb => {
return "id IN " + qb.subQuery()
.select('tagId')
.from('workflow_entity', 'workflow_entity')
.leftJoin('workflows_tags', 'workflows_tags', 'workflows_tags.workflowId = workflow_entity.id')
.where("workflow_entity.id = :id", { id: workflowId })
.getQuery();
})
.getRawMany();
}
// ----------------------------------
// mutations
// ----------------------------------
/**
* Link a workflow to one or more tags.
*/
export async function createTagWorkflowRelations(workflowId: string, tagIds: number[]) {
await getConnection().createQueryBuilder()
.insert()
.into('workflows_tags')
.values(tagIds.map(tagId => ({ workflowId, tagId })))
.execute();
}
/**
* Remove all tags for a workflow during a tag update operation.
*/
@ -95,24 +188,3 @@ export async function deleteAllTagsForWorkflow(workflowId: string) {
.where('workflowId = :id', { id: workflowId })
.execute();
}
/**
* Associate a workflow with one or many tags.
*/
export async function createTagWorkflowRelations(workflowId: string, tagIds: number[]) {
await getConnection().createQueryBuilder()
.insert()
.into('workflows_tags')
.values(tagIds.map(tagId => ({ workflowId, tagId })))
.execute();
}
/**
* Return tag IDs and names, only for use in a response.
*/
export async function getTagsForResponseData(tagIds: number[]) {
return await Db.collections.Tag!.find({
select: ['id', 'name'],
where: { id: In(tagIds) },
});
}