Bernd-L/exDateMan

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

Summary

Maintainability
D
1 day
Test Coverage
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 operation
Similar 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);
}
}