huridocs/uwazi

View on GitHub
app/api/settings/settings.ts

Summary

Maintainability
A
0 mins
Test Coverage
A
96%
import translations from 'api/i18n/translations';

import {
  Settings,
  SettingsLinkSchema,
  SettingsFilterSchema,
  SettingsSublinkSchema,
} from 'shared/types/settingsType';
import { ensure } from 'shared/tsUtils';
import templates from 'api/templates';
import { LanguageSchema, LatLonSchema, ObjectIdSchema } from 'shared/types/commonTypes';

import { TemplateSchema } from 'shared/types/templateType';
import { validateSettings } from 'shared/types/settingsSchema';
import { ContextType } from 'shared/translationSchema';
import { settingsModel } from './settingsModel';

const DEFAULT_MAP_STARTING_POINT: LatLonSchema[] = [{ lon: 6, lat: 46 }];

type FilterOrLink = SettingsFilterSchema | SettingsLinkSchema;

const isLink = (item: any): item is SettingsLinkSchema => item.type && item.title;

type SubLinkKeyType = keyof SettingsSublinkSchema;

const subLinkProperties: SubLinkKeyType[] = ['title' as 'title', 'url' as 'url'];

const isSubLinkKey = (key: string | number | symbol): key is SubLinkKeyType =>
  subLinkProperties.includes(key as any);

const getUpdatesAndDeletes = <T extends FilterOrLink>(
  matchProperty: keyof T,
  propertyName: keyof T,
  newValues: T[] = [],
  currentValues: T[] = []
) => {
  const updatedValues: { [k: string]: any } = {};
  const deletedValues: string[] = [];

  currentValues.forEach(value => {
    const matchValue = newValues.find(
      v => v[matchProperty] && v[matchProperty]?.toString() === value[matchProperty]?.toString()
    );

    if (value[propertyName] && matchValue && matchValue[propertyName] !== value[propertyName]) {
      if (isLink(value) && value.sublinks && isSubLinkKey(propertyName)) {
        value.sublinks.forEach(sublink => {
          updatedValues[ensure<string>(sublink[propertyName])] = sublink[propertyName];
        });
      }
      updatedValues[ensure<string>(value[propertyName])] = matchValue[propertyName];
    }
    if (!matchValue) {
      if (isLink(value) && value.sublinks && isSubLinkKey(propertyName)) {
        value.sublinks.forEach(sublink => {
          if (sublink[propertyName]) {
            deletedValues.push(ensure<string>(sublink[propertyName] as string));
          }
        });
      }
      deletedValues.push(ensure<string>(value[propertyName]));
    }
  });

  const values = newValues.reduce<{ [k: string]: string }>((result, value) => {
    const sublinkResults: { [key: string]: string | unknown } = {};
    if (isLink(value) && value.sublinks && isSubLinkKey(propertyName)) {
      value.sublinks.forEach(sublink => {
        sublinkResults[ensure<string>(sublink[propertyName])] = sublink[propertyName];
      });
    }
    return {
      ...result,
      [ensure<string>(value[propertyName])]: value[propertyName],
      ...sublinkResults,
    } as { [k: string]: string };
  }, {});
  return { updatedValues, deletedValues, values };
};

const saveLinksTranslations = async (
  newLinks: Settings['links'],
  currentLinks: Settings['links'] = []
) => {
  if (!newLinks) {
    return Promise.resolve();
  }

  const { updatedValues, deletedValues, values } = getUpdatesAndDeletes(
    '_id',
    'title',
    newLinks,
    currentLinks
  );

  return translations.updateContext(
    { id: 'Menu', label: 'Menu', type: ContextType.uwaziUI },
    updatedValues,
    deletedValues,
    values
  );
};

const saveFiltersTranslations = async (
  _newFilters: Settings['filters'],
  _currentFilters: Settings['filters'] = []
) => {
  if (!_newFilters) {
    return Promise.resolve();
  }

  const newFilters = _newFilters.filter(item => item.items);
  const currentFilters = _currentFilters.filter(item => item.items);

  const { updatedValues, deletedValues, values } = getUpdatesAndDeletes(
    'id',
    'name',
    newFilters,
    currentFilters
  );
  return translations.updateContext(
    { id: 'Filters', label: 'Filters', type: ContextType.uwaziUI },
    updatedValues,
    deletedValues,
    values
  );
};

function removeTemplate(filters: SettingsFilterSchema[], templateId: ObjectIdSchema) {
  const filterTemplate = (filter: SettingsFilterSchema) => filter.id !== templateId;
  return filters.filter(filterTemplate).map(_filter => {
    const filter = _filter;
    if (filter.items) {
      filter.items = removeTemplate(filter.items, templateId);
    }
    return filter;
  });
}

function setDefaults(storedSettings: Settings[]) {
  const [settings] = storedSettings;
  if (!settings) return {};

  settings.mapStartingPoint =
    settings.mapStartingPoint && settings.mapStartingPoint.length
      ? settings.mapStartingPoint
      : DEFAULT_MAP_STARTING_POINT;

  return settings;
}

export default {
  async get(query: any = {}, select: any = '') {
    return ensure<Settings>(
      await settingsModel.get(query, select).then(settings => setDefaults(settings))
    );
  },

  async save(settings: Settings) {
    await validateSettings(settings);
    const currentSettings = await this.get();
    await saveLinksTranslations(settings.links, currentSettings.links);
    await saveFiltersTranslations(settings.filters, currentSettings.filters);

    const result = await settingsModel.save({ ...settings, _id: currentSettings._id });

    if (!currentSettings.newNameGeneration && settings.newNameGeneration) {
      await (
        await templates.get()
      ).reduce<Promise<TemplateSchema>>(
        async (lastSave, template) => {
          await lastSave;
          return templates.save(
            template,
            ensure<LanguageSchema>(
              ensure<LanguageSchema[]>(currentSettings.languages).find(l => l.default)
            ).key
          );
        },
        Promise.resolve({} as TemplateSchema)
      );
    }

    return result;
  },

  async setDefaultLanguage(key: string) {
    return this.get().then(async currentSettings => {
      const languages = ensure<LanguageSchema[]>(currentSettings.languages).map(language => ({
        ...language,
        default: language.key === key,
      }));

      return settingsModel.save(Object.assign(currentSettings, { languages }));
    });
  },

  async getDefaultLanguage() {
    const currentSettings = await this.get();
    const defaultLanguage = currentSettings.languages?.find(language => language.default);
    if (!defaultLanguage) {
      throw new Error('There is no default language !');
    }
    return defaultLanguage;
  },

  async addLanguage(language: LanguageSchema) {
    const currentSettings = await this.get();
    currentSettings.languages = currentSettings.languages || [];
    const keys = new Set(currentSettings.languages.map(l => l.key));
    if (!keys.has(language.key)) currentSettings.languages.push(language);
    return settingsModel.save(currentSettings);
  },

  async deleteLanguage(key: string) {
    const currentSettings = await this.get();
    const languages = ensure<LanguageSchema[]>(currentSettings.languages).filter(
      language => language.key !== key
    );
    return settingsModel.save(Object.assign(currentSettings, { languages }));
  },

  async removeTemplateFromFilters(templateId: ObjectIdSchema) {
    const settings = await this.get();

    if (!settings.filters) {
      return Promise.resolve();
    }

    settings.filters = removeTemplate(settings.filters, templateId);
    return this.save(settings);
  },

  async updateFilterName(filterId: ObjectIdSchema, name: string) {
    const settings = await this.get();

    if (!(settings.filters || []).some(eachFilter => eachFilter.id === filterId)) {
      return Promise.resolve();
    }

    const filter = (settings.filters || []).find(eachFilter => eachFilter.id === filterId);
    if (filter) {
      filter.name = name;
    }

    return this.save(settings);
  },

  async getLinks() {
    const settings = await this.get();
    return settings.links || [];
  },

  async saveLinks(links: Settings['links']) {
    const currentSettings = await this.get();
    const newSettings = { ...currentSettings, links };
    return this.save(newSettings);
  },
};