aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Wilkins <samwilkins333@gmail.com>2019-09-03 12:51:59 -0400
committerSam Wilkins <samwilkins333@gmail.com>2019-09-03 12:51:59 -0400
commit38176e5ba949b84dc410d29197180121d81e085c (patch)
treefc16f9dadd97d76c8431640bab27cfa8bb75421f
parent8b992ef2c152e86299fd3460112124d476393a60 (diff)
implemented refresh tokens and create, get, list
-rw-r--r--src/client/apis/google_docs/GooglePhotosClientUtils.ts35
-rw-r--r--src/client/views/MainView.tsx10
-rw-r--r--src/server/RouteStore.ts3
-rw-r--r--src/server/apis/google/GoogleApiServerUtils.ts90
-rw-r--r--src/server/apis/google/GooglePhotosServerUtils.ts68
-rw-r--r--src/server/apis/google/GooglePhotosUploadUtils.ts42
-rw-r--r--src/server/apis/google/GooglePhotosUtils.ts21
-rw-r--r--src/server/apis/google/typings/albums.ts30
-rw-r--r--src/server/credentials/google_docs_token.json2
-rw-r--r--src/server/index.ts54
10 files changed, 252 insertions, 103 deletions
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
new file mode 100644
index 000000000..67a282f48
--- /dev/null
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -0,0 +1,35 @@
+import { Album } from "../../../server/apis/google/typings/albums";
+import { PostToServer } from "../../../Utils";
+import { RouteStore } from "../../../server/RouteStore";
+
+export namespace GooglePhotosClientUtils {
+
+ export const Create = async (title: string) => {
+ let parameters = {
+ action: Album.Action.Create,
+ body: { album: { title } }
+ } as Album.Create;
+ return PostToServer(RouteStore.googlePhotos, parameters);
+ };
+
+ export const List = async (options?: Partial<Album.ListOptions>) => {
+ let parameters = {
+ action: Album.Action.List,
+ parameters: {
+ pageSize: (options ? options.pageSize : 20) || 20,
+ pageToken: (options ? options.pageToken : undefined) || undefined,
+ excludeNonAppCreatedData: (options ? options.excludeNonAppCreatedData : false) || false,
+ } as Album.ListOptions
+ } as Album.List;
+ return PostToServer(RouteStore.googlePhotos, parameters);
+ };
+
+ export const Get = async (albumId: string) => {
+ let parameters = {
+ action: Album.Action.Get,
+ albumId
+ } as Album.Get;
+ return PostToServer(RouteStore.googlePhotos, parameters);
+ };
+
+} \ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index df0718072..ece475c80 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -42,6 +42,8 @@ import PresModeMenu from './presentationview/PresentationModeMenu';
import { PresBox } from './nodes/PresBox';
import { GoogleApiClientUtils } from '../apis/google_docs/GoogleApiClientUtils';
import { docs_v1 } from 'googleapis';
+import { Album } from '../../server/apis/google/typings/albums';
+import { GooglePhotosClientUtils } from '../apis/google_docs/GooglePhotosClientUtils';
@observer
export class MainView extends React.Component {
@@ -128,7 +130,7 @@ export class MainView extends React.Component {
window.removeEventListener("keydown", KeyManager.Instance.handle);
window.addEventListener("keydown", KeyManager.Instance.handle);
- PostToServer('/googleDocs/Photos/Test', {});
+ this.executeGooglePhotosAlbumTestRoutine();
reaction(() => {
let workspaces = CurrentUserUtils.UserDocument.workspaces;
@@ -147,6 +149,12 @@ export class MainView extends React.Component {
}, { fireImmediately: true });
}
+ executeGooglePhotosAlbumTestRoutine = async () => {
+ let title = "This is a generically created album!";
+ console.log(await GooglePhotosClientUtils.Create(title));
+ console.log(await GooglePhotosClientUtils.List({ pageSize: 50 }));
+ }
+
componentWillUnMount() {
window.removeEventListener("keydown", KeyManager.Instance.handle);
//close presentation
diff --git a/src/server/RouteStore.ts b/src/server/RouteStore.ts
index 014906054..fc5511f98 100644
--- a/src/server/RouteStore.ts
+++ b/src/server/RouteStore.ts
@@ -31,6 +31,7 @@ export enum RouteStore {
// APIS
cognitiveServices = "/cognitiveservices",
- googleDocs = "/googleDocs"
+ googleDocs = "/googleDocs",
+ googlePhotos = "/googlePhotos"
} \ No newline at end of file
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts
index 00e289b00..048ac4b21 100644
--- a/src/server/apis/google/GoogleApiServerUtils.ts
+++ b/src/server/apis/google/GoogleApiServerUtils.ts
@@ -1,13 +1,12 @@
-import { google, docs_v1, slides_v1 } from "googleapis";
+import { google } from "googleapis";
import { createInterface } from "readline";
import { readFile, writeFile } from "fs";
import { OAuth2Client, Credentials } from "google-auth-library";
import { Opt } from "../../../new_fields/Doc";
import { GlobalOptions } from "googleapis-common";
import { GaxiosResponse } from "gaxios";
-import { GooglePhotos } from "./GooglePhotosUtils";
-import { Utils } from "../../../Utils";
-import { Album } from "./typings/albums";
+import request = require('request-promise');
+import * as qs from 'query-string';
/**
* Server side authentication for Google Api queries.
@@ -31,8 +30,7 @@ export namespace GoogleApiServerUtils {
export enum Service {
Documents = "Documents",
- Slides = "Slides",
- Photos = "Photos"
+ Slides = "Slides"
}
export interface CredentialPaths {
@@ -49,38 +47,31 @@ export namespace GoogleApiServerUtils {
export type EndpointParameters = GlobalOptions & { version: "v1" };
export const GetEndpoint = async (sector: string, paths: CredentialPaths) => {
- return new Promise<Opt<Endpoint>>((resolve, reject) => {
+ return new Promise<Opt<Endpoint>>(resolve => {
+ RetrieveAuthenticationInformation(paths).then(authentication => {
+ let routed: Opt<Endpoint>;
+ let parameters: EndpointParameters = { auth: authentication.client, version: "v1" };
+ switch (sector) {
+ case Service.Documents:
+ routed = google.docs(parameters).documents;
+ break;
+ case Service.Slides:
+ routed = google.slides(parameters).presentations;
+ break;
+ }
+ resolve(routed);
+ });
+ });
+ };
+
+ export const RetrieveAuthenticationInformation = async (paths: CredentialPaths) => {
+ return new Promise<TokenResult>((resolve, reject) => {
readFile(paths.credentials, async (err, credentials) => {
if (err) {
reject(err);
return console.log('Error loading client secret file:', err);
}
- authorize(parseBuffer(credentials), paths.token).then(async result => {
- let routed: Opt<Endpoint>;
- let parameters: EndpointParameters = { auth: result.client, version: "v1" };
- switch (sector) {
- case Service.Documents:
- routed = google.docs(parameters).documents;
- break;
- case Service.Slides:
- routed = google.slides(parameters).presentations;
- break;
- case Service.Photos:
- let token = result.token.access_token;
- if (token) {
- let create: Album.Create = {
- action: Album.Action.Create,
- body: {
- album: {
- title: "Sam's Bulk Export",
- }
- }
- };
- console.log(await GooglePhotos.ExecuteQuery(token, create));
- }
- }
- resolve(routed);
- });
+ authorize(parseBuffer(credentials), paths.token).then(resolve, reject);
});
});
};
@@ -101,13 +92,44 @@ export namespace GoogleApiServerUtils {
if (err) {
return getNewToken(oAuth2Client, token_path).then(resolve, reject);
}
- let parsed = parseBuffer(token);
+ let parsed: Credentials = parseBuffer(token);
+ if (parsed.expiry_date! < new Date().getTime()) {
+ return refreshToken(parsed, client_id, client_secret, oAuth2Client, token_path).then(resolve, reject);
+ }
oAuth2Client.setCredentials(parsed);
resolve({ token: parsed, client: oAuth2Client });
});
});
}
+ const refreshEndpoint = "https://oauth2.googleapis.com/token";
+ const refreshToken = (credentials: Credentials, client_id: string, client_secret: string, oAuth2Client: OAuth2Client, token_path: string) => {
+ return new Promise<TokenResult>((resolve, reject) => {
+ let headerParameters = { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } };
+ let queryParameters = {
+ refreshToken: credentials.refresh_token,
+ client_id,
+ client_secret,
+ grant_type: "refresh_token"
+ };
+ let url = `${refreshEndpoint}?${qs.stringify(queryParameters)}`;
+ request.post(url, headerParameters).then(response => {
+ let parsed = JSON.parse(response);
+ credentials.access_token = parsed.access_token;
+ credentials.expiry_date = new Date().getTime() + parsed.expires_in;
+ writeFile(token_path, JSON.stringify(credentials), (err) => {
+ if (err) {
+ console.error(err);
+ reject(err);
+ }
+ console.log('Refreshed token stored to', token_path);
+ oAuth2Client.setCredentials(credentials);
+ resolve({ token: credentials, client: oAuth2Client });
+ });
+ });
+ });
+ };
+
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
diff --git a/src/server/apis/google/GooglePhotosServerUtils.ts b/src/server/apis/google/GooglePhotosServerUtils.ts
new file mode 100644
index 000000000..cb5464abc
--- /dev/null
+++ b/src/server/apis/google/GooglePhotosServerUtils.ts
@@ -0,0 +1,68 @@
+import request = require('request-promise');
+import { Album } from './typings/albums';
+import * as qs from 'query-string';
+
+const apiEndpoint = "https://photoslibrary.googleapis.com/v1/";
+
+export interface Authorization {
+ token: string;
+}
+
+export namespace GooglePhotos {
+
+ export type Query = Album.Query;
+ export type QueryParameters = { query: GooglePhotos.Query };
+ interface DispatchParameters {
+ required: boolean;
+ method: "GET" | "POST";
+ ignore?: boolean;
+ }
+
+ export const ExecuteQuery = async (parameters: Authorization & QueryParameters): Promise<any> => {
+ let action = parameters.query.action;
+ let dispatch = SuffixMap.get(action)!;
+ let suffix = Suffix(parameters, dispatch, action);
+ if (suffix) {
+ let query: any = parameters.query;
+ let options: any = {
+ headers: { 'Content-Type': 'application/json' },
+ auth: { 'bearer': parameters.token },
+ };
+ if (query.body) {
+ options.body = query.body;
+ options.json = true;
+ }
+ let queryParameters = query.parameters;
+ if (queryParameters) {
+ suffix += `?${qs.stringify(queryParameters)}`;
+ }
+ let dispatcher = dispatch.method === "POST" ? request.post : request.get;
+ return dispatcher(apiEndpoint + suffix, options);
+ }
+ };
+
+ const Suffix = (parameters: QueryParameters, dispatch: DispatchParameters, action: Album.Action) => {
+ let query: any = parameters.query;
+ let id = query.albumId;
+ let suffix = 'albums';
+ if (dispatch.required) {
+ if (!id) {
+ return undefined;
+ }
+ suffix += `/${id}${dispatch.ignore ? "" : `:${action}`}`;
+ }
+ return suffix;
+ };
+
+ const SuffixMap = new Map<Album.Action, DispatchParameters>([
+ [Album.Action.AddEnrichment, { required: true, method: "POST" }],
+ [Album.Action.BatchAddMediaItems, { required: true, method: "POST" }],
+ [Album.Action.BatchRemoveMediaItems, { required: true, method: "POST" }],
+ [Album.Action.Create, { required: false, method: "POST" }],
+ [Album.Action.Get, { required: true, ignore: true, method: "GET" }],
+ [Album.Action.List, { required: false, method: "GET" }],
+ [Album.Action.Share, { required: true, method: "POST" }],
+ [Album.Action.Unshare, { required: true, method: "POST" }]
+ ]);
+
+}
diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts
new file mode 100644
index 000000000..2e1599aaf
--- /dev/null
+++ b/src/server/apis/google/GooglePhotosUploadUtils.ts
@@ -0,0 +1,42 @@
+import request = require('request-promise');
+import { Authorization } from './GooglePhotosServerUtils';
+
+export namespace GooglePhotosUploadUtils {
+
+ interface UploadInformation {
+ title: string;
+ url: URL;
+ }
+
+ const apiEndpoint = "https://photoslibrary.googleapis.com/v1/uploads";
+
+ export const SubmitUpload = async (parameters: Authorization & UploadInformation) => {
+ let MEDIA_BINARY_DATA = binary(parameters.url.href);
+
+ let options = {
+ headers: {
+ 'Content-Type': 'application/octet-stream',
+ Authorization: { 'bearer': parameters.token },
+ 'X-Goog-Upload-File-Name': parameters.title,
+ 'X-Goog-Upload-Protocol': 'raw'
+ },
+ body: { MEDIA_BINARY_DATA },
+ json: true
+ };
+ const result = await request.post(apiEndpoint, options);
+ return result;
+ };
+
+ const binary = (source: string) => {
+ const image = document.createElement("img");
+ image.src = source;
+ const canvas = document.createElement("canvas");
+ canvas.width = image.width;
+ canvas.height = image.height;
+ const ctx = canvas.getContext("2d")!;
+ ctx.drawImage(image, 0, 0);
+ const dataUrl = canvas.toDataURL("image/png");
+ return dataUrl.replace(/^data:image\/(png|jpg);base64,/, "");
+ };
+
+} \ No newline at end of file
diff --git a/src/server/apis/google/GooglePhotosUtils.ts b/src/server/apis/google/GooglePhotosUtils.ts
deleted file mode 100644
index 750630626..000000000
--- a/src/server/apis/google/GooglePhotosUtils.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import request = require('request-promise');
-import { Album } from './typings/albums';
-
-const apiEndpoint = "https://photoslibrary.googleapis.com";
-
-export namespace GooglePhotos {
-
- export type Query = Album.Query;
-
- export const ExecuteQuery = async (authToken: string, query: GooglePhotos.Query) => {
- let options = {
- headers: { 'Content-Type': 'application/json' },
- auth: { 'bearer': authToken },
- body: query.body,
- json: true
- };
- const result = await request.post(apiEndpoint + '/v1/albums', options);
- return result;
- };
-
-}
diff --git a/src/server/apis/google/typings/albums.ts b/src/server/apis/google/typings/albums.ts
index 1c9b379fe..f3025567d 100644
--- a/src/server/apis/google/typings/albums.ts
+++ b/src/server/apis/google/typings/albums.ts
@@ -1,16 +1,16 @@
export namespace Album {
- export type Query = (AddEnrichment | BatchAddMediaItems | BatchRemoveMediaItems | Create | Get | List | Share | Unshare) & { body: any };
+ export type Query = (AddEnrichment | BatchAddMediaItems | BatchRemoveMediaItems | Create | Get | List | Share | Unshare);
export enum Action {
- AddEnrichment,
- BatchAddMediaItems,
- BatchRemoveMediaItems,
- Create,
- Get,
- List,
- Share,
- Unshare
+ AddEnrichment = "addEnrichment",
+ BatchAddMediaItems = "batchAddMediaItems",
+ BatchRemoveMediaItems = "batchRemoveMediaItems",
+ Create = "create",
+ Get = "get",
+ List = "list",
+ Share = "share",
+ Unshare = "unshare"
}
export interface AddEnrichment {
@@ -52,11 +52,13 @@ export namespace Album {
export interface List {
action: Action.List;
- parameters: {
- pageSize: number,
- pageToken: string,
- excludeNonAppCreatedData: boolean
- };
+ parameters: ListOptions;
+ }
+
+ export interface ListOptions {
+ pageSize: number;
+ pageToken: string;
+ excludeNonAppCreatedData: boolean;
}
export interface Share {
diff --git a/src/server/credentials/google_docs_token.json b/src/server/credentials/google_docs_token.json
index 61864512c..39e067c86 100644
--- a/src/server/credentials/google_docs_token.json
+++ b/src/server/credentials/google_docs_token.json
@@ -1 +1 @@
-{"access_token":"ya29.Glt3B8HoVEda7Ab5TQMVrfvjPN2fFp4sFHtGoDs3TsBgFfw4G208q90JiFjkmQqwODjJi3sf4NCZd78VZTVL3aI0By7_ElZF7XaCvA0LJnfcAi2gi1P-2-boyjYO","refresh_token":"1/tJOVDbPZlADzd2B8Q2_j7jqignXlRwHsU7LbZkdbDBc","scope":"https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/photoslibrary.sharing https://www.googleapis.com/auth/photoslibrary","token_type":"Bearer","expiry_date":1567374969108} \ No newline at end of file
+{"access_token":"ya29.Glx4B9p8tvFsmsD-AD-D4jygL_YZVCFpPewM9djtfOT3T4S6ROxN5r0WLAKTYVNnXQbUri3Gu_-vIb0NWq9wEy1TdFTLIM8azWD82X5-I5BQq2DSOsYiKugvgVoHLw","refresh_token":"1/kk_pPY7WBwT34JNPzx_HrSVoZlvfzys4EEVNjp7nqzg6aIbRrEKNMRTb0u0wr9GM","scope":"https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary.sharing https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/photoslibrary","token_type":"Bearer","expiry_date":1567529401142} \ No newline at end of file
diff --git a/src/server/index.ts b/src/server/index.ts
index 1f105e9d2..54b954cfb 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -42,11 +42,7 @@ var AdmZip = require('adm-zip');
import * as YoutubeApi from "./apis/youtube/youtubeApiSample";
import { Response } from 'express-serve-static-core';
import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils";
-// import { GaxiosResponse } from 'gaxios';
-// import { Opt } from '../new_fields/Doc';
-// import { docs_v1 } from 'googleapis';
-// import { Endpoint } from 'googleapis-common';
-// import { PhotosLibraryQuery } from './apis/google/GooglePhotosUtils';
+import { GooglePhotos } from './apis/google/GooglePhotosServerUtils';
const MongoStore = require('connect-mongo')(session);
const mongoose = require('mongoose');
const probe = require("probe-image-size");
@@ -195,32 +191,7 @@ app.get("/version", (req, res) => {
// SEARCH
const solrURL = "http://localhost:8983/solr/#/dash";
-// GETTERS
-
-// app.get('/auth/google', passport.authenticate('google', {
-// scope: OAuthConfig.scopes,
-// failureFlash: true, // Display errors to the user.
-// session: true,
-// }));
-
-// app.get("/failed", (req, res) => res.send("DIDN'T WORK!"));
-
-// app.get(
-// '/auth/google/callback',
-// passport.authenticate(
-// 'google', { failureRedirect: '/failed', failureFlash: true, session: true }),
-// (req, res) => {
-// // User has logged in.
-// console.log('OAUTH: user has logged in 1.');
-// PhotosLibraryQuery(req.user.token, {});
-// console.log('OAUTH: user has logged in 2.');
-// res.redirect('/');
-// });
-
-// app.get('/GooglePhotos', (req, res) => {
-// console.log("WORKING ON GOOGLE PHOTOS");
-// PhotosLibraryQuery(req.user.token, {});
-// });
+// GETTERSÃ¥
app.get("/search", async (req, res) => {
const solrQuery: any = {};
@@ -853,6 +824,27 @@ app.post(RouteStore.googleDocs + "/:sector/:action", (req, res) => {
});
});
+app.post(RouteStore.googlePhotos, (req, res) => {
+ GoogleApiServerUtils.RetrieveAuthenticationInformation({ credentials, token }).then(authentication => {
+ let validated = authentication.token.access_token;
+ if (!validated) {
+ res.send("Error: unable to authenticate Google Photos API request.");
+ return;
+ }
+ GooglePhotos.ExecuteQuery({ token: validated, query: req.body })
+ .then(response => {
+ if (response === undefined) {
+ res.send("Error: unable to build suffix for Google Photos API request");
+ return;
+ }
+ res.send(response);
+ })
+ .catch(error => {
+ res.send(`Error: an exception occurred in the execution of this Google Photos API request\n${error}`);
+ });
+ });
+});
+
const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = {
"number": "_n",
"string": "_t",