pmb0/express-sharp

View on GitHub
src/image-url.service.ts

Summary

Maintainability
A
0 mins
Test Coverage
import { inject, injectable } from 'tsyringe'
import { URL } from 'url'
import { ConfigService } from './config.service'
import { QueryParams, Signer } from './interfaces'
import { ResizeDto } from './resize.dto'
import { UrlSigner } from './signed-url.service'

@injectable()
export class ImageUrl {
  constructor(
    @inject('endpoint') private readonly endpoint: string,
    @inject(UrlSigner) private readonly urlSigner: Signer,
    private readonly config: ConfigService,
  ) {}

  private _buildUrl(
    imageId: string,
    params: Partial<Omit<ResizeDto, 'url'>>,
  ): URL {
    const url = new URL(this.endpoint)

    // Endpoint w/ search params not supported
    url.search = ''

    url.pathname += imageId
    url.pathname = url.pathname.replace(/\/\/+/, '')

    Object.entries(params)
      .filter(([, value]) => value !== undefined)
      .sort()
      .forEach(([name, value]) => {
        url.searchParams.set(
          QueryParams[name as Exclude<keyof ResizeDto, 'url'>],

          // Type Guard in .filter() does not work:
          // > A type predicate cannot reference element 'value' in a binding
          // > pattern.
          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
          value.toString(),
        )
      })

    if (this.config.get('signedUrl.secret')) {
      this.urlSigner.sign(url)
    }

    return url
  }

  url(imageId: string, params: Partial<Omit<ResizeDto, 'url'>>): string {
    return this._buildUrl(imageId, params).toString()
  }

  pathQuery(imageId: string, params: Partial<Omit<ResizeDto, 'url'>>): string {
    const url = this._buildUrl(imageId, params)
    return url.pathname + url.search
  }
}