ElectronicBabylonianLiterature/ebl-frontend

View on GitHub
src/fragmentarium/infrastructure/FragmentRepository.ts

Summary

Maintainability
D
1 day
Test Coverage
A
90%
import Promise from 'bluebird'
import _ from 'lodash'
import { stringify } from 'query-string'
import produce from 'immer'
import {
  Fragment,
  FragmentInfo,
  FragmentInfoDto,
  Script,
  ScriptDto,
} from 'fragmentarium/domain/fragment'
import { RecordEntry } from 'fragmentarium/domain/RecordEntry'
import Folio from 'fragmentarium/domain/Folio'
import { Museums, MuseumKey } from 'fragmentarium/domain/museum'
import {
  AnnotationRepository,
  CdliInfo,
  FragmentRepository,
} from 'fragmentarium/application/FragmentService'
import Annotation from 'fragmentarium/domain/annotation'
import {
  FragmentInfoRepository,
  FragmentInfosDtoPromise,
  FragmentInfosPromise,
} from 'fragmentarium/application/FragmentSearchService'
import Reference from 'bibliography/domain/Reference'
import { LemmatizationDto } from 'transliteration/domain/Lemmatization'
import { FolioPagerData, FragmentPagerData } from 'fragmentarium/domain/pager'
import { museumNumberToString } from 'fragmentarium/domain/MuseumNumber'
import { Genres } from 'fragmentarium/domain/Genres'
import Word from 'dictionary/domain/Word'
import {
  LineToVecRanking,
  LineToVecRankingDto,
  LineToVecScore,
  LineToVecScoreDto,
} from 'fragmentarium/domain/lineToVecRanking'
import createReference from 'bibliography/application/createReference'
import { createTransliteration } from 'transliteration/application/dtos'
import { Joins } from 'fragmentarium/domain/join'
import { ManuscriptAttestation } from 'corpus/domain/manuscriptAttestation'
import FragmentDto from 'fragmentarium/domain/FragmentDtos'
import { PeriodModifiers, Periods } from 'common/period'
import { FragmentQuery } from 'query/FragmentQuery'
import {
  QueryItem,
  QueryResult,
  FragmentAfoRegisterQueryResult,
} from 'query/QueryResult'
import { createResearchProject } from 'research-projects/researchProject'
import { MesopotamianDate } from 'chronology/domain/Date'
import { ArchaeologyDto } from 'fragmentarium/domain/archaeologyDtos'
import { createArchaeology } from 'fragmentarium/domain/archaeologyDtos'
import { JsonApiClient } from 'index'
import { Colophon } from 'fragmentarium/domain/Colophon'

export function createScript(dto: ScriptDto): Script {
  return {
    ...dto,
    period: Periods[dto.period],
    periodModifier: PeriodModifiers[dto.periodModifier],
  }
}

function createLineToVecScore(dto: LineToVecScoreDto): LineToVecScore {
  return { ...dto, script: createScript(dto.script) }
}

function createLineToVecRanking(dto: LineToVecRankingDto): LineToVecRanking {
  return {
    score: dto.score.map(createLineToVecScore),
    scoreWeighted: dto.scoreWeighted.map(createLineToVecScore),
  }
}

export function createJoins(joins): Joins {
  return joins.map((group) =>
    group.map((join) => ({
      ...join,
      museumNumber: museumNumberToString(join.museumNumber),
    }))
  )
}

function createFragment(dto: FragmentDto): Fragment {
  const museumKey: MuseumKey = dto.museum
  return Fragment.create({
    ...dto,
    number: museumNumberToString(dto.museumNumber),
    accession: dto.accession ? museumNumberToString(dto.accession) : '',
    museum: Museums[museumKey],
    joins: createJoins(dto.joins),
    measures: {
      length: dto.length.value || null,
      width: dto.width.value || null,
      thickness: dto.thickness.value || null,
    },
    folios: dto.folios.map((folioDto) => new Folio(folioDto)),
    record: dto.record.map((recordDto) => new RecordEntry(recordDto)),
    text: createTransliteration(dto.text),
    references: dto.references.map(createReference),
    uncuratedReferences: dto.uncuratedReferences,
    traditionalReferences: dto.traditionalReferences,
    genres: Genres.fromJson(dto.genres),
    script: createScript(dto.script),
    projects: dto.projects.map(createResearchProject),
    date: dto.date ? MesopotamianDate.fromJson(dto.date) : undefined,
    datesInText: dto.datesInText
      ? dto.datesInText.map((date) => MesopotamianDate.fromJson(date))
      : [],
    archaeology: dto.archaeology
      ? createArchaeology(dto.archaeology)
      : undefined,
    colophon: dto.colophon ? Colophon.fromJson(dto.colophon) : undefined,
  })
}

export function createFragmentInfo(dto: FragmentInfoDto): FragmentInfo {
  return {
    ...dto,
    script: createScript(dto.script),
    accession: dto.accession ? museumNumberToString(dto.accession) : '',
  }
}

function createFragmentPath(number: string, ...subResources: string[]): string {
  return ['/fragments', encodeURIComponent(number), ...subResources].join('/')
}

function createQueryItem(dto): QueryItem {
  return {
    ...dto,
    museumNumber: museumNumberToString(dto.museumNumber),
  }
}

function createQueryResult(dto): QueryResult {
  return {
    matchCountTotal: dto.matchCountTotal,
    items: dto.items.map(createQueryItem),
  }
}

class ApiFragmentRepository
  implements FragmentInfoRepository, FragmentRepository, AnnotationRepository {
  constructor(private readonly apiClient: JsonApiClient) {}

  statistics(): Promise<{ transliteratedFragments: number; lines: number }> {
    return this.apiClient.fetchJson(`/statistics`, false)
  }

  lineToVecRanking(number: string): Promise<LineToVecRanking> {
    return this.apiClient
      .fetchJson(createFragmentPath(number, 'match'), false)
      .then(createLineToVecRanking)
  }

  find(
    number: string,
    lines?: readonly number[],
    excludeLines?: boolean
  ): Promise<Fragment> {
    const params = _.omitBy(
      { lines: lines, excludeLines: excludeLines },
      (value) => _.isNil(value)
    )
    return this.apiClient
      .fetchJson(
        `/fragments/${encodeURIComponent(number)}${
          _.isEmpty(params) ? '' : `?${stringify(params)}`
        }`,
        false
      )
      .then(createFragment)
  }

  random(): FragmentInfosPromise {
    return this._fetch({ random: true }).then((fragmentInfos) =>
      fragmentInfos.map(createFragmentInfo)
    )
  }

  interesting(): FragmentInfosPromise {
    return this._fetch({ interesting: true }).then((fragmentInfos) =>
      fragmentInfos.map(createFragmentInfo)
    )
  }

  fetchNeedsRevision(): FragmentInfosPromise {
    return this._fetch({ needsRevision: true }).then((fragmentInfos) =>
      fragmentInfos.map(createFragmentInfo)
    )
  }

  _fetch(params: Record<string, unknown>): FragmentInfosDtoPromise {
    return this.apiClient.fetchJson(`/fragments?${stringify(params)}`, false)
  }

  fetchGenres(): Promise<string[][]> {
    return this.apiClient.fetchJson('/genres', false)
  }

  fetchProvenances(): Promise<string[][]> {
    return this.apiClient.fetchJson('/provenances', false)
  }

  fetchColophonNames(query: string): Promise<string[]> {
    return this.apiClient.fetchJson(
      `/fragments/colophon-names?${stringify({ query })}`,
      false
    )
  }

  fetchPeriods(): Promise<string[]> {
    return this.apiClient.fetchJson('/periods', false)
  }

  updateGenres(number: string, genres: Genres): Promise<Fragment> {
    const path = createFragmentPath(number, 'genres')
    return this.apiClient
      .postJson(path, {
        genres: genres.genres,
      })
      .then(createFragment)
  }

  updateScript(number: string, script: Script): Promise<Fragment> {
    const path = createFragmentPath(number, 'script')
    return this.apiClient
      .postJson(path, {
        script: {
          period: script.period.name,
          periodModifier: script.periodModifier.name,
          uncertain: script.uncertain,
        },
      })
      .then(createFragment)
  }

  updateDate(number: string, date: MesopotamianDate): Promise<Fragment> {
    const path = createFragmentPath(number, 'date')
    return this.apiClient.postJson(path, { date }).then(createFragment)
  }

  updateDatesInText(
    number: string,
    datesInText: readonly MesopotamianDate[]
  ): Promise<Fragment> {
    const path = createFragmentPath(number, 'dates-in-text')
    return this.apiClient.postJson(path, { datesInText }).then(createFragment)
  }

  updateTransliteration(
    number: string,
    transliteration: string
  ): Promise<Fragment> {
    const path = createFragmentPath(number, 'transliteration')
    return this.apiClient
      .postJson(path, {
        transliteration: transliteration,
      })
      .then(createFragment)
  }

  updateIntroduction(number: string, introduction: string): Promise<Fragment> {
    const path = createFragmentPath(number, 'introduction')
    return this.apiClient
      .postJson(path, {
        introduction: introduction,
      })
      .then(createFragment)
  }

  updateNotes(number: string, notes: string): Promise<Fragment> {
    const path = createFragmentPath(number, 'notes')
    return this.apiClient
      .postJson(path, {
        notes: notes,
      })
      .then(createFragment)
  }

  updateLemmatization(
    number: string,
    lemmatization: LemmatizationDto
  ): Promise<Fragment> {
    const path = createFragmentPath(number, 'lemmatization')
    return this.apiClient
      .postJson(path, { lemmatization: lemmatization })
      .then(createFragment)
  }

  updateReferences(number: string, references: Reference[]): Promise<Fragment> {
    const path = createFragmentPath(number, 'references')
    return this.apiClient
      .postJson(path, { references: references })
      .then(createFragment)
  }

  updateArchaeology(
    number: string,
    archaeology: ArchaeologyDto
  ): Promise<Fragment> {
    const path = createFragmentPath(number, 'archaeology')
    return this.apiClient
      .postJson(path, { archaeology: archaeology })
      .then(createFragment)
  }

  updateColophon(number: string, colophon: Colophon): Promise<Fragment> {
    const path = createFragmentPath(number, 'colophon')
    return this.apiClient
      .postJson(path, { colophon: colophon })
      .then(createFragment)
  }

  folioPager(folio: Folio, number: string): Promise<FolioPagerData> {
    return this.apiClient.fetchJson(
      `/fragments/${encodeURIComponent(number)}/pager/${encodeURIComponent(
        folio.name
      )}/${encodeURIComponent(folio.number)}`,
      false
    )
  }

  fragmentPager(fragmentNumber: string): Promise<FragmentPagerData> {
    return this.apiClient.fetchJson(
      `/fragments/${encodeURIComponent(fragmentNumber)}/pager`,
      false
    )
  }

  findLemmas(word: string, isNormalized: boolean): Promise<Word[][]> {
    return this.apiClient.fetchJson(
      `/lemmas?word=${encodeURIComponent(
        word
      )}&isNormalized=${encodeURIComponent(isNormalized)}`,
      false
    )
  }

  fetchCdliInfo(cdliNumber: string): Promise<CdliInfo> {
    return this.apiClient
      .fetchJson(`/cdli/${encodeURIComponent(cdliNumber)}`, false)
      .catch((error: Error) => {
        if (error.name === 'ApiError') {
          return { photoUrl: null, lineArtUrl: null, detailLineArtUrl: null }
        } else {
          throw error
        }
      })
  }

  findAnnotations(
    number: string,
    generateAnnotations = false
  ): Promise<readonly Annotation[]> {
    return this.apiClient
      .fetchJson(
        `${createFragmentPath(
          number
        )}/annotations?generateAnnotations=${generateAnnotations}`,
        false
      )
      .then(({ annotations }) =>
        annotations.map(
          ({ geometry, data }) =>
            new Annotation({ ...geometry, type: 'RECTANGLE' }, data)
        )
      )
  }

  updateAnnotations(
    number: string,
    annotations: readonly Annotation[]
  ): Promise<readonly Annotation[]> {
    return this.apiClient.postJson(
      `${createFragmentPath(number)}/annotations`,
      {
        fragmentNumber: number,
        annotations: annotations.map(
          produce((annotation) => ({
            geometry: _.omit(annotation.geometry, 'type'),
            data: annotation.data,
          }))
        ),
      }
    )
  }

  findInCorpus(number: string): Promise<ReadonlyArray<ManuscriptAttestation>> {
    return this.apiClient
      .fetchJson(`${createFragmentPath(number)}/corpus`, false)
      .then((manuscriptAttestations) =>
        manuscriptAttestations.map(
          (manuscriptAttestation) =>
            new ManuscriptAttestation(
              manuscriptAttestation.text,
              manuscriptAttestation.chapterId,
              manuscriptAttestation.manuscript,
              manuscriptAttestation.manuscriptSiglum
            )
        )
      )
  }

  query(fragmentQuery: FragmentQuery): Promise<QueryResult> {
    return this.apiClient
      .fetchJson(`/fragments/query?${stringify(fragmentQuery)}`, false)
      .then(createQueryResult)
  }

  queryLatest(): Promise<QueryResult> {
    return this.apiClient
      .fetchJson('/fragments/latest', false)
      .then(createQueryResult)
  }

  queryByTraditionalReferences(
    traditionalReferences: string[]
  ): Promise<FragmentAfoRegisterQueryResult> {
    return this.apiClient.postJson(
      `/fragments/query-by-traditional-references`,
      {
        traditionalReferences,
      },
      false
    )
  }

  listAllFragments(): Promise<string[]> {
    return this.apiClient.fetchJson(`/fragments/all`, false)
  }
}

export default ApiFragmentRepository