All files / app/core/services/metadata metadata.service.ts

97.14% Statements 34/35
77.77% Branches 7/9
100% Functions 11/11
96.96% Lines 32/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146                              1x 10x                         3x 1x     2x                         4x   4x 1x     3x   3x 1x     2x                             1x   1x                     1x                 1x       1x 2x 2x                   1x       1x 2x 2x                   1x 1x   1x 3x 3x 3x   3x 3x             1x    
import { Injectable } from '@angular/core';
import { DisotService } from '../disot.service';
import { 
  MetadataContent, 
  isMetadataContent 
} from '../../domain/interfaces/metadata-entry';
import { DisotEntry, DisotEntryType } from '../../domain/interfaces/disot.interface';
 
/**
 * Service for managing metadata entries
 * Follows Single Responsibility Principle - only handles metadata operations
 */
@Injectable({
  providedIn: 'root'
})
export class MetadataService {
  constructor(private readonly disotService: DisotService) {}
 
  /**
   * Creates a new metadata entry
   * @param content The metadata content
   * @param privateKey Private key for signing
   * @returns The created DISOT entry
   */
  async createMetadataEntry(
    content: MetadataContent,
    privateKey: string
  ): Promise<DisotEntry> {
    // Validate metadata before creating
    if (!isMetadataContent(content)) {
      throw new Error('Invalid metadata content');
    }
 
    return this.disotService.createEntry(
      content,
      DisotEntryType.METADATA,
      privateKey
    );
  }
 
  /**
   * Retrieves metadata content from an entry
   * @param entryId ID of the metadata entry
   * @returns The metadata content
   */
  async getMetadataContent(entryId: string): Promise<MetadataContent> {
    const entry = await this.disotService.getEntry(entryId);
    
    if (entry.type !== DisotEntryType.METADATA) {
      throw new Error('Entry is not a metadata entry');
    }
    
    const metadata = entry.metadata as MetadataContent;
    
    if (!isMetadataContent(metadata)) {
      throw new Error('Invalid metadata content');
    }
    
    return metadata;
  }
 
  /**
   * Updates metadata by creating a new version
   * @param previousId ID of the previous version
   * @param updates Partial updates to apply
   * @param privateKey Private key for signing
   * @returns The new metadata entry
   */
  async updateMetadataEntry(
    previousId: string,
    updates: Partial<MetadataContent>,
    privateKey: string
  ): Promise<DisotEntry> {
    const previousContent = await this.getMetadataContent(previousId);
    
    const newContent: MetadataContent = {
      ...previousContent,
      ...updates,
      timestamp: Date.now(),
      version: {
        ...previousContent.version,
        ...updates.version,
        previousVersion: previousId
      }
    };
    
    return this.createMetadataEntry(newContent, privateKey);
  }
 
  /**
   * Finds metadata entries that reference a specific content hash
   * @param contentHash The content hash to search for
   * @returns Array of entries containing the reference
   */
  async findByReference(contentHash: string): Promise<DisotEntry[]> {
    const entries = await this.disotService.listEntries({ 
      type: DisotEntryType.METADATA 
    });
    
    return entries.filter(entry => {
      const metadata = entry.metadata as MetadataContent;
      return metadata?.references?.some(ref => ref.hash === contentHash) ?? false;
    });
  }
 
  /**
   * Finds metadata entries by author
   * @param authorHash The author hash to search for
   * @returns Array of entries by the author
   */
  async findByAuthor(authorHash: string): Promise<DisotEntry[]> {
    const entries = await this.disotService.listEntries({ 
      type: DisotEntryType.METADATA 
    });
    
    return entries.filter(entry => {
      const metadata = entry.metadata as MetadataContent;
      return metadata?.authors?.some(author => author.authorHash === authorHash) ?? false;
    });
  }
 
  /**
   * Gets the complete version history of a metadata entry
   * @param metadataId ID of any entry in the version chain
   * @returns Array of entries from newest to oldest
   */
  async getVersionHistory(metadataId: string): Promise<DisotEntry[]> {
    const history: DisotEntry[] = [];
    let currentId = metadataId;
    
    while (currentId) {
      try {
        const entry = await this.disotService.getEntry(currentId);
        history.push(entry);
        
        const metadata = entry.metadata as MetadataContent;
        currentId = metadata?.version?.previousVersion || '';
      } catch {
        // End of chain or error
        break;
      }
    }
    
    return history;
  }
}