Save data in user Google Drive
Le but de cet article est d'expliquer comment enregistrer des données dans le stockage Google Drive de l'utilisateur.
Conditions préalables
Il est probablement préférable de savoir comment fonctionnent OAuth2 et HTTP.
- OAuth2 est un framework d'autorisation. Il gère donc les clés pour accéder à une zone protégée.
- La chose la plus importante à propos de HTTP dans notre situation est de savoir comment télécharger des données, en utilisant le formulaire POST et le multipart.
Scénario
- Configurez un projet Google Cloud. Ce projet détient les autorisations de la demande.
- Authentifiez-vous et obtenez les informations d'identification pour accéder à vos ressources (Google Drive).
- Obtenir l'autorisation d'utiliser une API
- Lisez/écrivez vos données à l'aide de l'API
Configuration du projet
L'éditeur de l'application doit configurer une clé d'application qui inclut l'autorisation (utiliser l'API Google Drive) dont il a besoin pour s'exécuter et les conditions d'utilisation des API (comme cors). Cela permet à l'éditeur et à l'utilisateur de s'assurer que l'application ou quelqu'un d'autre n'utilisera pas les API ou toute autre API au nom de l'utilisateur.
Accédez à votre compte de console Google : https://console.cloud.google.com/ et configurez ces informations d'identification.
NB : lors de la phase de test, n'oubliez pas d'ajouter des utilisateurs tests, sinon cela ne fonctionnera pas avec les erreurs 403.
Authentification
Le script Google GSI permet d'authentifier un utilisateur. Plusieurs options sont disponibles, notamment une sorte d'authentification automatique.
1this.$loadScript("https://accounts.google.com/gsi/client?hl=fr").then(args => {
2 google.accounts.id.initialize({
3 client_id: CLIENT_ID,
4 callback: this.handleCredentialResponse
5 });
6 this.tokenClient = google.accounts.oauth2.initTokenClient({
7 client_id: CLIENT_ID,
8 scope: SCOPES,
9 callback: () => {
10 this.googleInitialized = true
11 },
12 });
13 // load authorization here, looks to me more logical but you can load authorization in parallel
14});
Autorisation
Une fois l'utilisateur authentifié vous pouvez demander l'autorisation de l'application pour utiliser l'API "au nom de l'application"
1this.$loadScript("https://apis.google.com/js/api.js").then(args => {
2 gapi.load('client', () => {
3 gapi.client.init({
4 apiKey: API_KEY,
5 discoveryDocs: [DISCOVERY_DOC]
6 }).then(response => console.log(response));
7 if (gapi.client.getToken() === null) {
8 // Prompt the user to select a Google Account and ask for consent to share their data
9 // when establishing a new session.
10 this.tokenClient.requestAccessToken({prompt: 'consent'});
11 } else {
12 // Skip display of account chooser and consent dialog for an existing session.
13 this.tokenClient.requestAccessToken({prompt: ''});
14 }
15 });
16});
Parcourir le lecteur
gapi.client.drive.files
est l'API client permettant de travailler avec le lecteur. Voici comment trouver ou créer un dossier.
1function findOrCreateFolder(folderName) {
2 if (this.folderId) {
3 return new Promise((resolve, reject) => {
4 resolve(this.folderId);
5 });
6 }
7 // Rechercher le répertoire par nom
8 return new Promise((resolve, reject) => {
9 gapi.client.drive.files.list({
10 q: `mimeType='application/vnd.google-apps.folder' and name='${folderName}'`,
11 fields: 'files(id)',
12 }).then(searchResults => {
13 const folders = searchResults.result.files;
14 if (folders.length > 0) {
15 // Le répertoire existe, renvoyer son ID
16 this.folderId = folders[0].id;
17 } else {
18 // Le répertoire n'existe pas, le créer
19 const folderMetadata = {
20 name: folderName,
21 mimeType: 'application/vnd.google-apps.folder',
22 };
23 gapi.client.drive.files.create({
24 resource: folderMetadata,
25 fields: 'id',
26 }).then(response => {
27 this.folderId = response.result.id;
28 });
29 }
30 resolve(this.folderId);
31 }).catch(err => {
32 reject(err)
33 });
34 });
35}
Créer un fichier
Pour créer un fichier, selon la documentation, c'est très simple en utilisant l'API gapi.client.drive.files
. C'est
d'une certaine manière vrai. Vous pouvez créer un fichier avec toutes les métadonnées, mais pas le téléchargement du contenu.
travail.
Le code suivant crée un descripteur de fichier, donc le fichier existe mais il est vide.
1files.create({
2 name: name,
3 parents: [folderName],
4 upload_protocol: 'raw',
5 uploadType: 'media',
6 fields: 'id',
7 mimeType: 'application/json',
Pour contourner cette situation, nous publierons un formulaire contenant le contenu du fichier et ses métadonnées. Le POST a également le jeton d'autorisation dans ses en-têtes.
1const form = new FormData();
2form.append('metadata', new Blob([JSON.stringify(metadata)], {type: 'application/json'}));
3form.append('file', new Blob([JSON.stringify(data)], {type: 'application/json'}));
4fetch("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id", {
5 method: 'POST',
6 headers: new Headers({
7 'Authorization': 'Bearer ' + gapi.auth.getToken().access_token
8 }),
9 body: form
10}).then(res => console.log(res));
Exemple complet de Vuejs
Cet exemple utilise des composants Element-plus.
1
2<template>
3 <div class="g_id_signin" data-type="standard" data-shape="rectangular" data-theme="outline" data-text="signin_with" data-size="large" data-logo_alignment="left"></div>
4 {{ user.given_name }}
5 <el-button @click="login" v-if="!googleInitialized">Login</el-button>
6 <el-button @click="logout" v-if="googleInitialized">Logout</el-button>
7 <el-button @click="listFiles" :disabled="!googleInitialized">Refresh</el-button>
8 <el-button @click="saveJson" :disabled="!googleInitialized">Create</el-button>
9 <el-table :data="files">
10 <el-table-column fixed prop="name" label="Name" width="150"/>
11 <el-table-column fixed prop="modifiedTime" label="Date" width="150"/>
12 <el-table-column fixed="right" label="Operations" width="120">
13 <template #default="scope">
14 <el-button link type="primary" size="small" @click.prevent="open(scope.row)">
15 Open
16 </el-button>
17 </template>
18 </el-table-column>
19 </el-table>
20</template>
21<script>
22 import VueJwtDecode from 'vue-jwt-decode';
23
24 const CLIENT_ID = '....apps.googleusercontent.com';
25 const API_KEY = '...';
26 const DISCOVERY_DOC = 'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest';
27 const REDIRECT_URI = 'https://developers.google.com/oauthplayground';
28 const SCOPES = 'https://www.googleapis.com/auth/drive';
29 export default {
30 name: "UserDrive",
31 props: {
32 folder: String,
33 },
34 components: {},
35 data() {
36 return {
37 user: {},
38 googleInitialized: false,
39 tokenClient: null,
40 files: [],
41 folderId: null,
42 }
43 },
44 mounted() {
45 },
46 methods: {
47 open(file) {
48 console.log(file);
49 this.$emit("open", file);
50 },
51 logout() {
52 gapi.client.setToken(null);
53 },
54 login() {
55 if (!this.googleInitialized) {
56 this.$loadScript("https://accounts.google.com/gsi/client?hl=fr").then(args => {
57 google.accounts.id.initialize({
58 client_id: CLIENT_ID,
59 callback: this.handleCredentialResponse
60 });
61 /*google.accounts.id.prompt((resp) => {
62 console.log(resp);
63 });*/
64 this.tokenClient = google.accounts.oauth2.initTokenClient({
65 client_id: CLIENT_ID,
66 scope: SCOPES,
67 callback: () => {
68 this.googleInitialized = true
69 }, // defined later
70 });
71 this.$loadScript("https://apis.google.com/js/api.js").then(args => {
72 gapi.load('client', () => {
73 gapi.client.init({
74 apiKey: API_KEY,
75 discoveryDocs: [DISCOVERY_DOC]
76 }).then(response => console.log(response));
77 if (gapi.client.getToken() === null) {
78 // Prompt the user to select a Google Account and ask for consent to share their data
79 // when establishing a new session.
80 this.tokenClient.requestAccessToken({prompt: 'consent'});
81 } else {
82 // Skip display of account chooser and consent dialog for an existing session.
83 this.tokenClient.requestAccessToken({prompt: ''});
84 }
85 });
86 });
87 });
88 }
89 },
90 handleCredentialResponse(response) {
91 this.user = VueJwtDecode.decode(response.credential);
92 },
93 findOrCreateFolder(folderName) {
94 if (this.folderId) {
95 return new Promise((resolve, reject) => {
96 resolve(this.folderId);
97 });
98 }
99 // Rechercher le répertoire par nom
100 return new Promise((resolve, reject) => {
101 gapi.client.drive.files.list({
102 q: `mimeType='application/vnd.google-apps.folder' and name='${folderName}'`,
103 fields: 'files(id)',
104 }).then(searchResults => {
105 const folders = searchResults.result.files;
106 if (folders.length > 0) {
107 // Le répertoire existe, renvoyer son ID
108 this.folderId = folders[0].id;
109 } else {
110 // Le répertoire n'existe pas, le créer
111 const folderMetadata = {
112 name: folderName,
113 mimeType: 'application/vnd.google-apps.folder',
114 };
115 gapi.client.drive.files.create({
116 resource: folderMetadata,
117 fields: 'id',
118 }).then(response => {
119 this.folderId = response.result.id;
120 });
121 }
122 resolve(this.folderId);
123 }).catch(err => {
124 reject(err)
125 });
126 });
127 },
128 saveJson() {
129 this._saveJson({name: 'stephane'}, "stephane.json")
130 },
131 _saveJson(data, name) {
132 this.findOrCreateFolder(this.folderName)
133 .then(folder => {
134 let metadata = {
135 name: name,
136 parents: [folder],
137 };
138 let files = gapi.client.drive.files;
139 let media = {
140 mimeType: 'application/json',
141 body: JSON.stringify(data)
142 };
143 const form = new FormData();
144 form.append('metadata', new Blob([JSON.stringify(metadata)], {type: 'application/json'}));
145 form.append('file', new Blob([JSON.stringify(data)], {type: 'application/json'}));
146 fetch("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id", {
147 method: 'POST',
148 headers: new Headers({
149 'Authorization': 'Bearer ' + gapi.auth.getToken().access_token
150 }),
151 body: form
152 }).then(res => console.log(res));
153 this.listFiles();
154 }).catch(err => console.log(err))
155 },
156 listFiles() {
157 this.findOrCreateFolder(this.folderName)
158 .then((folder) => gapi.client.drive.files.list({
159 'q': `'${folder}' in parents`,
160 'pageSize': 10,
161 'fields': 'files(id, name,modifiedTime)',
162 }).then((response) => {
163 this.files.splice(0, this.files.length);
164 response.result.files.forEach(file => {
165 this.files.push(file);
166 })
167 }).catch(err => console.log(err)));
168 }
169 }
170 }
171</script>
172<style></style>
173