File

src/app/core/entity/entity-actions/entity-anonymize.service.ts

Description

Anonymize an entity including handling references with related entities. This service is usually used in combination with the EntityActionsService, which provides user confirmation processes around this.

Extends

CascadingEntityAction

Index

Methods

Constructor

constructor(entityMapper: EntityMapperService, schemaService: EntitySchemaService, entityRelationsService: EntityRelationsService, fileService: FileService)
Parameters :
Name Type Optional
entityMapper EntityMapperService No
schemaService EntitySchemaService No
entityRelationsService EntityRelationsService No
fileService FileService No

Methods

Async anonymizeEntity
anonymizeEntity(entity: Entity)

The actual anonymize action without user interactions.

Parameters :
Name Type Optional
entity Entity No
Protected Async cascadeActionToRelatedEntities
cascadeActionToRelatedEntities(entity: Entity, compositeAction: (relatedEntity: Entity,refField: string,entity: Entity) => void, aggregateAction: (relatedEntity: Entity,refField: string,entity: Entity) => void)
Inherited from CascadingEntityAction

Recursively call the given actions on all related entities that contain a reference to the given entity.

Returns an array of all affected related entities (excluding the given entity) in their state before the action to support an undo action.

Parameters :
Name Type Optional Description
entity Entity No
compositeAction function No

The method to be called on relationships with entityReferenceRole "composite"

aggregateAction function No

The method to be called on relationships with entityReferenceRole "aggregate" (this is also the default for any relationship)

import { Injectable } from "@angular/core";
import { EntityMapperService } from "../entity-mapper/entity-mapper.service";
import { EntitySchemaService } from "../schema/entity-schema.service";
import {
  CascadingActionResult,
  CascadingEntityAction,
} from "./cascading-entity-action";
import { firstValueFrom } from "rxjs";
import { FileDatatype } from "../../../features/file/file.datatype";
import { FileService } from "../../../features/file/file.service";
import { Entity } from "../model/entity";
import { asArray } from "app/utils/asArray";
import { Logging } from "../../logging/logging.service";
import { EntityRelationsService } from "../entity-mapper/entity-relations.service";

/**
 * Anonymize an entity including handling references with related entities.
 * This service is usually used in combination with the `EntityActionsService`, which provides user confirmation processes around this.
 */
@Injectable({
  providedIn: "root",
})
export class EntityAnonymizeService extends CascadingEntityAction {
  constructor(
    protected override entityMapper: EntityMapperService,
    protected override schemaService: EntitySchemaService,
    protected override entityRelationsService: EntityRelationsService,
    private fileService: FileService,
  ) {
    super(entityMapper, schemaService, entityRelationsService);
  }

  /**
   * The actual anonymize action without user interactions.
   * @param entity
   * @private
   */
  async anonymizeEntity(entity: Entity): Promise<CascadingActionResult> {
    if (!entity.getConstructor().hasPII) {
      // entity types that are generally without PII by default retain all fields
      // this should only be called through a cascade action anyway
      Logging.debug("Anonymize for entity without PII skipped", entity);
      return new CascadingActionResult();
    }

    const originalEntity = entity.copy();

    for (const [key, schema] of entity.getSchema().entries()) {
      if (entity[key] === undefined) {
        continue;
      }

      switch (schema.anonymize) {
        case "retain":
          break;
        case "retain-anonymized":
          await this.anonymizeProperty(entity, key);
          break;
        default:
          await this.removeProperty(entity, key);
      }
    }

    entity.anonymized = true;
    entity.inactive = true;

    await this.entityMapper.save(entity);

    const cascadeResult = await this.cascadeActionToRelatedEntities(
      entity,
      (e) => this.anonymizeEntity(e),
      (e) => this.keepEntityUnchanged(e),
    );

    return new CascadingActionResult([originalEntity]).mergeResults(
      cascadeResult,
    );
  }

  private async anonymizeProperty(entity: Entity, key: string) {
    const dataType = this.schemaService.getDatatypeOrDefault(
      entity.getSchema().get(key).dataType,
    );

    let anonymizedValue;
    if (entity.getSchema().get(key).isArray) {
      anonymizedValue = await Promise.all(
        asArray(entity[key]).map((v) =>
          dataType.anonymize(v, entity.getSchema().get(key), entity),
        ),
      );
    } else {
      anonymizedValue = await dataType.anonymize(
        entity[key],
        entity.getSchema().get(key),
        entity,
      );
    }
    entity[key] = anonymizedValue;
  }

  private async removeProperty(entity: Entity, key: string) {
    if (
      entity.getSchema().get(key).dataType === FileDatatype.dataType &&
      entity[key]
    ) {
      await firstValueFrom(this.fileService.removeFile(entity, key));
    }

    if (entity[key] !== undefined) {
      entity[key] = null;
    }
  }

  private async keepEntityUnchanged(e: Entity): Promise<CascadingActionResult> {
    return new CascadingActionResult([], e.getConstructor().hasPII ? [e] : []);
  }
}

results matching ""

    No results matching ""