nukeop/nuclear

View on GitHub
packages/app/app/actions/playlists.ts

Summary

Maintainability
A
1 hr
Test Coverage
import fs from 'fs';
import { v4 } from 'uuid';
import { remote } from 'electron';
import { createAsyncAction, createStandardAction } from 'typesafe-actions';

import { store, PlaylistHelper, Playlist, PlaylistTrack, rest } from '@nuclear/core';
import { GetPlaylistsByUserIdResponseBody } from '@nuclear/core/src/rest/Nuclear/Playlists.types';
import { ErrorBody } from '@nuclear/core/src/rest/Nuclear/types';

import { Playlists } from './actionTypes';

import {
  deletePlaylistEffect,
  updatePlaylistEffect,
  updatePlaylistsOrderEffect
} from './playlists.effects';
import { success, error } from './toasts';
import { IdentityStore } from '../reducers/nuclear/identity';
import { PlaylistsStore } from '../reducers/playlists';
import { SpotifyPlaylist } from '../containers/SpotifyPlaylistImporter/hooks';

export const updatePlaylistsAction = createStandardAction(Playlists.UPDATE_LOCAL_PLAYLISTS)<PlaylistsStore['localPlaylists']['data']>();

export const loadLocalPlaylistsAction = createAsyncAction(
  Playlists.LOAD_LOCAL_PLAYLISTS_START,
  Playlists.LOAD_LOCAL_PLAYLISTS_SUCCESS,
  Playlists.LOAD_LOCAL_PLAYLISTS_ERROR
)<void, Array<Playlist>, void>();

export const loadRemotePlaylistsAction = createAsyncAction(
  Playlists.LOAD_REMOTE_PLAYLISTS_START,
  Playlists.LOAD_REMOTE_PLAYLISTS_SUCCESS,
  Playlists.LOAD_REMOTE_PLAYLISTS_ERROR
)<void, GetPlaylistsByUserIdResponseBody, ErrorBody>();

export const addPlaylist = (tracks: Array<PlaylistTrack>, name: string) => dispatch => {
  if (name?.length === 0) {
    return;
  }
  let playlists: PlaylistsStore['localPlaylists']['data'] = store.get('playlists') || [];
  const playlist = PlaylistHelper.formatPlaylistForStorage(name, tracks, v4());

  playlists = [...playlists, playlist];

  store.set('playlists', playlists);
  dispatch(updatePlaylistsAction(playlists));
};

export const deletePlaylist = (id: string) => dispatch => {
  const playlists = deletePlaylistEffect(store)(id);
  dispatch(updatePlaylistsAction(playlists));
};

export const loadLocalPlaylists = () => dispatch => {
  dispatch(loadLocalPlaylistsAction.request());

  try {
    const playlists: Playlist[] = store.get('playlists');
    dispatch(loadLocalPlaylistsAction.success(playlists ?? []));
  } catch (error) {
    dispatch(loadLocalPlaylistsAction.failure());
  }
};


export const loadRemotePlaylists = ({ token, signedInUser }: IdentityStore) => async (dispatch, getState) => {
  dispatch(loadRemotePlaylistsAction.request());
  const { settings } = getState();
  const service = new rest.NuclearPlaylistsService(
    settings.nuclearPlaylistsServiceUrl
  );

  try {
    if (token) {
      const playlists = await service.getPlaylistsByUserId(token, signedInUser.id);
      if (playlists.ok) {
        dispatch(loadRemotePlaylistsAction.success(playlists.body as GetPlaylistsByUserIdResponseBody));
      } else {
        throw playlists.body;
      }
    } else {
      throw new Error('No token');
    }
  } catch (e) {
    dispatch(loadRemotePlaylistsAction.failure(e.message));
  }
};

export const updatePlaylist = (playlist: Playlist) => dispatch => {
  const playlists = updatePlaylistEffect(store)(playlist);
  dispatch(updatePlaylistsAction(playlists));
};

export const reorderPlaylists = (source: number, destination: number) => async (dispatch) => {
  const playlists = updatePlaylistsOrderEffect(store)(source, destination);
  dispatch(updatePlaylistsAction(playlists));
};


export const exportPlaylist = (playlist, t) => async (dispatch) => {
  const name = playlist.name;
  const dialogResult = await remote.dialog.showSaveDialog({
    defaultPath: name,
    filters: [
      { name: 'file', extensions: ['json'] }
    ],
    properties: ['createDirectory', 'showOverwriteConfirmation']
  });
  const filePath = dialogResult?.filePath?.replace(/\\/g, '/');

  if (filePath) {
    try {
      const data = JSON.stringify(playlist, null, 2);
      fs.writeFile(filePath, data, (err) => {
        if (err) {
          dispatch(error(t('export-fail-title'), t('error-save-file'), null, null));
          return;
        }
        dispatch(success(t('export-success-title'), t('playlist-exported', { name }), null, null));
      });
    } catch (e) {
      dispatch(error(t('export-fail-title'), t('error-save-file'), null, null));
    }
  }
};

export function addPlaylistFromUrl(playlist: SpotifyPlaylist, t) {
  return async dispatch => {
    try {
      if (!playlist.name || !playlist.tracks) {
        throw new Error('missing tracks or name');
      }

      let playlists = store.get('playlists') || [];
      const importedPlaylist = PlaylistHelper.formatPlaylistForStorage(playlist.name, playlist.tracks, v4(), playlist.source);

      if (!(playlist.tracks?.length > 0)) {
        dispatch(error(t('import-fail-title'), t('error-empty-data'), null, null));
        return;
      }

      playlists = [...playlists, importedPlaylist];

      store.set('playlists', playlists);
      dispatch(success(t('import-success-title'), t('playlist-created', { name: playlist.name }), null, null));
      dispatch(updatePlaylistsAction(playlists));

    } catch (e) {
      dispatch(error(t('import-fail-title'), t('error-invalid-data'), null, null));
    }
  };
}

export function addPlaylistFromFile(filePath, t) {
  return async dispatch => {
    if (!filePath || typeof filePath !== 'string') {
      dispatch(error(t('import-fail-title'), t('error-empty-data'), null, null));
      return;
    }
    fs.readFile(filePath, (err, data) => {
      
      if (err) {
        dispatch(error(t('import-fail-title'), t('error-open-file'), null, null));
        return;
      }

      try {
        const parsed = JSON.parse(data.toString());
        const name = parsed && 'name' in parsed ? parsed.name : null;
        const tracks = parsed && 'tracks' in parsed ? parsed.tracks : null;
        const source = parsed && 'source' in parsed ? parsed.source : null;

        if (!name || !tracks) {
          throw new Error('missing tracks or name');
        }

        let playlists = store.get('playlists') || [];
        const playlist = PlaylistHelper.formatPlaylistForStorage(name, tracks, v4(), source);

        if (!(tracks?.length > 0)) {
          dispatch(error(t('import-fail-title'), t('error-empty-data'), null, null));
          return;
        }

        playlists = [...playlists, playlist];

        store.set('playlists', playlists);
        dispatch(success(t('import-success-title'), t('playlist-created', { name }), null, null));
        dispatch(updatePlaylistsAction(playlists));

      } catch (e) {
        dispatch(error(t('import-fail-title'), t('error-invalid-data'), null, null));
      }
    });
  };
}