ampache_react/app/actions/APIActions.js

257 lines
8.2 KiB
JavaScript

/**
* This file implements actions to fetch and load data from the API.
*/
// NPM imports
import { normalize, arrayOf } from "normalizr";
import humps from "humps";
// Other actions
import { CALL_API } from "../middleware/api";
import { pushEntities } from "./entities";
// Models
import { artist, song, album } from "../models/api";
// Constants
export const DEFAULT_LIMIT = 32; /** Default max number of elements to retrieve. */
/**
* This function wraps around an API action to generate actions trigger
* functions to load items etc.
*
* @param action API action.
* @param requestType Action type to trigger on request.
* @param successType Action type to trigger on success.
* @param failureType Action type to trigger on failure.
*/
export default function (action, requestType, successType, failureType) {
/** Get the name of the item associated with action */
const itemName = action.rstrip("s");
/**
* Normalizr helper to normalize API response.
*
* @param jsonData The JS object returned by the API.
* @return A normalized object.
*/
const _normalizeAPIResponse = function (jsonData) {
return normalize(
jsonData,
{
artist: arrayOf(artist),
album: arrayOf(album),
song: arrayOf(song),
},
{
// Use custom assignEntity function to delete useless fields
assignEntity: function (output, key, value) {
if (key == "sessionExpire") {
delete output.sessionExpire;
} else {
output[key] = value;
}
},
}
);
};
/**
* Callback on successful fetch of paginated items
*
* @param jsonData JS object returned from the API.
* @param pageNumber Number of the page that was fetched.
*/
const fetchPaginatedItemsSuccess = function (jsonData, pageNumber, limit) {
jsonData = _normalizeAPIResponse(jsonData);
// Compute the total number of pages
const nPages = Math.ceil(jsonData.result[itemName].length / limit);
// Return success actions
return [
// Action for the global entities store
pushEntities(jsonData.entities, [itemName]),
// Action for the paginated store
{
type: successType,
payload: {
type: itemName,
result: jsonData.result[itemName],
nPages: nPages,
currentPage: pageNumber,
},
},
];
};
/**
* Callback on successful fetch of single item
*
* @param jsonData JS object returned from the API.
* @param pageNumber Number of the page that was fetched.
*/
const fetchItemSuccess = function (jsonData) {
jsonData = _normalizeAPIResponse(jsonData);
return pushEntities(jsonData.entities, [itemName]);
};
/** Callback on request */
const fetchItemsRequest = function () {
// Return a request type action
return {
type: requestType,
payload: {
},
};
};
/**
* Callback on failed fetch
*
* @param error An error object, either a string or an i18nError
* object.
*/
const fetchItemsFailure = function (error) {
// Return a failure type action
return {
type: failureType,
payload: {
error: error,
},
};
};
/**
* Method to trigger a fetch of items.
*
* @param endpoint Ampache server base URL.
* @param username Username to use for API request.
* @param filter An eventual filter to apply (mapped to API filter
* param)
* @param pageNumber Number of the page to fetch items from.
* @param limit Max number of items to fetch.
* @param include [Optional] A list of includes to return as well
* (mapped to API include param)
*
* @return A CALL_API action to fetch the specified items.
*/
const fetchItems = function (endpoint, username, passphrase, filter, pageNumber, limit, include = []) {
// Compute offset in number of items from the page number
const offset = (pageNumber - 1) * DEFAULT_LIMIT;
// Set extra params for pagination
let extraParams = {
offset: offset,
limit: limit,
};
// Handle filter
if (filter) {
extraParams.filter = filter;
}
// Handle includes
if (include && include.length > 0) {
extraParams.include = include;
}
// Return a CALL_API action
return {
type: CALL_API,
payload: {
endpoint: endpoint,
dispatch: [
fetchItemsRequest,
null,
fetchItemsFailure,
],
action: action,
auth: passphrase,
username: username,
extraParams: extraParams,
},
};
};
/**
* High level method to load paginated items from the API wihtout dealing about credentials.
*
* @param pageNumber [Optional] Number of the page to fetch items from.
* @param filter [Optional] An eventual filter to apply (mapped to
* API filter param)
* @param include [Optional] A list of includes to return as well
* (mapped to API include param)
*
* Dispatches the CALL_API action to fetch these items.
*/
const loadPaginatedItems = function ({ pageNumber = 1, limit = DEFAULT_LIMIT, filter = null, include = [] } = {}) {
return (dispatch, getState) => {
// Get credentials from the state
const { auth } = getState();
// Get the fetch action to dispatch
const fetchAction = fetchItems(
auth.endpoint,
auth.username,
auth.token.token,
filter,
pageNumber,
limit,
include
);
// Set success callback
fetchAction.payload.dispatch[1] = (
jsonData => dispatch => {
// Dispatch all the necessary actions
const actions = fetchPaginatedItemsSuccess(jsonData, pageNumber, limit);
actions.map(action => dispatch(action));
}
);
// Dispatch action
dispatch(fetchAction);
};
};
/**
* High level method to load a single item from the API wihtout dealing about credentials.
*
* @param filter The filter to apply (mapped to API filter param)
* @param include [Optional] A list of includes to return as well
* (mapped to API include param)
*
* Dispatches the CALL_API action to fetch this item.
*/
const loadItem = function ({ filter = null, include = [] } = {}) {
return (dispatch, getState) => {
// Get credentials from the state
const { auth } = getState();
// Get the action to dispatch
const fetchAction = fetchItems(
auth.endpoint,
auth.username,
auth.token.token,
filter,
1,
DEFAULT_LIMIT,
include
);
// Set success callback
fetchAction.payload.dispatch[1] = (
jsonData => dispatch => {
dispatch(fetchItemSuccess(jsonData));
}
);
// Dispatch action
dispatch(fetchAction);
};
};
// Remap the above methods to methods including item name
var returned = {};
const camelizedAction = humps.pascalize(action);
returned["loadPaginated" + camelizedAction] = loadPaginatedItems;
returned["load" + camelizedAction.rstrip("s")] = loadItem;
return returned;
}