frontend/src/app/services/category/category.service.ts
import { Injectable } from "@angular/core";import { Category } from "src/app/models/category/category";import { InventoryService } from "../inventory/inventory.service";import { EventSourcingService, itemType, Event, crudType} from "../EventSourcing/event-sourcing.service";import { AuthService } from "../auth/auth.service";import { StockService } from "../stock/stock.service";import { v4 } from "uuid"; @Injectable({ providedIn: "root"})export class CategoryService { /** * The current state of Categories of all Inventories * * (a projection from the event logs) */ private static inventoryCategoriesProjection: { [inventoryUuid: string]: Category[]; }; /** * The Categories projection * * Usage: * `[inventoryUuid][thingUuid]` */ get categories() { return CategoryService.inventoryCategoriesProjection; } /** * 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 (CategoryService.inventoryCategoriesProjection == null) { this.fetchAllInventoryCategories().then(result => { // Mark as ready resolve(null); }); } else { // Mark as ready resolve(null); } }); } /** * Iterates over all Inventories and their Things and fetches their Stocks */Function `fetchAllInventoryCategories` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring. private async fetchAllInventoryCategories() { // Wait for the other services to be ready await this.is.ready; await this.ess.ready; // Initialize the dict CategoryService.inventoryCategoriesProjection = {}; // Iterate over the Inventories projection for (const inventoryUuid in this.is.inventories) { if (this.is.inventories.hasOwnProperty(inventoryUuid)) { // Initialize the outer array for the Inventory CategoryService.inventoryCategoriesProjection[inventoryUuid] = []; } } // Iterate over all Inventory-UUIDs of the EventsLogsSimilar blocks of code found in 2 locations. Consider refactoring. for (const inventoryEvents of EventSourcingService.events) { // Iterate over the Events of the Inventory for (const event of inventoryEvents.events) { // But only if when the Event is a CategoryEvent if (event.data.itemType === itemType.CATEGORY) { try { // Apply the Event await this.applyCategoryEvent(event); } catch (err) { // Ignore errors caused by deleted Things } } } } } /** * Modifies the current Category projection by applying an event * * This does not modify any stored data */Function `applyCategoryEvent` has 59 lines of code (exceeds 25 allowed). Consider refactoring.
Function `applyCategoryEvent` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring. private async applyCategoryEvent(categoryEvent: Event) { await this.is.ready; const newCategory = { name: categoryEvent.data.categoryData?.name, uuid: categoryEvent.data.uuid, parentUuid: categoryEvent.data.categoryData?.parentUuid, createdOn: categoryEvent.data.categoryData?.createdOn, children: [] } as Category; /** * The Category of the event in the projection, if any */ const existingCategory = this.getCategoryByUuid( categoryEvent.inventoryUuid, categoryEvent.data.uuid ); switch (categoryEvent.data.crudType) { case crudType.DELETE: // Avoid deleting what doesn't exist if (existingCategory == null) { throw new Error("The category does not exist"); } // Check if the Category is top-level if (existingCategory.parentUuid === "root") { // Find the index of the Category const index = CategoryService.inventoryCategoriesProjection[ categoryEvent.inventoryUuid ].findIndex(c => c.uuid === categoryEvent.data.uuid); // Remove a category from the projection CategoryService.inventoryCategoriesProjection[ categoryEvent.inventoryUuid ].splice(index, 1); } else { /** * The parent category to delete from */ const parentCategory = this.getCategoryByUuid( categoryEvent.inventoryUuid, existingCategory.parentUuid ); // Find the index of the Category const index = parentCategory.children.findIndex( c => c.uuid === categoryEvent.data.uuid ); // Remove a category from the projection parentCategory.children.splice(index, 1); } break; case crudType.CREATE: // Check, if th parent UUID is null if (existingCategory != null) { throw new Error("The category already exists"); } // Check if the Category should be top-level if (categoryEvent.data.categoryData.parentUuid === "root") { // Push a new Stock onto the Projection CategoryService.inventoryCategoriesProjection[ categoryEvent.inventoryUuid ].push(newCategory); } else { /** * The category to which to add the new category to */ const parent = this.getCategoryByUuid( categoryEvent.inventoryUuid, categoryEvent.data.categoryData.parentUuid ); // Push to the parent Category parent.children.push(newCategory); } break; case crudType.UPDATE: // Check, if th parent UUID is null if (existingCategory == null) { throw new Error("The category does not exist"); } // Assign the changed values existingCategory.name = newCategory.name; existingCategory.parentUuid = newCategory.parentUuid; break; } } /** * Recursively finds and returns a category from the projection * * @param uuid The UUID of the Category to be found */ public getCategoryByUuid( inventoryUuid: string, categoryUuid: string ): Category { // Loop over every root-level category in the inventory for (const rootLevelCategory of CategoryService .inventoryCategoriesProjection[inventoryUuid]) { const result = this.getCategoryByUuidRecursive( rootLevelCategory, categoryUuid ); if (result != null) { return result; } } // If the category could not be found, return null return null; } /** * The recursive implementation of the Category search * * @param baseCategory The category on which to start the search * @param targetCategoryUuid The category UUID to search for * * @returns The category to search for, null if not found */ private getCategoryByUuidRecursive( baseCategory: Category, targetCategoryUuid: string ): Category { // Check if this is the right category if (baseCategory.uuid === targetCategoryUuid) { return baseCategory; } // If the UUID doesn't match, continue searching for (const c of baseCategory.children) { // Make a recursive call with the child const result = this.getCategoryByUuidRecursive(c, targetCategoryUuid); // If the recursive call was successful, return the result if (result !== null) { return result; } } // If nothing was found return null return null; } /** * Creates a Category * * @param category The category to be created * @param parentUuid The UUID of the parent of the category, root gets "" * @param inventoryUuid The UUID of the Inventory */ async createCategory( name: string, parentUuid: string, inventoryUuid: string ): Promise<void> { /** * A date object which represents the current moment in time */ const now = new Date(); /** * The event to be appended to the event log */ const event = { date: now, inventoryUuid, data: { uuid: v4(), crudType: crudType.CREATE, itemType: itemType.CATEGORY, userUuid: (await this.as.getCurrentUser()).user.uuid, categoryData: { createdOn: now, name, parentUuid } } } as Event; // Write the event to the event log and the API await this.ess.appendEventToInventoryLog(event); // Update the categories projection await this.applyCategoryEvent(event); } /** * Updates a Category * * @param category The Category to be updated * @param inventoryUuid The UUID of the Inventory */ async updateCategory( category: Category, inventoryUuid: string ): Promise<void> { const now = new Date(); const event = { inventoryUuid, date: now, data: { crudType: crudType.UPDATE, itemType: itemType.CATEGORY, userUuid: (await this.as.getCurrentUser()).user.uuid, uuid: category.uuid, categoryData: {} } } as Event; // Avoid removing unchanged data if (category.name != null) { event.data.categoryData.name = category.name; } if (category.parentUuid != null) { event.data.categoryData.parentUuid = category.parentUuid; } await this.ess.appendEventToInventoryLog(event); await this.applyCategoryEvent(event); } /** * Deletes a Category * * @param category The Category to be deleted * @param inventoryUuid The UUID of the Inventory */ async deleteCategory( category: Category, inventoryUuid: string ): Promise<void> { const now = new Date(); const event = { inventoryUuid, date: now,Similar blocks of code found in 2 locations. Consider refactoring. data: { crudType: crudType.DELETE, itemType: itemType.CATEGORY, userUuid: (await this.as.getCurrentUser()).user.uuid, uuid: category.uuid } } as Event; await this.ess.appendEventToInventoryLog(event); await this.applyCategoryEvent(event); }}