diff options
| author | andrewdkim <adkim414@gmail.com> | 2019-09-15 17:00:46 -0400 | 
|---|---|---|
| committer | andrewdkim <adkim414@gmail.com> | 2019-09-15 17:00:46 -0400 | 
| commit | 7dba132c37b0f4402e375d95c068a5fe31904a1f (patch) | |
| tree | cd4ab0558f6b599cf685f99e542f24d86328e0cc /src/server/apis/google | |
| parent | c7678db105f952e7562f1b573266fb295e13cf7b (diff) | |
| parent | 143d4669e764f6967d4d826b00b29912892ca637 (diff) | |
new changes + pull from master
Diffstat (limited to 'src/server/apis/google')
| -rw-r--r-- | src/server/apis/google/GoogleApiServerUtils.ts | 130 | ||||
| -rw-r--r-- | src/server/apis/google/GooglePhotosUploadUtils.ts | 176 | 
2 files changed, 306 insertions, 0 deletions
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts new file mode 100644 index 000000000..8785cd974 --- /dev/null +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -0,0 +1,130 @@ +import { google, docs_v1, slides_v1 } from "googleapis"; +import { createInterface } from "readline"; +import { readFile, writeFile } from "fs"; +import { OAuth2Client } from "google-auth-library"; +import { Opt } from "../../../new_fields/Doc"; +import { GlobalOptions } from "googleapis-common"; +import { GaxiosResponse } from "gaxios"; + +/** + * Server side authentication for Google Api queries. + */ +export namespace GoogleApiServerUtils { + +    // If modifying these scopes, delete token.json. +    const prefix = 'https://www.googleapis.com/auth/'; +    const SCOPES = [ +        'documents.readonly', +        'documents', +        'presentations', +        'presentations.readonly', +        'drive', +        'drive.file', +    ]; + +    export const parseBuffer = (data: Buffer) => JSON.parse(data.toString()); + +    export enum Service { +        Documents = "Documents", +        Slides = "Slides" +    } + + +    export interface CredentialPaths { +        credentials: string; +        token: string; +    } + +    export type ApiResponse = Promise<GaxiosResponse>; +    export type ApiRouter = (endpoint: Endpoint, paramters: any) => ApiResponse; +    export type ApiHandler = (parameters: any) => ApiResponse; +    export type Action = "create" | "retrieve" | "update"; + +    export type Endpoint = { get: ApiHandler, create: ApiHandler, batchUpdate: ApiHandler }; +    export type EndpointParameters = GlobalOptions & { version: "v1" }; + +    export const GetEndpoint = async (sector: string, paths: CredentialPaths) => { +        return new Promise<Opt<Endpoint>>((resolve, reject) => { +            readFile(paths.credentials, (err, credentials) => { +                if (err) { +                    reject(err); +                    return console.log('Error loading client secret file:', err); +                } +                return authorize(parseBuffer(credentials), paths.token).then(auth => { +                    let routed: Opt<Endpoint>; +                    let parameters: EndpointParameters = { auth, version: "v1" }; +                    switch (sector) { +                        case Service.Documents: +                            routed = google.docs(parameters).documents; +                            break; +                        case Service.Slides: +                            routed = google.slides(parameters).presentations; +                            break; +                    } +                    resolve(routed); +                }); +            }); +        }); +    }; + + +    /** +     * Create an OAuth2 client with the given credentials, and returns the promise resolving to the authenticated client +     * @param {Object} credentials The authorization client credentials. +     */ +    export function authorize(credentials: any, token_path: string): Promise<OAuth2Client> { +        const { client_secret, client_id, redirect_uris } = credentials.installed; +        const oAuth2Client = new google.auth.OAuth2( +            client_id, client_secret, redirect_uris[0]); + +        return new Promise<OAuth2Client>((resolve, reject) => { +            readFile(token_path, (err, token) => { +                // Check if we have previously stored a token. +                if (err) { +                    return getNewToken(oAuth2Client, token_path).then(resolve, reject); +                } +                oAuth2Client.setCredentials(parseBuffer(token)); +                resolve(oAuth2Client); +            }); +        }); +    } + +    /** +     * Get and store new token after prompting for user authorization, and then +     * execute the given callback with the authorized OAuth2 client. +     * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for. +     * @param {getEventsCallback} callback The callback for the authorized client. +     */ +    function getNewToken(oAuth2Client: OAuth2Client, token_path: string) { +        return new Promise<OAuth2Client>((resolve, reject) => { +            const authUrl = oAuth2Client.generateAuthUrl({ +                access_type: 'offline', +                scope: SCOPES.map(relative => prefix + relative), +            }); +            console.log('Authorize this app by visiting this url:', authUrl); +            const rl = createInterface({ +                input: process.stdin, +                output: process.stdout, +            }); +            rl.question('Enter the code from that page here: ', (code) => { +                rl.close(); +                oAuth2Client.getToken(code, (err, token) => { +                    if (err || !token) { +                        reject(err); +                        return console.error('Error retrieving access token', err); +                    } +                    oAuth2Client.setCredentials(token); +                    // Store the token to disk for later program executions +                    writeFile(token_path, JSON.stringify(token), (err) => { +                        if (err) { +                            console.error(err); +                            reject(err); +                        } +                        console.log('Token stored to', token_path); +                    }); +                    resolve(oAuth2Client); +                }); +            }); +        }); +    } +}
\ No newline at end of file diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts new file mode 100644 index 000000000..35f986250 --- /dev/null +++ b/src/server/apis/google/GooglePhotosUploadUtils.ts @@ -0,0 +1,176 @@ +import request = require('request-promise'); +import { GoogleApiServerUtils } from './GoogleApiServerUtils'; +import * as fs from 'fs'; +import { Utils } from '../../../Utils'; +import * as path from 'path'; +import { Opt } from '../../../new_fields/Doc'; +import * as sharp from 'sharp'; + +const uploadDirectory = path.join(__dirname, "../../public/files/"); + +export namespace GooglePhotosUploadUtils { + +    export interface Paths { +        uploadDirectory: string; +        credentialsPath: string; +        tokenPath: string; +    } + +    export interface MediaInput { +        url: string; +        description: string; +    } + +    const prepend = (extension: string) => `https://photoslibrary.googleapis.com/v1/${extension}`; +    const headers = (type: string) => ({ +        'Content-Type': `application/${type}`, +        'Authorization': Bearer, +    }); + +    let Bearer: string; +    let Paths: Paths; + +    export const initialize = async (paths: Paths) => { +        Paths = paths; +        const { tokenPath, credentialsPath } = paths; +        const token = await GoogleApiServerUtils.RetrieveAccessToken({ tokenPath, credentialsPath }); +        Bearer = `Bearer ${token}`; +    }; + +    export const DispatchGooglePhotosUpload = async (url: string) => { +        const body = await request(url, { encoding: null }); +        const parameters = { +            method: 'POST', +            headers: { +                ...headers('octet-stream'), +                'X-Goog-Upload-File-Name': path.basename(url), +                'X-Goog-Upload-Protocol': 'raw' +            }, +            uri: prepend('uploads'), +            body +        }; +        return new Promise<any>(resolve => request(parameters, (error, _response, body) => resolve(error ? undefined : body))); +    }; + +    export const CreateMediaItems = (newMediaItems: any[], album?: { id: string }) => { +        return new Promise<any>((resolve, reject) => { +            const parameters = { +                method: 'POST', +                headers: headers('json'), +                uri: prepend('mediaItems:batchCreate'), +                body: { newMediaItems } as any, +                json: true +            }; +            album && (parameters.body.albumId = album.id); +            request(parameters, (error, _response, body) => { +                if (error) { +                    reject(error); +                } else { +                    resolve(body); +                } +            }); +        }); +    }; + +} + +export namespace DownloadUtils { + +    export interface Size { +        width: number; +        suffix: string; +    } + +    export const Sizes: { [size: string]: Size } = { +        SMALL: { width: 100, suffix: "_s" }, +        MEDIUM: { width: 400, suffix: "_m" }, +        LARGE: { width: 900, suffix: "_l" }, +    }; + +    const png = ".png"; +    const pngs = [".png", ".PNG"]; +    const jpgs = [".jpg", ".JPG", ".jpeg", ".JPEG"]; +    const formats = [".jpg", ".png", ".gif"]; +    const size = "content-length"; +    const type = "content-type"; + +    export interface UploadInformation { +        mediaPaths: string[]; +        fileNames: { [key: string]: string }; +        contentSize?: number; +        contentType?: string; +    } + +    const generate = (prefix: string, url: string) => `${prefix}upload_${Utils.GenerateGuid()}${path.extname(url).toLowerCase()}`; +    const sanitize = (filename: string) => filename.replace(/\s+/g, "_"); + +    export const UploadImage = async (url: string, filename?: string, prefix = ""): Promise<Opt<UploadInformation>> => { +        const resolved = filename ? sanitize(filename) : generate(prefix, url); +        const extension = path.extname(url) || path.extname(resolved) || png; +        let information: UploadInformation = { +            mediaPaths: [], +            fileNames: { clean: resolved } +        }; +        const { isLocal, stream, normalized } = classify(url); +        url = normalized; +        if (!isLocal) { +            const metadata = (await new Promise<any>((resolve, reject) => { +                request.head(url, async (error, res) => { +                    if (error) { +                        return reject(error); +                    } +                    resolve(res); +                }); +            })).headers; +            information.contentSize = parseInt(metadata[size]); +            information.contentType = metadata[type]; +        } +        return new Promise<UploadInformation>(async (resolve, reject) => { +            const resizers = [ +                { resizer: sharp().rotate(), suffix: "_o" }, +                ...Object.values(Sizes).map(size => ({ +                    resizer: sharp().resize(size.width, undefined, { withoutEnlargement: true }).rotate(), +                    suffix: size.suffix +                })) +            ]; +            if (pngs.includes(extension)) { +                resizers.forEach(element => element.resizer = element.resizer.png()); +            } else if (jpgs.includes(extension)) { +                resizers.forEach(element => element.resizer = element.resizer.jpeg()); +            } else if (!formats.includes(extension.toLowerCase())) { +                return reject(); +            } +            for (let resizer of resizers) { +                const suffix = resizer.suffix; +                let mediaPath: string; +                await new Promise<void>(resolve => { +                    const filename = resolved.substring(0, resolved.length - extension.length) + suffix + extension; +                    information.mediaPaths.push(mediaPath = uploadDirectory + filename); +                    information.fileNames[suffix] = filename; +                    stream(url).pipe(resizer.resizer).pipe(fs.createWriteStream(mediaPath)) +                        .on('close', resolve) +                        .on('error', reject); +                }); +            } +            resolve(information); +        }); +    }; + +    const classify = (url: string) => { +        const isLocal = /Dash-Web(\\|\/)src(\\|\/)server(\\|\/)public(\\|\/)files/g.test(url); +        return { +            isLocal, +            stream: isLocal ? fs.createReadStream : request, +            normalized: isLocal ? path.normalize(url) : url +        }; +    }; + +    export const createIfNotExists = async (path: string) => { +        if (await new Promise<boolean>(resolve => fs.exists(path, resolve))) { +            return true; +        } +        return new Promise<boolean>(resolve => fs.mkdir(path, error => resolve(error === null))); +    }; + +    export const Destroy = (mediaPath: string) => new Promise<boolean>(resolve => fs.unlink(mediaPath, error => resolve(error === null))); +}
\ No newline at end of file  | 
