Bernd-L/exDateMan

View on GitHub
frontend/src/app/services/thing/thing.service.ts

Summary

Maintainability
B
6 hrs
Test Coverage
import { Injectable } from "@angular/core";
import { Thing } from "../../models/thing/thing";
import { InventoryService } from "../inventory/inventory.service";
import {
EventSourcingService,
crudType,
itemType,
Event
} from "../EventSourcing/event-sourcing.service";
import { AuthService } from "../auth/auth.service";
 
@Injectable({
providedIn: "root"
})
export class ThingService {
/**
* The current state of things of all inventories (a projection from the event logs)
*/
private static inventoryTingsProjection: { [uuid: string]: Thing[] };
 
/**
* Public inventoryTingsProjection accessor
*/
get things() {
return ThingService.inventoryTingsProjection;
}
 
/**
* Sneaky stuff
*
* Used to get around the "no async constructors" limitation
*/
public ready: Promise<any>;
 
Similar blocks of code found in 2 locations. Consider refactoring.
constructor(
private is: InventoryService,
private ess: EventSourcingService,
private as: AuthService
) {
// Ready declaration
this.ready = new Promise((resolve, reject) => {
if (ThingService.inventoryTingsProjection == null) {
this.fetchAllInventoryThings().then(result => {
// Mark as ready
resolve(null);
});
} else {
// Mark as ready
resolve(null);
}
});
}
 
/**
* Iterates over all inventories and fetches their things
*/
async fetchAllInventoryThings() {
// Wait for the InventoryService & EventSourcingService to be ready
await this.is.ready;
await this.ess.ready;
 
// Initialize the array
ThingService.inventoryTingsProjection = ([] as unknown) as {
[uuid: string]: Thing[];
};
 
for (const inventory in this.is.inventories) {
if (this.is.inventories.hasOwnProperty(inventory)) {
const uuid = this.is.inventories[inventory].uuid;
 
// Initialize the inner array
ThingService.inventoryTingsProjection[uuid] = [];
 
// Fetch and apply the Things of the Inventory
await this.fetchInventoryThings(uuid);
}
}
}
 
/**
* Fetches an inventories' things and applies them to a projection
*
* @param inventoryUuid The UUID of the inventory to be fetched
*/
async fetchInventoryThings(inventoryUuid: string) {
// Iterate over the events
for (const event of EventSourcingService.events.find(
ep => ep.uuid === inventoryUuid
).events) {
// Only apply Thing events
if (event.data.itemType !== itemType.THING) {
continue;
}
 
// Apply the event
this.applyThingEvent(event, inventoryUuid);
}
}
 
/**
* Applies an Event to the Things projection
*
* @param event The Event to be applied
* @param inventoryUuid The UUID of the Inventory
*/
Function `applyThingEvent` has 27 lines of code (exceeds 25 allowed). Consider refactoring.
private applyThingEvent(event: Event, inventoryUuid: string) {
/**
* The index of the event in the projection, if any
*/
const index = ThingService.inventoryTingsProjection[
inventoryUuid
].findIndex(thing => thing.uuid === event.data.uuid);
 
switch (event.data.crudType) {
// Handle Thing creation
case crudType.CREATE:
ThingService.inventoryTingsProjection[inventoryUuid].push({
uuid: event.data.uuid,
name: event.data.thingData.name,
categoryUuids: event.data.thingData.categoryUuids,
createdOn: event.data.thingData.createdOn
});
break;
 
// Handle Thing alteration
case crudType.UPDATE:
// Only update from values not equaling null
if (event.data.thingData.name != null) {
ThingService.inventoryTingsProjection[inventoryUuid][index].name =
event.data.thingData.name;
}
 
if (event.data.thingData.categoryUuids != null) {
ThingService.inventoryTingsProjection[inventoryUuid][
index
].categoryUuids = event.data.thingData.categoryUuids;
}
break;
 
// Handle Thing deletion
case crudType.DELETE:
ThingService.inventoryTingsProjection[inventoryUuid].splice(index, 1);
break;
}
}
 
/**
* Creates a thing by adding a thingCreatedEvent to the event log
*
* @param thing The thing to be created
* @param inventoryUuid THhe UUID of the things inventory
*/
async newThing(thing: Thing, inventoryUuid: string): Promise<void> {
// Wait for ThingService to be ready
await this.ready;
 
/**
* The current date
*
* This is stored in a constant, in order to have the exact same date for
* both the createdOn of the Thing and the date of the Event itself.
*/
const now = new Date();
 
/**
* The new-inventory event
*/
const newThingEvent: Event = {
inventoryUuid,
date: now,
data: {
userUuid: (await this.as.getCurrentUser()).user.uuid,
crudType: crudType.CREATE,
itemType: itemType.THING,
uuid: thing.uuid,
thingData: {
name: thing.name,
createdOn: now,
categoryUuids: thing.categoryUuids
}
}
};
 
// Push the event to the API and append it to the log
await this.ess.appendEventToInventoryLog(newThingEvent);
this.applyThingEvent(newThingEvent, inventoryUuid);
document.dispatchEvent(
new CustomEvent("new-thing", { detail: newThingEvent })
);
}
 
/**
* Updates a Thing by issuing a new Event, sending it to the DB via the API,
* adding it to the local Event log and applying it immediately to the
* projection.
*
* @param thing The thing to be updated with the new values
* @param inventoryUuid The UUID of the Things Inventory
*/
async updateThing(thing: Thing, inventoryUuid: string): Promise<void> {
const event: Event = {
date: new Date(),
inventoryUuid,
data: {
itemType: itemType.THING,
crudType: crudType.UPDATE,
userUuid: (await this.as.getCurrentUser()).user.uuid,
uuid: thing.uuid,
thingData: {
name: thing.name,
categoryUuids: thing.categoryUuids
}
}
};
 
await this.ess.appendEventToInventoryLog(event);
this.applyThingEvent(event, inventoryUuid);
}
 
/**
* Removes a Thing by issuing a new Event, sending it to the DB via the API,
* adding it to the local Event log and applying it immediately to the
* projection.
*
* @param thing The thing to be removed
* @param inventoryUuid The UUID of the Things Inventory
*/
async removeThing(thing: Thing, inventoryUuid: string): Promise<void> {
const event: Event = {
date: new Date(),
inventoryUuid,
Similar blocks of code found in 2 locations. Consider refactoring.
data: {
itemType: itemType.THING,
crudType: crudType.DELETE,
userUuid: (await this.as.getCurrentUser()).user.uuid,
uuid: thing.uuid
}
};
 
await this.ess.appendEventToInventoryLog(event);
this.applyThingEvent(event, inventoryUuid);
}
}