All files internal-url-adapter.js

76.92% Statements 40/52
75% Branches 27/36
61.53% Functions 8/13
76.47% Lines 39/51

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 147 148 149 150 151 152 153 154 155 156 157 158 159            1x 1x       20x 20x     20x     20x 1x                   17x 1x       16x 16x 16x     16x 16x 1x       15x 15x     15x                   2x         2x 2x   2x               1x 1x 1x 1x                                                         16x 7x     9x 3x     8x 7x 7x 10x   7x     1x                                         1x             2x             1x
/**
 * Internal URL Adapter
 * Převádí abstraktní internal:// URL na skutečné endpointy
 * Skrývá implementační detaily před službami
 */
 
const fs = require('fs');
const runtimeCfg = require('./config');
 
class InternalUrlAdapter {
  constructor(config = {}) {
    this.environment = runtimeCfg.get('nodeEnv', config.environment);
    this.dockerNetwork = runtimeCfg.get('dockerNetwork', config.dockerNetwork);
 
    // Mapování služeb na jejich interní adresy
    this.serviceMap = { ...runtimeCfg.get('internalUrlServiceMap') };
 
    // Rozšiřitelné přes config
    if (config.services) {
      Object.assign(this.serviceMap, config.services);
    }
  }
 
  /**
   * Převede internal:// URL na skutečnou adresu
   * @param {string} internalUrl - URL ve formátu internal://service/path
   * @returns {string} Skutečná HTTP/HTTPS URL
   */
  resolve(internalUrl) {
    if (!internalUrl || !internalUrl.startsWith('internal://')) {
      return internalUrl; // Není internal URL, vrátíme jak je
    }
 
    // Parsování internal://service/path
    const urlParts = internalUrl.replace('internal://', '').split('/');
    const serviceName = urlParts[0];
    const path = urlParts.slice(1).join('/');
 
    // Najdeme mapování pro službu
    const serviceConfig = this.serviceMap[serviceName];
    if (!serviceConfig) {
      throw new Error(`Unknown service: ${serviceName}`);
    }
 
    // Určíme endpoint podle prostředí
    const isDocker = this.dockerNetwork || this.isRunningInDocker();
    const endpoint = isDocker ? serviceConfig.docker : serviceConfig.local;
 
    // Sestavíme skutečnou URL
    return `http://${endpoint}/${path}`;
  }
 
  /**
   * Převede skutečnou URL zpět na internal://
   * @param {string} actualUrl - Skutečná HTTP URL
   * @param {string} serviceName - Název služby
   * @returns {string} Internal URL
   */
  toInternal(actualUrl, serviceName) {
    Iif (!actualUrl || !serviceName) {
      return actualUrl;
    }
 
    // Odstranit protokol a host
    const url = new URL(actualUrl);
    const path = url.pathname + url.search + url.hash;
 
    return `internal://${serviceName}${path}`;
  }
 
  /**
   * Detekce Docker prostředí
   */
  isRunningInDocker() {
    // Několik způsobů detekce Docker kontejneru
    const dockerEnv = runtimeCfg.get('dockerEnv');
    const dockerContainer = runtimeCfg.get('dockerContainer');
    const hostname = runtimeCfg.get('hostname');
    return !!(
      dockerEnv ||
      dockerContainer ||
      (fs.existsSync('/.dockerenv')) ||
      (hostname && hostname.length === 12) // Docker container ID
    );
  }
 
  /**
   * Middleware pro Express - automaticky resolvuje internal:// URL v odpovědích
   */
  expressMiddleware() {
    return (req, res, next) => {
      const originalJson = res.json;
 
      res.json = (data) => {
        // Rekurzivně projít response a nahradit internal:// URL
        const transformed = this.transformResponse(data);
        return originalJson.call(res, transformed);
      };
 
      next();
    };
  }
 
  /**
   * Rekurzivní transformace odpovědi
   */
  transformResponse(obj) {
    if (typeof obj === 'string' && obj.startsWith('internal://')) {
      return this.resolve(obj);
    }
 
    if (Array.isArray(obj)) {
      return obj.map(item => this.transformResponse(item));
    }
 
    if (obj && typeof obj === 'object') {
      const transformed = {};
      for (const [key, value] of Object.entries(obj)) {
        transformed[key] = this.transformResponse(value);
      }
      return transformed;
    }
 
    return obj;
  }
 
  /**
   * HTTP client interceptor - automaticky resolvuje internal:// URL v požadavcích
   */
  axiosInterceptor(axios) {
    axios.interceptors.request.use((config) => {
      if (config.url && config.url.startsWith('internal://')) {
        config.url = this.resolve(config.url);
      }
      return config;
    });
 
    return axios;
  }
 
  /**
   * Získat všechny dostupné služby
   */
  getAvailableServices() {
    return Object.keys(this.serviceMap);
  }
 
  /**
   * Přidat nebo aktualizovat mapování služby
   */
  registerService(name, dockerEndpoint, localEndpoint) {
    this.serviceMap[name] = {
      docker: dockerEndpoint,
      local: localEndpoint
    };
  }
}
 
module.exports = InternalUrlAdapter;