src/*: code refactor and comments

Some refactoring in the database utilitie functions.
pull/20/head
leonnicolas 4 years ago
parent 1545cce614
commit f80f619ed9
No known key found for this signature in database
GPG Key ID: 088D0743E2B65C07

@ -26,7 +26,7 @@ import { Equipment } from '../../model/Equipment';
import { Engagement } from '../../model/Engagement'; import { Engagement } from '../../model/Engagement';
import { Provider } from '../../model/Provider'; import { Provider } from '../../model/Provider';
import { TimeFrame } from '../../model/TimeFrame'; import { TimeFrame } from '../../model/TimeFrame';
import { ActionLogger, deleteEntity, getAllEntity, LockUtils } from './utils'; import { ActionLogger, DBUtils, LockUtils } from './utils';
import { EquipmentType } from '../../model/EquipmentType'; import { EquipmentType } from '../../model/EquipmentType';
import { BikeEventType } from '../../model/BikeEventType'; import { BikeEventType } from '../../model/BikeEventType';
import { UserInputError } from 'apollo-server-express'; import { UserInputError } from 'apollo-server-express';
@ -43,7 +43,7 @@ export class CargoBikeAPI extends DataSource {
} }
async getCargoBikes (offset?: number, limit?: number) { async getCargoBikes (offset?: number, limit?: number) {
return await getAllEntity(this.connection, CargoBike, 'cb', offset, limit); return await DBUtils.getAllEntity(this.connection, CargoBike, 'cb', offset, limit);
} }
/** /**
@ -192,11 +192,11 @@ export class CargoBikeAPI extends DataSource {
} }
async deleteBikeEventType (id: number, userId: number) { async deleteBikeEventType (id: number, userId: number) {
return await deleteEntity(this.connection, BikeEventType, 'bet', id, userId); return await DBUtils.deleteEntity(this.connection, BikeEventType, 'bet', id, userId);
} }
async deleteBikeEvent (id: number, userId: number) { async deleteBikeEvent (id: number, userId: number) {
return await deleteEntity(this.connection, BikeEvent, 'be', id, userId); return await DBUtils.deleteEntity(this.connection, BikeEvent, 'be', id, userId);
} }
async cargoBikeByEventId (id: number) { async cargoBikeByEventId (id: number) {
@ -270,11 +270,11 @@ export class CargoBikeAPI extends DataSource {
} }
async bikeEventTypes (offset?: number, limit?: number) { async bikeEventTypes (offset?: number, limit?: number) {
return await getAllEntity(this.connection, BikeEventType, 'bet', offset, limit); return await DBUtils.getAllEntity(this.connection, BikeEventType, 'bet', offset, limit);
} }
async bikeEvents (offset?: number, limit?: number) { async bikeEvents (offset?: number, limit?: number) {
return await getAllEntity(this.connection, BikeEvent, 'be', offset, limit); return await DBUtils.getAllEntity(this.connection, BikeEvent, 'be', offset, limit);
} }
async bikeEventTypeById (id: number) { async bikeEventTypeById (id: number) {
@ -408,7 +408,7 @@ export class CargoBikeAPI extends DataSource {
} }
async deleteEquipment (id: number, userId: number) { async deleteEquipment (id: number, userId: number) {
return await deleteEntity(this.connection, Equipment, 'e', id, userId); return await DBUtils.deleteEntity(this.connection, Equipment, 'e', id, userId);
} }
async getEquipment (offset: number, limit: number) { async getEquipment (offset: number, limit: number) {
@ -462,7 +462,7 @@ export class CargoBikeAPI extends DataSource {
} }
async deleteEquipmentType (id:number, userId: number) { async deleteEquipmentType (id:number, userId: number) {
return await deleteEntity(this.connection, EquipmentType, 'et', id, userId); return await DBUtils.deleteEntity(this.connection, EquipmentType, 'et', id, userId);
} }
async equipmentTypeById (id: number) { async equipmentTypeById (id: number) {

@ -21,7 +21,7 @@ import { DataSource } from 'apollo-datasource';
import { Connection, EntityManager, getConnection } from 'typeorm'; import { Connection, EntityManager, getConnection } from 'typeorm';
import { ContactInformation } from '../../model/ContactInformation'; import { ContactInformation } from '../../model/ContactInformation';
import { Person } from '../../model/Person'; import { Person } from '../../model/Person';
import { ActionLogger, deleteEntity, getAllEntity, LockUtils } from './utils'; import { ActionLogger, DBUtils, LockUtils } from './utils';
import { GraphQLError } from 'graphql'; import { GraphQLError } from 'graphql';
import { LendingStation } from '../../model/LendingStation'; import { LendingStation } from '../../model/LendingStation';
@ -33,7 +33,7 @@ export class ContactInformationAPI extends DataSource {
} }
async contactInformation (offset?: number, limit?: number) { async contactInformation (offset?: number, limit?: number) {
return await getAllEntity(this.connection, ContactInformation, 'ci', offset, limit); return await DBUtils.getAllEntity(this.connection, ContactInformation, 'ci', offset, limit);
} }
async contactInformationById (id: number) { async contactInformationById (id: number) {
@ -83,11 +83,11 @@ export class ContactInformationAPI extends DataSource {
} }
async deletePerson (id: number, userId: number) { async deletePerson (id: number, userId: number) {
return await deleteEntity(this.connection, Person, 'p', id, userId); return await DBUtils.deleteEntity(this.connection, Person, 'p', id, userId);
} }
async persons (offset?: number, limit?: number) { async persons (offset?: number, limit?: number) {
return await getAllEntity(this.connection, Person, 'p', offset, limit); return await DBUtils.getAllEntity(this.connection, Person, 'p', offset, limit);
} }
async personById (id: number) { async personById (id: number) {
@ -161,7 +161,7 @@ export class ContactInformationAPI extends DataSource {
} }
async deleteContactInformation (id: number, userId: number) { async deleteContactInformation (id: number, userId: number) {
return await deleteEntity(this.connection, ContactInformation, 'ci', id, userId); return await DBUtils.deleteEntity(this.connection, ContactInformation, 'ci', id, userId);
} }
async contactInformationByPersonId (id: number) { async contactInformationByPersonId (id: number) {

@ -24,7 +24,7 @@ import { Connection, EntityManager, getConnection } from 'typeorm';
import { CargoBike } from '../../model/CargoBike'; import { CargoBike } from '../../model/CargoBike';
import { LendingStation } from '../../model/LendingStation'; import { LendingStation } from '../../model/LendingStation';
import { TimeFrame } from '../../model/TimeFrame'; import { TimeFrame } from '../../model/TimeFrame';
import { ActionLogger, deleteEntity, genDateRange, getAllEntity, LockUtils } from './utils'; import { ActionLogger, genDateRange, DBUtils, LockUtils } from './utils';
export class LendingStationAPI extends DataSource { export class LendingStationAPI extends DataSource {
connection : Connection connection : Connection
@ -45,7 +45,7 @@ export class LendingStationAPI extends DataSource {
* get all lendingStations * get all lendingStations
*/ */
async lendingStations (offset?: number, limit?: number) { async lendingStations (offset?: number, limit?: number) {
return await getAllEntity(this.connection, LendingStation, 'ls', offset, limit); return await DBUtils.getAllEntity(this.connection, LendingStation, 'ls', offset, limit);
} }
/** /**
@ -74,7 +74,7 @@ export class LendingStationAPI extends DataSource {
} }
async timeFrames (offset?: number, limit?: number) { async timeFrames (offset?: number, limit?: number) {
return await getAllEntity(this.connection, TimeFrame, 'tf', offset, limit); return await DBUtils.getAllEntity(this.connection, TimeFrame, 'tf', offset, limit);
} }
async timeFramesByCargoBikeId (id: number) { async timeFramesByCargoBikeId (id: number) {
@ -179,7 +179,7 @@ export class LendingStationAPI extends DataSource {
} }
async deleteLendingStationById (id: number, userId: number) { async deleteLendingStationById (id: number, userId: number) {
return await deleteEntity(this.connection, LendingStation, 'ls', id, userId); return await DBUtils.deleteEntity(this.connection, LendingStation, 'ls', id, userId);
} }
async createTimeFrame (timeFrame: any) { async createTimeFrame (timeFrame: any) {
@ -257,6 +257,6 @@ export class LendingStationAPI extends DataSource {
} }
async deleteTimeFrame (id: number, userId: number) { async deleteTimeFrame (id: number, userId: number) {
return await deleteEntity(this.connection, TimeFrame, 'tf', id, userId); return await DBUtils.deleteEntity(this.connection, TimeFrame, 'tf', id, userId);
} }
} }

@ -23,7 +23,7 @@ import { ContactInformation } from '../../model/ContactInformation';
import { Engagement } from '../../model/Engagement'; import { Engagement } from '../../model/Engagement';
import { Participant } from '../../model/Participant'; import { Participant } from '../../model/Participant';
import { EngagementType } from '../../model/EngagementType'; import { EngagementType } from '../../model/EngagementType';
import { ActionLogger, deleteEntity, genDateRange, getAllEntity, LockUtils } from './utils'; import { ActionLogger, DBUtils, genDateRange, LockUtils } from './utils';
import { UserInputError } from 'apollo-server-express'; import { UserInputError } from 'apollo-server-express';
import { GraphQLError } from 'graphql'; import { GraphQLError } from 'graphql';
@ -43,7 +43,7 @@ export class ParticipantAPI extends DataSource {
} }
async getParticipants (offset?: number, limit?: number) { async getParticipants (offset?: number, limit?: number) {
return await getAllEntity(this.connection, Participant, 'p', offset, limit); return await DBUtils.getAllEntity(this.connection, Participant, 'p', offset, limit);
} }
async participantByEngagementId (id: number) { async participantByEngagementId (id: number) {
@ -110,7 +110,7 @@ export class ParticipantAPI extends DataSource {
} }
async engagements (offset?: number, limit?: number) { async engagements (offset?: number, limit?: number) {
return await getAllEntity(this.connection, Engagement, 'e', offset, limit); return await DBUtils.getAllEntity(this.connection, Engagement, 'e', offset, limit);
} }
async engagementById (id: number) { async engagementById (id: number) {
@ -130,7 +130,7 @@ export class ParticipantAPI extends DataSource {
} }
async engagementTypes (offset?: number, limit?: number) { async engagementTypes (offset?: number, limit?: number) {
return await getAllEntity(this.connection, Engagement, 'e', offset, limit); return await DBUtils.getAllEntity(this.connection, Engagement, 'e', offset, limit);
} }
async engagementTypeByEngagementId (id: number) { async engagementTypeByEngagementId (id: number) {
@ -223,7 +223,7 @@ export class ParticipantAPI extends DataSource {
} }
async deleteParticipant (id: number, userId: number) { async deleteParticipant (id: number, userId: number) {
return await deleteEntity(this.connection, Participant, 'p', id, userId); return await DBUtils.deleteEntity(this.connection, Participant, 'p', id, userId);
} }
async createEngagement (engagement: any) { async createEngagement (engagement: any) {
@ -292,7 +292,7 @@ export class ParticipantAPI extends DataSource {
} }
async deleteEngagement (id: number, userId: number) { async deleteEngagement (id: number, userId: number) {
return await deleteEntity(this.connection, Engagement, 'e', id, userId); return await DBUtils.deleteEntity(this.connection, Engagement, 'e', id, userId);
} }
async createEngagementType (engagementType: any) { async createEngagementType (engagementType: any) {
@ -334,6 +334,6 @@ export class ParticipantAPI extends DataSource {
} }
async deleteEngagementType (id: number, userId: number) { async deleteEngagementType (id: number, userId: number) {
return await deleteEntity(this.connection, EngagementType, 'et', id, userId); return await DBUtils.deleteEntity(this.connection, EngagementType, 'et', id, userId);
} }
} }

@ -24,7 +24,7 @@ import { Organisation } from '../../model/Organisation';
import { UserInputError } from 'apollo-server-express'; import { UserInputError } from 'apollo-server-express';
import { CargoBike } from '../../model/CargoBike'; import { CargoBike } from '../../model/CargoBike';
import { LendingStation } from '../../model/LendingStation'; import { LendingStation } from '../../model/LendingStation';
import { ActionLogger, deleteEntity, getAllEntity, LockUtils } from './utils'; import { ActionLogger, DBUtils, LockUtils } from './utils';
import { GraphQLError } from 'graphql'; import { GraphQLError } from 'graphql';
export class ProviderAPI extends DataSource { export class ProviderAPI extends DataSource {
@ -43,7 +43,7 @@ export class ProviderAPI extends DataSource {
} }
async provider (offset?: number, limit?: number) { async provider (offset?: number, limit?: number) {
return await getAllEntity(this.connection, Provider, 'p', offset, limit); return await DBUtils.getAllEntity(this.connection, Provider, 'p', offset, limit);
} }
async providerByOrganisationId (id: number) { async providerByOrganisationId (id: number) {
@ -71,7 +71,7 @@ export class ProviderAPI extends DataSource {
} }
async organisations (offset?: number, limit?: number) { async organisations (offset?: number, limit?: number) {
return await getAllEntity(this.connection, Organisation, 'o', offset, limit); return await DBUtils.getAllEntity(this.connection, Organisation, 'o', offset, limit);
} }
async organisationById (id: number) { async organisationById (id: number) {
@ -170,7 +170,7 @@ export class ProviderAPI extends DataSource {
} }
async deleteProvider (id: number, userId: number) { async deleteProvider (id: number, userId: number) {
return await deleteEntity(this.connection, Provider, 'p', id, userId); return await DBUtils.deleteEntity(this.connection, Provider, 'p', id, userId);
} }
async createOrganisation (organisation: any) { async createOrganisation (organisation: any) {
@ -213,6 +213,6 @@ export class ProviderAPI extends DataSource {
} }
async deleteOrganisation (id: number, userId: number) { async deleteOrganisation (id: number, userId: number) {
return await deleteEntity(this.connection, Organisation, 'o', id, userId); return await DBUtils.deleteEntity(this.connection, Organisation, 'o', id, userId);
} }
} }

@ -36,21 +36,38 @@ export function genDateRange (struct: any) {
} }
/** /**
* Can be used in resolvers to specify if entry is locked by other user. * Can be used in resolvers to specify, if entry is locked by other user.
* Returns true if locked by other user. * Returns true if locked by other user.
* @param parent * @param parent
* @param dataSources * @param req
* @param req user request
*/ */
export function isLocked (parent: any, { req }: { req: any }) { export function isLocked (parent: any, { req }: { req: any }) {
return req.userId !== parent.lockedBy && new Date() <= new Date(parent.lockedUntil); return req.userId !== parent.lockedBy && new Date() <= new Date(parent.lockedUntil);
} }
/**
* Can be used in resolvers to specify, if entry is locked by the current user.
* @param parent
* @param req
*/
export function isLockedByMe (parent: any, { req }: { req: any }) { export function isLockedByMe (parent: any, { req }: { req: any }) {
return req.userId === parent.lockedBy && new Date() <= new Date(parent.lockedUntil); return req.userId === parent.lockedBy && new Date() <= new Date(parent.lockedUntil);
} }
export async function deleteEntity (connection: Connection, target: ObjectType<Lockable>, alias: string, id: number, userId: number): Promise<Boolean> { /**
* Some utility functions for the database
*/
export class DBUtils {
/**
* Delete any instance of an entity that implements the Lockable interface.
* It must implement the interface, so it can be be ensured, that the instance is not locked by another user.
* @param connection
* @param target
* @param alias
* @param id
* @param userId
*/
static async deleteEntity (connection: Connection, target: ObjectType<Lockable>, alias: string, id: number, userId: number): Promise<Boolean> {
return await connection.transaction(async (entityManger: EntityManager) => { return await connection.transaction(async (entityManger: EntityManager) => {
if (await LockUtils.isLocked(entityManger, target, alias, id, userId)) { if (await LockUtils.isLocked(entityManger, target, alias, id, userId)) {
throw new UserInputError('Attempting to delete locked resource'); throw new UserInputError('Attempting to delete locked resource');
@ -62,9 +79,18 @@ export async function deleteEntity (connection: Connection, target: ObjectType<L
.where('id = :id', { id: id }) .where('id = :id', { id: id })
.execute().then(value => value.affected === 1); .execute().then(value => value.affected === 1);
}); });
} }
export async function getAllEntity (connection: Connection, target: ObjectType<any>, alias: string, offset?: number, limit?: number) { /**
* Return all instances of the given entity called target.
* When offset or limit is not specified, both values are ignored.
* @param connection
* @param target
* @param alias
* @param offset
* @param limit
*/
static async getAllEntity (connection: Connection, target: ObjectType<any>, alias: string, offset?: number, limit?: number) {
if (offset === null || limit === null) { if (offset === null || limit === null) {
return await connection.getRepository(target) return await connection.getRepository(target)
.createQueryBuilder(alias) .createQueryBuilder(alias)
@ -78,10 +104,25 @@ export async function getAllEntity (connection: Connection, target: ObjectType<a
.take(limit) .take(limit)
.getMany(); .getMany();
} }
}
} }
/**
* Some static functions for the locking feature.
*/
export class LockUtils { export class LockUtils {
static async findById (connection: Connection, target: ObjectType<Lockable>, alias: string, id: number): Promise<Lockable> { /**
* A helper function to find an instance of any entity that implements Lockable.
* It will throw an error, if nothing is found.
* Using this function only makes sense to use in the context of locking because there is no point in locking
* an instance that does not exist.
* @param connection
* @param target
* @param alias
* @param id
* @private
*/
private static async findById (connection: Connection, target: ObjectType<Lockable>, alias: string, id: number): Promise<Lockable> {
return await connection.getRepository(target) return await connection.getRepository(target)
.createQueryBuilder(alias) .createQueryBuilder(alias)
.select() .select()
@ -91,6 +132,17 @@ export class LockUtils {
}); });
} }
/**
* Lock an instance of an entity target that implements the Lockable interface and return that instance.
* If lock could not be set, it will still return the entity.
* If lock was set or not can be obtained by the field isLockedByMe in the graphql interface,
* or the the fields lockedBy and lockedUntil in the database.
* @param connection
* @param target
* @param alias
* @param id
* @param userId
*/
static async lockEntity (connection: Connection, target: ObjectType<Lockable>, alias: string, id: number, userId: number): Promise<Lockable> { static async lockEntity (connection: Connection, target: ObjectType<Lockable>, alias: string, id: number, userId: number): Promise<Lockable> {
const lock = await connection.getRepository(target) const lock = await connection.getRepository(target)
.createQueryBuilder(alias) .createQueryBuilder(alias)
@ -119,6 +171,17 @@ export class LockUtils {
return await this.findById(connection, target, alias, id); return await this.findById(connection, target, alias, id);
} }
/**
* Unlock an instance of an entity target that implements the Lockable interface and return that instance.
* If lock could not be unset, it will still return the entity.
* If lock was set or not can be obtained by the field isLockedByMe in the graphql interface,
* or the the fields lockedBy and lockedUntil in the database.
* @param connection
* @param target
* @param alias
* @param id
* @param userId
*/
static async unlockEntity (connection: Connection, target: ObjectType<Lockable>, alias: string, id: number, userId: number): Promise<Lockable> { static async unlockEntity (connection: Connection, target: ObjectType<Lockable>, alias: string, id: number, userId: number): Promise<Lockable> {
await connection.getRepository(target) await connection.getRepository(target)
.createQueryBuilder(alias) .createQueryBuilder(alias)
@ -161,7 +224,18 @@ export class LockUtils {
} }
} }
/**
* Some utility function for the logging features.
*/
export class ActionLogger { export class ActionLogger {
/**
* Create array of strings, that can be used to select them form the database.
* If you want to avoid logging all old values, for an update, but only the ones that are updated,
* use this function. If updates are null, ['*'] will be returned. Use this for delete actions.
* @param updates
* @param alias
* @private
*/
private static buildSelect (updates: any, alias: string) : string[] { private static buildSelect (updates: any, alias: string) : string[] {
// this hacky shit makes it possible to select subfields like the address or insurance data. Only one layer at the moment // this hacky shit makes it possible to select subfields like the address or insurance data. Only one layer at the moment
if (updates === null) { if (updates === null) {
@ -181,6 +255,16 @@ export class ActionLogger {
return ret; return ret;
} }
/**
* Insert an entry in the log. The log ist just another entity in the database.
* You can only use this in a transaction. So you have to pass an entity manager.
* @param em
* @param target
* @param alias
* @param updates
* @param userId
* @param action
*/
static async log (em: EntityManager, target: ObjectType<any>, alias: string, updates: any, userId: number, action: Actions = Actions.UPDATE) { static async log (em: EntityManager, target: ObjectType<any>, alias: string, updates: any, userId: number, action: Actions = Actions.UPDATE) {
const oldValues = await em.getRepository(target).createQueryBuilder(alias) const oldValues = await em.getRepository(target).createQueryBuilder(alias)
.select(this.buildSelect(updates, alias)) .select(this.buildSelect(updates, alias))

@ -21,7 +21,7 @@ import { DataSource } from 'apollo-datasource';
import { Connection, EntityManager, getConnection } from 'typeorm'; import { Connection, EntityManager, getConnection } from 'typeorm';
import { WorkshopType } from '../../model/WorkshopType'; import { WorkshopType } from '../../model/WorkshopType';
import { Workshop } from '../../model/Workshop'; import { Workshop } from '../../model/Workshop';
import { ActionLogger, deleteEntity, getAllEntity, LockUtils } from './utils'; import { ActionLogger, DBUtils, LockUtils } from './utils';
import { UserInputError } from 'apollo-server-express'; import { UserInputError } from 'apollo-server-express';
import { GraphQLError } from 'graphql'; import { GraphQLError } from 'graphql';
import { Participant } from '../../model/Participant'; import { Participant } from '../../model/Participant';
@ -73,7 +73,7 @@ export class WorkshopAPI extends DataSource {
} }
async deleteWorkshop (id: number, userId: number) { async deleteWorkshop (id: number, userId: number) {
return await deleteEntity(this.connection, Workshop, 'w', id, userId); return await DBUtils.deleteEntity(this.connection, Workshop, 'w', id, userId);
} }
async createWorkshopType (workshopType: any) { async createWorkshopType (workshopType: any) {
@ -115,7 +115,7 @@ export class WorkshopAPI extends DataSource {
} }
async deleteWorkshopType (id: number, userId: number) { async deleteWorkshopType (id: number, userId: number) {
return await deleteEntity(this.connection, WorkshopType, 'wt', id, userId); return await DBUtils.deleteEntity(this.connection, WorkshopType, 'wt', id, userId);
} }
async workshopTypeById (id: number) { async workshopTypeById (id: number) {
@ -127,7 +127,7 @@ export class WorkshopAPI extends DataSource {
} }
async workshopTypes (offset?: number, limit?: number) { async workshopTypes (offset?: number, limit?: number) {
return getAllEntity(this.connection, WorkshopType, 'wt', offset, limit); return DBUtils.getAllEntity(this.connection, WorkshopType, 'wt', offset, limit);
} }
async workshopById (id: number) { async workshopById (id: number) {
@ -144,7 +144,7 @@ export class WorkshopAPI extends DataSource {
* @param limit * @param limit
*/ */
async workshops (offset?: number, limit?: number) { async workshops (offset?: number, limit?: number) {
return await getAllEntity(this.connection, Workshop, 'w', offset, limit); return await DBUtils.getAllEntity(this.connection, Workshop, 'w', offset, limit);
} }
async trainer1ByWorkshopId (id: number) { async trainer1ByWorkshopId (id: number) {

Loading…
Cancel
Save