|
| 1 | +/** |
| 2 | + * Entrypoint for the APIv2 calls. Prepulates an axios instance with some config settings. |
| 3 | + * |
| 4 | + * @copyright 2009-2019 Vanilla Forums Inc. |
| 5 | + * @license GPL-2.0-only |
| 6 | + */ |
| 7 | + |
| 8 | +import { formatUrl, t, getMeta, siteUrl } from "@library/utility/appUtils"; |
| 9 | +import { indexArrayByKey } from "@vanilla/utils"; |
| 10 | +import axios, { AxiosResponse, AxiosRequestConfig } from "axios"; |
| 11 | +import qs from "qs"; |
| 12 | +import { sprintf } from "sprintf-js"; |
| 13 | +import { humanFileSize } from "@library/utility/fileUtils"; |
| 14 | +import { IApiError, IFieldError } from "@library/@types/api/core"; |
| 15 | +import { IUserFragment } from "@library/@types/api/users"; |
| 16 | + |
| 17 | +function fieldErrorTransformer(responseData) { |
| 18 | + if (responseData && responseData.status >= 400 && responseData.errors && responseData.errors.length > 0) { |
| 19 | + responseData.errors = indexArrayByKey(responseData.errors, "field"); |
| 20 | + } |
| 21 | + |
| 22 | + return responseData; |
| 23 | +} |
| 24 | + |
| 25 | +const apiv2 = axios.create({ |
| 26 | + baseURL: siteUrl("/api/v2/"), |
| 27 | + headers: { |
| 28 | + common: { |
| 29 | + "X-Requested-With": "vanilla", |
| 30 | + }, |
| 31 | + }, |
| 32 | + transformResponse: [...(axios.defaults.transformResponse as any), fieldErrorTransformer], |
| 33 | + paramsSerializer: params => qs.stringify(params), |
| 34 | +}); |
| 35 | + |
| 36 | +export default apiv2; |
| 37 | + |
| 38 | +export type ProgressHandler = (progressEvent: any) => void; |
| 39 | + |
| 40 | +export function createTrackableRequest( |
| 41 | + requestFunction: (progressHandler: ProgressHandler) => () => Promise<AxiosResponse>, |
| 42 | +) { |
| 43 | + return (onUploadProgress: ProgressHandler) => { |
| 44 | + return requestFunction(onUploadProgress); |
| 45 | + }; |
| 46 | +} |
| 47 | +/** |
| 48 | + * Upload an image using Vanilla's API v2. |
| 49 | + * |
| 50 | + * @param file - The file to upload. |
| 51 | + */ |
| 52 | +export async function uploadFile(file: File, requestConfig: AxiosRequestConfig = {}) { |
| 53 | + let allowedExtensions = getMeta("upload.allowedExtensions", []) as string[]; |
| 54 | + allowedExtensions = allowedExtensions.map((ext: string) => ext.toLowerCase()); |
| 55 | + const maxSize = getMeta("upload.maxSize", 0); |
| 56 | + const filePieces = file.name.split("."); |
| 57 | + const extension = filePieces[filePieces.length - 1] || ""; |
| 58 | + |
| 59 | + if (file.size > maxSize) { |
| 60 | + const humanSize = humanFileSize(maxSize); |
| 61 | + const stringTotal: string = humanSize.amount + humanSize.unitAbbr; |
| 62 | + const message = sprintf(t("The uploaded file was too big (max %s)."), stringTotal); |
| 63 | + throw new Error(message); |
| 64 | + } else if (!allowedExtensions.includes(extension.toLowerCase())) { |
| 65 | + const attachmentsString = allowedExtensions.join(", "); |
| 66 | + const message = sprintf( |
| 67 | + t( |
| 68 | + "The uploaded file did not have an allowed extension. \nOnly the following extensions are allowed. \n%s.", |
| 69 | + ), |
| 70 | + attachmentsString, |
| 71 | + ); |
| 72 | + throw new Error(message); |
| 73 | + } |
| 74 | + |
| 75 | + const data = new FormData(); |
| 76 | + data.append("file", file, file.name); |
| 77 | + |
| 78 | + const result = await apiv2.post("/media", data, requestConfig); |
| 79 | + return result.data; |
| 80 | +} |
| 81 | + |
| 82 | +/** |
| 83 | + * Extract a field specific error from an ILoadable if applicable. |
| 84 | + * |
| 85 | + * @param apiError - The error to extract from. |
| 86 | + * @param field - The field to extract. |
| 87 | + * |
| 88 | + * @returns an array of IFieldErrors if found or undefined. |
| 89 | + */ |
| 90 | +export function getFieldErrors(apiError: IApiError | undefined, field: string): IFieldError[] | undefined { |
| 91 | + if (!apiError) { |
| 92 | + return; |
| 93 | + } |
| 94 | + |
| 95 | + const serverError = apiError.response.data; |
| 96 | + if (serverError && serverError.errors && serverError.errors[field]) { |
| 97 | + return serverError.errors[field]; |
| 98 | + } |
| 99 | +} |
| 100 | + |
| 101 | +/** |
| 102 | + * Extract a global error message out of an ILoadable if applicable. |
| 103 | + * |
| 104 | + * @param apiError - The error to extract from. |
| 105 | + * @param validFields - Field to check for overriding fields errors from. A global error only shows if there are no valid field errors. |
| 106 | + * |
| 107 | + * @returns A global error message or an undefined. |
| 108 | + */ |
| 109 | +export function getGlobalErrorMessage(apiError: IApiError | undefined, validFields: string[] = []): string | undefined { |
| 110 | + if (!apiError) { |
| 111 | + return; |
| 112 | + } |
| 113 | + for (const field of validFields) { |
| 114 | + if (getFieldErrors(apiError, field)) { |
| 115 | + return; |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + const serverError = apiError.response && apiError.response.data; |
| 120 | + if (serverError && serverError.message) { |
| 121 | + return serverError.message; |
| 122 | + } |
| 123 | + |
| 124 | + return t("Something went wrong while contacting the server."); |
| 125 | +} |
0 commit comments