nazirov91/ra-strapi-rest

Fails with current version of strapi and react admin

Closed this issue · 5 comments

I am not sure if this is going to work anymore cause i see the post and get request it is trying to call /count and gets 404. also it does not automatically get all the fields. So were do i find the updated one ?

hey brother , i am getting a same error you solved it or not. or if you have any other solution please tell us.

SetiZ commented

found a solution here. You need to add a count method:
https://forum.strapi.io/t/how-to-count-in-rest-api-in-v4/14765
but now I have this error
The response to 'getList' must be like { data : [...] }, but the received data is not an array. The dataProvider is probably wrong for 'getList'

SetiZ commented

screw it I just copied the index.js and rewrote some parts.
Strapi returns { data: [..], meta: {..} } which is not the exact format react-admin is waitingfor
react admin is waiting for { data: [..], total: x }

modified version that now works for me. will submit a pull request soon.

import {
    fetchUtils,
    GET_LIST,
    GET_ONE,
    GET_MANY,
    GET_MANY_REFERENCE,
    CREATE,
    UPDATE,
    UPDATE_MANY,
    DELETE,
    DELETE_MANY,
} from 'react-admin';

/**
 * Maps react-admin queries to a simple REST API
 * @example
 * GET_LIST     => GET http://my.api.url/posts?sort=['title','ASC']&range=[0, 24]
 * GET_ONE      => GET http://my.api.url/posts/123
 * GET_MANY     => GET http://my.api.url/posts?filter={ids:[123,456,789]}
 * UPDATE       => PUT http://my.api.url/posts/123
 * CREATE       => POST http://my.api.url/posts
 * DELETE       => DELETE http://my.api.url/posts/123
 */
export default (apiUrl, httpClient = fetchUtils.fetchJson, uploadFields = []) => {
    /**
     * @param {String} type One of the constants appearing at the top if this file, e.g. 'UPDATE'
     * @param {String} resource Name of the resource to fetch, e.g. 'posts'
     * @param {Object} params The data request params, depending on the type
     * @returns {Object} { url, options } The HTTP request parameters
     */
    const convertDataRequestToHTTP = (type, resource, params) => {
        let url = '';
        const options = {};
        switch (type) {
            case GET_LIST:
            case GET_MANY_REFERENCE:
                url = `${apiUrl}/${resource}?${adjustQueryForStrapi(params)}`;
                break;
            case GET_ONE:
                url = `${apiUrl}/${resource}/${params.id}`;
                break;
            case UPDATE:
                url = `${apiUrl}/${resource}/${params.id}`;
                options.method = 'PUT';
                // Omit created_at/updated_at(RDS) and createdAt/updatedAt(Mongo) in request body
                let {...dataUpdate} = params.data.attributes;
                dataUpdate.id = params.data.id;
                options.body = `{"data": ${JSON.stringify(dataUpdate)}}`;
                console.log(params.data)
                break;
            case CREATE:
                url = `${apiUrl}/${resource}`;
                options.method = 'POST';
                let {...dataCreate} = params.data.attributes;
                dataCreate.id = params.data.id;
                options.body = `{"data": ${JSON.stringify(dataCreate)}}`;
                break;
            case DELETE:
                url = `${apiUrl}/${resource}/${params.id}`;
                options.method = 'DELETE';
                break;
            default:
                throw new Error(`Unsupported fetch action type ${type}`);
        }
        return { url, options };
    };

    const adjustQueryForStrapi = (params) => {

        /*
        params = { 
            pagination: { page: {int} , perPage: {int} }, 
            sort: { field: {string}, order: {string} }, 
            filter: {Object}, 
            target: {string}, (REFERENCE ONLY)
            id: {mixed} (REFERENCE ONLY)
        }
        */

        // Handle SORTING
        const s = params.sort;
        const sort = s.field === "" ? "_sort=updated_at:DESC" : ("_sort=" + s.field + ":" + s.order);

        // Handle FILTER
        const f = params.filter;
        let filter = "";
        const keys = Object.keys(f);
        for(let i = 0; i < keys.length; i++){
            //react-admin uses q filter in several components and strapi use _q
            if (keys[i] === "q" && f[keys[i]] !== '') {
                filter += "_q=" + f[keys[i]] + (keys[i + 1] ? "&" : "")
            } else {
                filter += keys[i] + "=" + f[keys[i]] + (keys[i + 1] ? "&" : "");
            }
        }
        if(params.id && params.target && params.target.indexOf('_id') !== -1){
            const target = params.target.substring(0, params.target.length - 3);
            filter += "&" + target + "=" + params.id;
        }

        // Handle PAGINATION
        const { page, perPage } = params.pagination;
        const start = (page - 1) * perPage;
        const limit = perPage;//for strapi the _limit params indicate the amount of elements to return in the response
        const range = "_start=" + start + "&_limit=" + limit;

        return sort + "&" + range + "&" + filter; 
    }
    
    // Determines if there are new files to upload
    const determineUploadFieldNames = params => {
	if (!params.data) return [];

	// Check if the field names are mentioned in the uploadFields
	// and verify there are new files being added
	const requestUplaodFieldNames = [];
	Object.keys(params.data).forEach(field => {
	   let fieldData = params.data[field];
	   if (uploadFields.includes(field)) {
	      fieldData = !Array.isArray(fieldData) ? [fieldData] : fieldData;
	      fieldData.filter(f => f && f.rawFile instanceof File).length > 0 
		  && requestUplaodFieldNames.push(field);
	   }
	});
	
	// Return an array of field names where new files are added
	return requestUplaodFieldNames;
     };
    
    // Handles file uploading for CREATE and UPDATE types
    const handleFileUpload = (type, resource, params, uploadFieldNames) => {
	const { created_at, updated_at, createdAt, updatedAt, ...data } = params.data;
	const id = type === UPDATE ? `/${params.id}` : "";
	const url = `${apiUrl}/${resource}${id}`;
	const requestMethod = type === UPDATE ? "PUT" : "POST";
	const formData = new FormData();

	for (let fieldName of uploadFieldNames) {
	    let fieldData = params.data[fieldName];
	    fieldData = !Array.isArray(fieldData) ? [fieldData] : fieldData;
	    const existingFileIds = [];
	    
	    for (let item of fieldData) {
		item.rawFile instanceof File
		  ? formData.append(`files.${fieldName}`, item.rawFile)
		  : existingFileIds.push(item.id || item._id);
	    }

	    data[fieldName] = [...existingFileIds];
	}
	formData.append("data", JSON.stringify(data));

	return httpClient(url, {
	    method: requestMethod,
	    body: formData
	}).then(response => ({ data: replaceRefObjectsWithIds(response.json) }));
    };
    
    // Replace reference objects with reference object IDs	
    const replaceRefObjectsWithIds = json => {
    	Object.keys(json).forEach(key => {
	    const fd = json[key]; // field data
	    const referenceKeys = [];
	    if (fd && (fd.id || fd._id) && !fd.mime) {
	        json[key] = fd.id || fd._id;
	    } else if (Array.isArray(fd) && fd.length > 0 && !fd[0].mime) {
	        fd.map(item => referenceKeys.push(item.id || item._id));
	        json[key] = referenceKeys;
	    }
	});
	return json;
    }

    /**
     * @param {Object} response HTTP response from fetch()
     * @param {String} type One of the constants appearing at the top if this file, e.g. 'UPDATE'
     * @param {String} resource Name of the resource to fetch, e.g. 'posts'
     * @param {Object} params The data request params, depending on the type
     * @returns {Object} Data response
     */
    const convertHTTPResponse = (response, type, resource, params) => {
        const { headers, json, total } = response;
        switch (type) {
	    case GET_ONE:
	        return { data: replaceRefObjectsWithIds(json.data) };
            case GET_LIST:
            case GET_MANY_REFERENCE:
                return {
                    data: json.data,
                    total
                };
            case CREATE:
                return { data: { ...params.data, id: json.id } };
            case DELETE:
                return { data: { id: null } };
            default:
                return { data: json.data };
        }
    };

    /**
     * @param {string} type Request type, e.g GET_LIST
     * @param {string} resource Resource name, e.g. "posts"
     * @param {Object} payload Request parameters. Depends on the request type
     * @returns {Promise} the Promise for a data response
     */
    return (type, resource, params) => {
        
        // Handle file uploading
	const uploadFieldNames = determineUploadFieldNames(params);
	if (uploadFieldNames.length > 0) {
	    return handleFileUpload(type, resource, params, uploadFieldNames);
	}
        
        // simple-rest doesn't handle filters on UPDATE route, so we fallback to calling UPDATE n times instead
        if (type === UPDATE_MANY) {
            return Promise.all(
                params.ids.map(id => {
                    // Omit created_at/updated_at(RDS) and createdAt/updatedAt(Mongo) in request body
                    const {...data} = params.data.attributes;
                    data.id = params.data.id;
                    return httpClient(`${apiUrl}/${resource}/${id}`, {
                        method: 'PUT',
                        body: `{"data": ${JSON.stringify(data)}}`,
                    })
                })
            ).then(responses => ({
                data: responses.map(response => response.json),
            }));
        }
        // simple-rest doesn't handle filters on DELETE route, so we fallback to calling DELETE n times instead
        if (type === DELETE_MANY) {
            return Promise.all(
                params.ids.map(id =>
                    httpClient(`${apiUrl}/${resource}/${id}`, {
                        method: 'DELETE',
                    })
                )
            ).then(responses => ({
                data: responses.map(response => response.json),
            }));
        }
        //strapi doesn't handle filters in GET route
        if (type === GET_MANY) {
            return Promise.all(
                params.ids.map(i =>
                    httpClient(`${apiUrl}/${resource}/${i.id || i._id || i}`, {
                        method: 'GET',
                    })
                )
            ).then(responses => ({
                data: responses.map(response => response.json),
            }));
        }

        const { url, options } = convertDataRequestToHTTP(
            type,
            resource,
            params
        );

        // Get total via model/count endpoint
        if (type === GET_MANY_REFERENCE || type === GET_LIST) {
            return Promise.all([
                httpClient(url, options),
            ]).then(promises => {
                console.log(promises[0]);
                const response = {
                    ...promises[0],
                    // Add total for further use
                    total: parseInt(promises[0].json.meta.pagination.total, 10),
                };
                return convertHTTPResponse(response, type, resource, params);
            });
        } else {
            return httpClient(url, options).then((response) =>
                convertHTTPResponse(response, type, resource, params)
            );
        }
    };
}; 

Hi!

Please see the readme file. I have updated the package to make it work with Strapi V4. Please open a new issue if the problem persists.

Thanks!