frontend/src/app/services/inventory/inventory.service.ts
import { Injectable } from "@angular/core";import { Inventory } from "../../models/inventory/inventory";import { EventSourcingService, crudType, itemType, Event} from "../EventSourcing/event-sourcing.service";import { v4 } from "uuid";import { AuthService } from "../auth/auth.service";import { AsyncConstructor } from "../../interfaces/async-constructor"; /** * This Service parses and projects inventory events. * * This class needs to await async operations before it can be used, but * TypeScript does not support await to be used in constructors, so the user * has to await the ready promise of this class. */@Injectable({ providedIn: "root"})export class InventoryService implements AsyncConstructor { constructor(private ess: EventSourcingService, private as: AuthService) { this.prepare(); } /** * Public accessor for the inventories projection */ get inventories(): { [uuid: string]: Inventory } { return InventoryService.inventoriesProjection; } /** * The current state of inventories (a projection from the event logs) */ private static inventoriesProjection: { [uuid: string]: Inventory }; /** * Sneaky stuff * * Used to get around the "no async constructors" limitation */ public ready: Promise<null>; /** * Prepares this class for operaton and resolves the ready promise when done */ private prepare() { // Ready declaration this.ready = new Promise((resolve, reject) => { if (InventoryService.inventoriesProjection == null) { document.addEventListener("auth", () => this.reFetchAll()); this.fetchInventoryEvents().then(result => { // Mark as ready resolve(null); }); } else { // Mark as ready resolve(null); } }); } /** * Re-fetches all events from the EventSourcingService */ public async reFetchAll(): Promise<void> { await this.ess.reFetchAll(); await this.fetchInventoryEvents(); } /** * Obtains the inventories event log from the db and parses them */Function `fetchInventoryEvents` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring. private async fetchInventoryEvents() { // Initialize the inventory projection dictionary InventoryService.inventoriesProjection = {}; // Wait for EventSourcingService to be ready await this.ess.ready; // Iterate over all event logs for (const eventLog of EventSourcingService.events) { // Iterate over the events in the log for (const event of eventLog.events) { // Check if the event is about an inventory if (event.data.itemType === itemType.INVENTORY) { // Update the inventories projection according to the event this.updateInventoriesProjection(event); } } } } /** * Updates the inventories projection, one event at a time * * @param event The event to be used to update the projection with */Function `updateInventoriesProjection` has 30 lines of code (exceeds 25 allowed). Consider refactoring. private updateInventoriesProjection(event: Event): Inventory { /** * The inventory to be created or updated (ignored for delete events) */Similar blocks of code found in 2 locations. Consider refactoring. const newInventory = { uuid: event.data.uuid, name: event.data.inventoryData.name, createdOn: event.data.inventoryData.createdOn, ownerUuid: event.data.inventoryData.ownerUuid, adminUuids: event.data.inventoryData.adminsUuids, writableUuids: event.data.inventoryData.WritablesUuids, readableUuids: event.data.inventoryData.readablesUuids }; // Find the right CRUD operationSimilar blocks of code found in 2 locations. Consider refactoring. switch (event.data.crudType) { case crudType.CREATE: // Assign a new inventory to the dictionary InventoryService.inventoriesProjection[ event.inventoryUuid ] = newInventory; // Return the new inventory return newInventory; case crudType.UPDATE: // Remove immutable values delete newInventory.uuid; delete newInventory.createdOn; // Delete all null values Object.keys(newInventory).forEach( key => newInventory[key] == null && delete newInventory[key] ); // Assign the changed values Object.assign( InventoryService.inventoriesProjection[event.inventoryUuid], newInventory ); // Return the updated inventory return InventoryService.inventoriesProjection[event.inventoryUuid]; case crudType.DELETE: // Delete the inventory delete InventoryService.inventoriesProjection[event.inventoryUuid]; // Return nothing (the inventory is gone) return null; } TODO found // throw new Error("Method not implemented."); // TODO implement inventories projection } /** * Creates an inventory by generating and issuing an appropriate event * * @param name The name of the new inventory * @returns The uuid of the new inventory */ async createInventory(name: string): Promise<Inventory> { /** * The uuid for the new inventory */ const newUuid = v4(); /** * The uuid of the user */ const myUuid = (await this.as.getCurrentUser()).user.uuid; /** * The current date (and time) */ const currentDate = new Date(); const createInventoryEvent = { inventoryUuid: newUuid, date: currentDate, data: { crudType: crudType.CREATE, itemType: itemType.INVENTORY, uuid: newUuid, userUuid: myUuid, // Core data inventoryData: { name, createdOn: currentDate, ownerUuid: myUuid, adminsUuids: [], WritablesUuids: [], readablesUuids: [] } } }; // Append to the event log await this.ess.appendEventToInventoryLog(createInventoryEvent); // Update the inventories projection return this.updateInventoriesProjection(createInventoryEvent); } /** * Updates the data of an inventory by generating and issuing an appropriate event * * @param inventory The inventory to be updated */ async updateInventory(inventory: Inventory): Promise<Inventory> {TODO found // TODO implement access rights #91 /** * The uuid of the user */ const myUuid = (await this.as.getCurrentUser()).user.uuid; // Create an inventory updated event const updateInventoryEvent = { // Event metadata inventoryUuid: inventory.uuid, date: new Date(), data: { // Generic data crudType: crudType.UPDATE, itemType: itemType.INVENTORY, uuid: inventory.uuid, userUuid: myUuid, // Core data inventoryData: { name: inventory.name, ownerUuid: inventory.ownerUuid, adminsUuids: inventory.adminUuids, WritablesUuids: inventory.writableUuids, readablesUuids: inventory.readableUuids } } } as Event; // Append to the event log await this.ess.appendEventToInventoryLog(updateInventoryEvent); // Update the inventories projection return this.updateInventoriesProjection(updateInventoryEvent); } /** * Updates the data of an inventory by generating and issuing an appropriate event * * @param inventory The inventory to be deleted */ async deleteInventory(inventory: Inventory): Promise<Inventory> { /** * The uuid of the user */ const myUuid = (await this.as.getCurrentUser()).user.uuid; // Create an inventory deleted event const deleteInventoryEvent = { // Event metadata inventoryUuid: inventory.uuid, date: new Date(), data: { // Generic data crudType: crudType.DELETE, itemType: itemType.INVENTORY, uuid: inventory.uuid, userUuid: myUuid } } as Event; // Append to the event log await this.ess.appendEventToInventoryLog(deleteInventoryEvent); // Update the inventories projection return this.updateInventoriesProjection(deleteInventoryEvent); }}