/** * @fileoverview Redux action creators for ComicVine API and comic book information. * Provides actions for searching ComicVine, fetching comic metadata, managing * library statistics, and applying ComicVine matches to local comic records. * @module actions/comicinfo */ import axios from "axios"; import rateLimiter from "axios-rate-limit"; import { setupCache } from "axios-cache-interceptor"; import { CV_SEARCH_SUCCESS, CV_API_CALL_IN_PROGRESS, CV_API_GENERIC_FAILURE, IMS_COMIC_BOOK_DB_OBJECT_CALL_IN_PROGRESS, IMS_COMIC_BOOK_DB_OBJECT_FETCHED, CV_ISSUES_METADATA_CALL_IN_PROGRESS, CV_CLEANUP, IMS_COMIC_BOOKS_DB_OBJECTS_FETCHED, CV_ISSUES_MATCHES_IN_LIBRARY_FETCHED, CV_ISSUES_FOR_VOLUME_IN_LIBRARY_SUCCESS, CV_WEEKLY_PULLLIST_CALL_IN_PROGRESS, CV_WEEKLY_PULLLIST_FETCHED, LIBRARY_STATISTICS_CALL_IN_PROGRESS, LIBRARY_STATISTICS_FETCHED, } from "../constants/action-types"; import { COMICVINE_SERVICE_URI, LIBRARY_SERVICE_BASE_URI, } from "../constants/endpoints"; /** * Rate-limited axios instance for ComicVine API calls. * Limited to 1 request per second to comply with API rate limits. * @constant {AxiosInstance} */ const http = rateLimiter(axios.create(), { maxRequests: 1, perMilliseconds: 1000, maxRPS: 1, }); /** * Cached axios instance for reducing redundant API calls. * @constant {AxiosInstance} */ const cachedAxios = setupCache(axios); /** * Redux thunk action creator to fetch the weekly comic pull list. * Retrieves upcoming comic releases from the ComicVine service. * * @param {Object} options - Query parameters for the pull list request * @returns {Function} Redux thunk function that dispatches CV_WEEKLY_PULLLIST_FETCHED */ export const getWeeklyPullList = (options) => async (dispatch) => { try { dispatch({ type: CV_WEEKLY_PULLLIST_CALL_IN_PROGRESS, }); await cachedAxios(`${COMICVINE_SERVICE_URI}/getWeeklyPullList`, { method: "get", params: options, }).then((response) => { dispatch({ type: CV_WEEKLY_PULLLIST_FETCHED, data: response.data.result, }); }); } catch (error) { // Error handling could be added here if needed } }; /** * Generic Redux thunk action creator for ComicVine API calls. * Handles rate-limited requests to the ComicVine service with configurable * endpoints, methods, and parameters. * * @param {Object} options - API call configuration options * @param {string} options.callURIAction - API endpoint action (e.g., "search") * @param {string} options.callMethod - HTTP method (GET, POST, etc.) * @param {Object} options.callParams - Query parameters for the request * @param {any} [options.data] - Request body data * @returns {Function} Redux thunk function that dispatches appropriate action based on callURIAction */ export const comicinfoAPICall = (options) => async (dispatch) => { try { dispatch({ type: CV_API_CALL_IN_PROGRESS, inProgress: true, }); const serviceURI = `${COMICVINE_SERVICE_URI}/${options.callURIAction}`; const response = await http(serviceURI, { method: options.callMethod, params: options.callParams, data: options.data ? options.data : null, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }, }); switch (options.callURIAction) { case "search": dispatch({ type: CV_SEARCH_SUCCESS, searchResults: response.data, }); break; default: break; } } catch (error) { dispatch({ type: CV_API_GENERIC_FAILURE, error, }); } }; /** * Redux thunk action creator to fetch all issues for a comic series. * Retrieves issue list from ComicVine for a given volume/series. * * @param {string} comicObjectID - ComicVine volume/series ID * @returns {Function} Redux thunk function that dispatches CV_ISSUES_FOR_VOLUME_IN_LIBRARY_SUCCESS */ export const getIssuesForSeries = (comicObjectID: string) => async (dispatch) => { dispatch({ type: CV_ISSUES_METADATA_CALL_IN_PROGRESS, }); dispatch({ type: CV_CLEANUP, }); const issues = await axios({ url: `${COMICVINE_SERVICE_URI}/getIssuesForSeries`, method: "POST", params: { comicObjectID, }, }); dispatch({ type: CV_ISSUES_FOR_VOLUME_IN_LIBRARY_SUCCESS, issues: issues.data.results, }); }; /** * Redux thunk action creator to analyze library issues against ComicVine data. * Maps issues to query objects and finds matching issues in the local library. * * @param {Array} issues - Array of ComicVine issue objects to analyze * @param {string} issues[].id - Issue ID * @param {string} issues[].name - Issue name * @param {string} issues[].issue_number - Issue number * @param {Object} issues[].volume - Volume information * @param {string} issues[].volume.name - Volume name * @returns {Function} Redux thunk function that dispatches CV_ISSUES_MATCHES_IN_LIBRARY_FETCHED */ export const analyzeLibrary = (issues) => async (dispatch) => { dispatch({ type: CV_ISSUES_METADATA_CALL_IN_PROGRESS, }); const queryObjects = issues.map((issue) => { const { id, name, issue_number } = issue; return { issueId: id, issueName: name, volumeName: issue.volume.name, issueNumber: issue_number, }; }); const foo = await axios({ url: `${LIBRARY_SERVICE_BASE_URI}/findIssueForSeries`, method: "POST", data: { queryObjects, }, }); dispatch({ type: CV_ISSUES_MATCHES_IN_LIBRARY_FETCHED, matches: foo.data, }); }; /** * Redux thunk action creator to fetch library statistics. * Retrieves aggregate statistics about the comic library. * * @returns {Function} Redux thunk function that dispatches LIBRARY_STATISTICS_FETCHED */ export const getLibraryStatistics = () => async (dispatch) => { dispatch({ type: LIBRARY_STATISTICS_CALL_IN_PROGRESS, }); const result = await axios({ url: `${LIBRARY_SERVICE_BASE_URI}/libraryStatistics`, method: "GET", }); dispatch({ type: LIBRARY_STATISTICS_FETCHED, data: result.data, }); }; /** * Redux thunk action creator to fetch detailed comic book information. * Retrieves full comic book document from the library database by ID. * * @param {string} comicBookObjectId - Database ID of the comic book * @returns {Function} Redux thunk function that dispatches IMS_COMIC_BOOK_DB_OBJECT_FETCHED */ export const getComicBookDetailById = (comicBookObjectId: string) => async (dispatch) => { dispatch({ type: IMS_COMIC_BOOK_DB_OBJECT_CALL_IN_PROGRESS, IMS_inProgress: true, }); const result = await axios.request({ url: `${LIBRARY_SERVICE_BASE_URI}/getComicBookById`, method: "POST", data: { id: comicBookObjectId, }, }); dispatch({ type: IMS_COMIC_BOOK_DB_OBJECT_FETCHED, comicBookDetail: result.data, IMS_inProgress: false, }); }; /** * Redux thunk action creator to fetch multiple comic books by their IDs. * Retrieves full comic book documents from the library database for a list of IDs. * * @param {Array} comicBookObjectIds - Array of database IDs * @returns {Function} Redux thunk function that dispatches IMS_COMIC_BOOKS_DB_OBJECTS_FETCHED */ export const getComicBooksDetailsByIds = (comicBookObjectIds: Array) => async (dispatch) => { dispatch({ type: IMS_COMIC_BOOK_DB_OBJECT_CALL_IN_PROGRESS, IMS_inProgress: true, }); const result = await axios.request({ url: `${LIBRARY_SERVICE_BASE_URI}/getComicBooksByIds`, method: "POST", data: { ids: comicBookObjectIds, }, }); dispatch({ type: IMS_COMIC_BOOKS_DB_OBJECTS_FETCHED, comicBooks: result.data, }); }; /** * Redux thunk action creator to apply ComicVine metadata to a local comic. * Associates a ComicVine match with a comic book record in the database, * updating the comic with metadata from ComicVine. * * @param {Object} match - ComicVine match object containing metadata to apply * @param {string} comicObjectId - Database ID of the local comic book to update * @returns {Function} Redux thunk function that dispatches IMS_COMIC_BOOK_DB_OBJECT_FETCHED */ export const applyComicVineMatch = (match, comicObjectId) => async (dispatch) => { dispatch({ type: IMS_COMIC_BOOK_DB_OBJECT_CALL_IN_PROGRESS, IMS_inProgress: true, }); const result = await axios.request({ url: `${LIBRARY_SERVICE_BASE_URI}/applyComicVineMetadata`, method: "POST", data: { match, comicObjectId, }, }); dispatch({ type: IMS_COMIC_BOOK_DB_OBJECT_FETCHED, comicBookDetail: result.data, IMS_inProgress: false, }); };