I am using google drive api to write to a folder located in shared drive.
I have created a service account and service account is added to folder with permission as 'content manager'.
However, when I try to use the api to upload file, I keep getting an error stating 'folder not found'.
The same works fine when I try to create a folder onto my personal drive and add service account with 'editor' permission.
Can someone please help me if I missed something or that is as per design?
Below is sample code snippet:
google auth:
const driveauth = new google.auth.JWT(gSuiteUser, null,
JSON.parse(gSuiteKey), [
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive.file',
'https://www.googleapis.com/auth/drive.appdata'
])
const drive = google.drive({
version: 'v3',
auth: driveauth
});
Below is the code for uploading on google drive:
const fileMetadata = {
'name': 'Filename',
'mimeType': 'application/vnd.google-apps.spreadsheet',
parents: [gSuiteFolder],
convert: true
};
const media = {
mimeType: 'text/csv',
body: csvData
};
drive.files.create({
resource: fileMetadata,
media: media,
fields: 'id'
}, (err, file) => {
if (err) {
// Handle error
console.error(`Failure`);
callback(err);
} else {
console.log('Success', file.data.id);
callback(undefined, "done");
}
});
Turned out that we need to send additional attribute 'supportsAllDrives' as true as shown below:
drive.files.create({
resource: fileMetadata,
media: media,
supportsAllDrives: true,
fields: 'id'
}, (err, file) => {
if (err) {
// Handle error
console.error(`Failure`);
callback(err);
} else {
console.log('Success', file.data.id);
callback(undefined, "done");
}
});
I think something might be off in your deligation.
// loads credentials from .env file
require("dotenv").config();
import { google } from "googleapis";
function initializeDrive(version = "v3") {
const client_email = process.env.GOOGLE_CLIENT_EMAIL;
// add some necessary escaping so to avoid errors when parsing the private key.
const private_key = process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, "\n");
// impersonate an account with rights to create team drives
const emailToImpersonate = "some-user#acme-industries.com";
const jwtClient = new google.auth.JWT(
client_email,
null, // undefined if you use TypeScript
private_key,
["https://www.googleapis.com/auth/drive"],
emailToImpersonate
);
return google.drive({
version: version,
auth: jwtClient
});
}
Or maybe a bit more simple
const auth = new google.auth.JWT({ // JWT instead of GoogleAuth
subject: "me#mycompany.com", // include this property
keyFile: "service-account.json",
scopes: [ ... ],
})
Related
I have created a custom route in Strapi v4 called "user-screens". Locally I hit it with my FE code and it returns some data as expected. However when I deploy it to Heroku and attempt to access the endpoint with code also deployed to Heroku it returns a 404. I've tailed the Heroku logs and can see that the endpoint is hit on the server side, but the logs don't give anymore info other than it returned a 404.
I am doing other non custom route api calls and these all work fine on Heroku. I am able to auth, save the token, and hit the api with the JWT token and all other endpoints return data. This is only happening on my custom route when deployed to Heroku. I've set up cors with the appropriate origins, and I am wondering if I need to add something to my policies and middlewares in the custom route. I have verified the permissions and verified the route is accessible to authenticated users in the Strapi admin.
Here is my route:
module.exports = {
routes: [
{
method: "GET",
path: "/user-screens",
handler: "user-screens.getUserScreens",
config: {
policies: [],
middlewares: [],
},
},
],
};
And my controller:
"use strict";
/**
* A set of functions called "actions" for `user-screens`
*/
module.exports = {
getUserScreens: async (ctx) => {
const user = ctx.state.user;
if (!user) {
return ctx.badRequest(null, [
{ messages: [{ id: "No authorization header was found" }] },
]);
}
strapi.entityService
.findMany("api::screen.screen", {
owner: user.id,
populate: ["image"],
})
.then((result) => {
ctx.send(result);
});
},
};
For anyone facing this, the answer was to change how I returned the ctx response from a 'send' to a 'return' from the controller method. I am not sure why this works locally and not on Heroku, but this fixes it:
New controller code:
module.exports = {
getUserScreens: async (ctx) => {
const user = ctx.state.user;
if (!user) {
return ctx.badRequest(null, [
{ messages: [{ id: "No authorization header was found" }] },
]);
}
return strapi.entityService
.findMany("api::screen.screen", {
owner: user.id,
populate: ["image"],
})
.then((result) => {
return result;
})
.catch((error) => {
return error;
});
},
};
I want to upload a file using google drvie api. The code returns 200 OK but I can't see the file when I logged in to the google drive in browser.
The credentials.json is generated from a service_account. This account is associated with the google drive api.
The id in parent is copied from the browsers address bar when opened the folder. This folder is also shared with the service account.
const auth = new google.auth.GoogleAuth({
keyFile: 'credentials.json',
scopes: ['https://www.googleapis.com/auth/drive.file'],
});
const drive = google.drive({version: 'v3', auth});
const fileMetadata = {
'name': 'test.txt',
'parents':[{"id":"1yh6V..."}]
};
const media = {
mimeType: 'text/txt',
body: fs.createReadStream('test.txt')
};
drive.files.create({
resource: fileMetadata,
media: media,
fields: 'id'
}, (err, file) => {
if (err) {
console.error(err);
} else {
console.log('Upload success!');
}
});
It seems you are using Node.js when uploading a file using Drive API, you can use this:
const fileMetadata = {
name: 'test.txt',
parents:["1yh6V..."]
};
const media = {
mimeType: 'text/plain',
body: fs.createReadStream('test.txt')
};
drive.files.create({
resource: fileMetadata,
media: media,
fields: 'id'
}, (err, file) => {
if (err) {
console.error(err);
} else {
console.log('Upload success!');
}
});
Here are the changes:
Replaced your media.mimeType to 'text/plain' for a plain text file, here are the list of supported Export MIMETypes
Replaced your fileMetadata.parents to ["1yh6V..."], based on this create file in a folder using Node.js where parent property was specified
Note:
Due to my limitation with my environment platforms available, I wasn't able to replicate your issue. I just look for useful references that could help verify your code.
I use firebase and AngularFireAuthGuard to protect specific routes, so that only authenticated users are allowed to access them.
In particular, my MainComponent and MgmtComponent should only be accessible to AUTHENTICATED users.
const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['/login']);
const routes: Routes = [
{ path: 'teams/:teamId/sessions/:sessionId',
component: MainComponent,
canActivate: [AngularFireAuthGuard], data: { authGuardPipe: redirectUnauthorizedToLogin }
},
{ path: 'mgmt',
component: MgmtComponent,
canActivate: [AngularFireAuthGuard], data: { authGuardPipe: redirectUnauthorizedToLogin }
},
{
path: 'login',
component: LoginComponent
}
];
My Problem is, that the user is not redirected back to the originally requested URL, after a successful login.
So what I want/expect is:
user goes to /mgmt
as the user is not authenticated he is automatically redirected to /login
user authenticates (e.g. via google or Facebook OAuth)
user is automatically redirected back to the originally requested page (/mgmt)
Steps 1-3 work fine, but step 4 is missing.
Now that the feature request is in, you can do this using the auth guard. However, the docs are unclear, so here is how I did it.
/** add redirect URL to login */
const redirectUnauthorizedToLogin = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
return redirectUnauthorizedTo(`/login?redirectTo=${state.url}`);
};
/** Uses the redirectTo query parameter if available to redirect logged in users, or defaults to '/' */
const redirectLoggedInToPreviousPage = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
let redirectUrl = '/';
try {
const redirectToUrl = new URL(state.url, location.origin);
const params = new URLSearchParams(redirectToUrl.search);
redirectUrl = params.get('redirectTo') || '/';
} catch (err) {
// invalid URL
}
return redirectLoggedInTo(redirectUrl);
};
This is an open feature request, the angularfire team is working on it: https://github.com/angular/angularfire/pull/2448
Meanwhile I found this workaround:
In the app-routing-module.ts instead of
const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['/login']);
I use following to store the url in the sessionStorage:
const redirectUnauthorizedToLogin = (route: ActivatedRouteSnapshot) => {
const path = route.pathFromRoot.map(v => v.url.map(segment => segment.toString()).join('/')).join('/');
return pipe(
loggedIn,
tap((isLoggedIn) => {
if (!isLoggedIn) {
console.log('Saving afterLogin path', path);
sessionStorage.setItem('afterLogin', path);
}
}),
map(loggedIn => loggedIn || ['/login'])
);
};
In the LoginComponent I read the value from the session storage to redirect:
sessionStorage.getItem('afterLogin');
this.router.navigateByUrl(redirectUrl);
I'm embedding YouTube videos into an app I'm writing, but some of the results are videos that aren't allowed to be played on my site. I've tried setting both videoSyndicated and videoEmbeddable to true in the params but it doesn't seem to fix my problem.
const axios = require('axios');
const ROOT_URL = 'https://www.googleapis.com/youtube/v3/search';
const search = (options, callback) => {
if(!options.key) {
throw new Error('Youtube Search expected key, received undefined');
}
const params = {
type: 'video',
videoEmbeddable: true,
videoSyndicated: true,
part: 'snippet',
key: options.key,
q: options.term,
};
axios.get(ROOT_URL, { params })
.then((response) => {
if(callback) { callback(response.data.items); }
})
.catch((error) => {
console.error(error);
});
};
export default search;`
This looks like a copyright claim by a 3rd party, which happens outside of the YouTube API. You can check this separately by scraping the video page and searching for the copyright text.
In defaut registration API, I need to uplaod the image of user in registration API. So how could I manage it ? I'm sending in a formData and it works fine. I can see (binary) in network.
I tried to add image field and it works in admin panel but from API side I tried to send the file in key names like files, profileImage.
I didn't get the error in res. I got success in res.
Issue: When I reload the admin panel, I didn't get user's profile image.
Try this way. I used in react and it works fine for me.
signUpHandler = () => {
console.log("SignUp data ::: ", this.state);
let data = {
username: this.state.signUpForm.username.value,
phone: this.state.signUpForm.phone.value,
email: this.state.signUpForm.email.value,
password: this.state.signUpForm.password.value
}
axios.post('http://0.0.0.0:1337/auth/local/register', data)
.then(res => {
console.log(res);
return res.data.user.id;
})
.then(refId =>{
const data = new FormData();
data.append('files', this.state.selectedFile);
data.append('refId', refId);
data.append('ref', 'user');
data.append('source', 'users-permissions');
data.append('field', 'profileImage');
return axios.post('http://0.0.0.0:1337/upload', data)
})
.then(res =>{
console.log(res);
alert("You registered successfully...");
this.props.history.push('/login');
})
.catch(error =>{
console.log(error);
})
}
First, you will have to customize your user-permission
To do so, you will have to understand this concept: https://strapi.io/documentation/3.0.0-beta.x/concepts/customization.html
Then you will have to find the function you want to update - in your case, the register function.
And tada here it is https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-users-permissions/controllers/Auth.js#L383.
So you will have to create ./extensions/users-permissions/controllers/Auth.js with the same content as the original file.
Then you will have to add
const { parseMultipartData, sanitizeEntity } = require('strapi-utils');
const uploadFiles = require('strapi/lib/core-api/utils/upload-files');
on the top of your file.
And in your function use this
const { data, files } = parseMultipartData(ctx); to parse data and files.
Then you will have to replace ctx.request.body by data to make sure to use the correct data.
After that you will have to add this after the user creation line
https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-users-permissions/controllers/Auth.js#L510
if (files) {
// automatically uploads the files based on the entry and the model
await uploadFiles(user, files, { model: strapi.plugins['users-permissions'].models.user })
}
Solution for Strapi v4:
var myHeaders = new Headers();
myHeaders.append("Authorization", "Bearer XXXX");
var formdata = new FormData();
formdata.append("files", fileInput.files[0], "XXX.png");
formdata.append("refId", "46");
formdata.append("field", "image");
formdata.append("ref", "plugin::users-permissions.user");
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: formdata,
redirect: 'follow'
};
fetch("http://localhost:1337/api/upload", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));