Can't find files uploaded to Google Drive using REST API

Viewed 514

I'm trying to upload media files to Google Drive using the REST API and service account. I have a Cloud Functions backend where I authenticate with the right scopes for Google DRIVE API and return the access token (shown in the snippet below) to the client which could then make an upload request to Google Drive.

const auth = new google.auth.GoogleAuth({
    scopes: [
        'https://www.googleapis.com/auth/cloud-platform',
        'https://www.googleapis.com/auth/drive.appdata',
        'https://www.googleapis.com/auth/drive'
    ]
});

const client = await auth.getClient();
return await client.getAccessToken();

This works fine on the web as seen here and even on expo-snack-web as seen here. Now, I need to get this to work on the mobile platform, using React Native, but every time I upload a file, I get a 200 response and the file id, but I don't find the file on the specified folder or anywhere else on Google Drive. The snippet below is just a rewrite of the same logic I'm using to upload on the web version.

import React, {useEffect, useState} from 'react'
import {View, Text, StyleSheet, Platform, Button, Image} from 'react-native'
import * as ImagePicker from 'expo-image-picker';
import axios from 'axios'
import {decode as atob} from 'base-64'

export const UploadToDrive = (props) => {

  const [image, setImage] = useState(null);
  const [uploadPercent, setUploadPercent] = useState(0)
  const [mimeType, setMimeType] = useState('')

  useEffect(() => {
    (async () => {
      if (Platform.OS !== 'web') {
        const { status } = await ImagePicker.requestCameraRollPermissionsAsync();
        if (status !== 'granted') {
          alert('Sorry, we need camera roll permissions to make this work!');
        }
      }
    })();
  }, []);

  const pickImage = async () => {
    const baseURL = 'https://dummy-server-address/api';

    // Simultaneously get the access token while picking the media
    const [{response}, result] = await Promise.all([
      fetch(
        `${baseURL}/services/fetch-access-token`,
        {
          mode: "cors",
        }
      )
        .then((res) => res.json())
        .then((resp) => resp)
        .catch((err) => console.log(err)),

      ImagePicker.launchImageLibraryAsync({
        mediaTypes: ImagePicker.MediaTypeOptions.All,
        allowsEditing: true,
        aspect: [4, 3],
        quality: 1,
      })
    ]).catch((err) => console.log(err))

    // ensure that all needed parameters/fields are present
    if (!result.cancelled && response.token) {
      const accessToken = response.token

      const uri =  "file:///" + result.uri.split("file:/").join("");
      setImage(uri);

      const file = result
 
      let filename = uri.split('/').pop();
      let match = /\.(\w+)$/.exec(filename);
      let type = match ? `image/${match[1]}` : `image`;

      file.name = filename;
      file.type = type
      file.uri = uri
      console.log(file)

      setMimeType(file.type)
      // Upload the image using the fetch and FormData APIs
      let formData = new FormData();

      const metadata = {
        name: file.name, 
        type: file.type || 'multipart/form-data',
        parents: ["1yz6MUU0YfXz0rl7TObq-JOPCmC6sHKdQ"],
      };
     // construct the file metadata for the upload
     formData.append(
        "metadata",
        new Blob([JSON.stringify(metadata)], { type: "application/json" })
      );

      //  formData.append('file', { uri, name: filename, type });
      formData.append('file', file);
      const url =
        "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id";

      // let uploadPercent;
      const upload_res = await axios(url, {
        method: "POST",
        headers: { 
          Authorization: "Bearer " + accessToken,
        },
        data: formData,
        onUploadProgress: (p) => {
          setUploadPercent((p.loaded / p.total) * 100)
        },
      })
      .catch(err => console.log({err}))
      .finally(() => setUploadPercent(0));

      console.log({ data: upload_res.data });

    } else {
      throw new Error('You do not have all the right variables to make an upload')
    }
  };

  return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>

        {!!uploadPercent && <Text style={styles.uploadPercent}>Upload Percent: {`${uploadPercent.toFixed(2)}%`}</Text>}

        <Button style={styles.button} title="Pick a file" onPress={pickImage} />
        {image && mimeType.includes('image') && <Image source={{ uri: image }} style={{ width: 200, height: 200, marginTop: 20 }} />}
    </View>
  )
}


const styles = StyleSheet.create({
  button: {
    position: 'absolute',
    top: '10%'
  },
  uploadPercent: {
    color: 'tomato',
    fontSize: 15,
    fontWeight: 'bold',
    marginBottom: 20
  }
})

Please, what could I be doing wrong?

2 Answers
  • If you upload with the service account files to a Drive that is not his, you need to set the parameter supportsAllDrives to true

  • Alternatively, use domain-wide delegation with impersonation to make the service account upload files on your behalf - in this case you do not need to share your folder with the service account.

Remember a service account is not you. A service account has its own Google drive account. If you do a files.list and you will see the files. They are being uploaded to the service accounts drive account.

If you want them uploaded to your own google drive account then you could take the service accounts email address and share a folder on your personal drive account and grant it access to upload to that folder. Just remember to have the service account grant your personal account access to the files or you wont be able to access them as the service account will be owner as it uploaded the file.

If you have a gsuite account then you could set upl domain wide delegation and allow the service account to impersonate a user on the domain and upload them on behalf of that user meaning there will be no more issue with permissions.

metadata name issue

Your metadata should include the name of the file you are uploading, it doesnt have to if you dont then it will be uploaded with untitled as its name. You should be passing the metadata variable this appears to be passing a metadata string.

formData.append(
    "metadata",
    new Blob([JSON.stringify(metadata)], { type: "application/json" })
  );
Related