diff options
Diffstat (limited to 'src/server/apis')
-rw-r--r-- | src/server/apis/google/GoogleApiServerUtils.ts | 28 | ||||
-rw-r--r-- | src/server/apis/google/GooglePhotosServerUtils.ts | 68 | ||||
-rw-r--r-- | src/server/apis/google/GooglePhotosUploadUtils.ts | 122 | ||||
-rw-r--r-- | src/server/apis/google/typings/albums.ts | 150 |
4 files changed, 127 insertions, 241 deletions
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts index b6330a609..ac8023ce1 100644 --- a/src/server/apis/google/GoogleApiServerUtils.ts +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -7,6 +7,7 @@ import { GlobalOptions } from "googleapis-common"; import { GaxiosResponse } from "gaxios"; import request = require('request-promise'); import * as qs from 'query-string'; +import Photos = require('googlephotos'); /** * Server side authentication for Google Api queries. @@ -35,19 +36,19 @@ export namespace GoogleApiServerUtils { } export interface CredentialPaths { - credentials: string; - token: string; + credentialsPath: string; + tokenPath: string; } export type ApiResponse = Promise<GaxiosResponse>; - export type ApiRouter = (endpoint: Endpoint, paramters: any) => ApiResponse; + export type ApiRouter = (endpoint: Endpoint, parameters: 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) => { + export const GetEndpoint = (sector: string, paths: CredentialPaths) => { return new Promise<Opt<Endpoint>>(resolve => { RetrieveCredentials(paths).then(authentication => { let routed: Opt<Endpoint>; @@ -65,19 +66,19 @@ export namespace GoogleApiServerUtils { }); }; - export const RetrieveCredentials = async (paths: CredentialPaths) => { + export const RetrieveCredentials = (paths: CredentialPaths) => { return new Promise<TokenResult>((resolve, reject) => { - readFile(paths.credentials, async (err, credentials) => { + readFile(paths.credentialsPath, async (err, credentials) => { if (err) { reject(err); return console.log('Error loading client secret file:', err); } - authorize(parseBuffer(credentials), paths.token).then(resolve, reject); + authorize(parseBuffer(credentials), paths.tokenPath).then(resolve, reject); }); }); }; - export const RetrieveAccessToken = async (paths: CredentialPaths) => { + export const RetrieveAccessToken = (paths: CredentialPaths) => { return new Promise<string>((resolve, reject) => { RetrieveCredentials(paths).then( credentials => resolve(credentials.token.access_token!), @@ -86,6 +87,15 @@ export namespace GoogleApiServerUtils { }); }; + export const RetrievePhotosEndpoint = (paths: CredentialPaths) => { + return new Promise<any>((resolve, reject) => { + RetrieveAccessToken(paths).then( + token => resolve(new Photos(token)), + reject + ); + }); + }; + type TokenResult = { token: Credentials, client: OAuth2Client }; /** * Create an OAuth2 client with the given credentials, and returns the promise resolving to the authenticated client @@ -126,7 +136,7 @@ export namespace GoogleApiServerUtils { 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 * 1000; + credentials.expiry_date = new Date().getTime() + (parsed.expires_in * 1000); writeFile(token_path, JSON.stringify(credentials), (err) => { if (err) { console.error(err); diff --git a/src/server/apis/google/GooglePhotosServerUtils.ts b/src/server/apis/google/GooglePhotosServerUtils.ts deleted file mode 100644 index cb5464abc..000000000 --- a/src/server/apis/google/GooglePhotosServerUtils.ts +++ /dev/null @@ -1,68 +0,0 @@ -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 index cd2a586eb..3b513aaf1 100644 --- a/src/server/apis/google/GooglePhotosUploadUtils.ts +++ b/src/server/apis/google/GooglePhotosUploadUtils.ts @@ -1,28 +1,122 @@ import request = require('request-promise'); -import { Authorization } from './GooglePhotosServerUtils'; +import { GoogleApiServerUtils } from './GoogleApiServerUtils'; +import * as fs from 'fs'; +import { Utils } from '../../../Utils'; +import * as path from 'path'; +import { Opt } from '../../../new_fields/Doc'; export namespace GooglePhotosUploadUtils { - interface UploadInformation { - title: string; - MEDIA_BINARY_DATA: string; + export interface Paths { + uploadDirectory: string; + credentialsPath: string; + tokenPath: string; } - const apiEndpoint = "https://photoslibrary.googleapis.com/v1/uploads"; + export interface MediaInput { + description: string; + source: string; + } + + export interface DownloadInformation { + mediaPath: string; + contentType?: string; + contentSize?: 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 SubmitUpload = async (parameters: Authorization & UploadInformation) => { - let options = { + 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 (filename: string) => { + let body: Buffer; + if (filename.includes('upload_')) { + const mediaPath = Paths.uploadDirectory + filename; + body = await new Promise<Buffer>((resolve, reject) => { + fs.readFile(mediaPath, (error, data) => error ? reject(error) : resolve(data)); + }); + } else { + body = await request(filename, { encoding: null }); + } + const parameters = { + method: 'POST', headers: { - 'Content-Type': 'application/octet-stream', - Authorization: `Bearer ${parameters.token}`, - 'X-Goog-Upload-File-Name': parameters.title, + ...headers('octet-stream'), + 'X-Goog-Upload-File-Name': filename, 'X-Goog-Upload-Protocol': 'raw' }, - body: { MEDIA_BINARY_DATA: parameters.MEDIA_BINARY_DATA }, - json: true + uri: prepend('uploads'), + body }; - const result = await request.post(apiEndpoint, options); - return result; + 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 IOUtils { + + export const Download = async (url: string): Promise<Opt<DownloadInformation>> => { + const filename = `temporary_upload_${Utils.GenerateGuid()}${path.extname(url).toLowerCase()}`; + const temporaryDirectory = Paths.uploadDirectory + "temporary/"; + const mediaPath = temporaryDirectory + filename; + + if (!(await createIfNotExists(temporaryDirectory))) { + return undefined; + } + + return new Promise<DownloadInformation>((resolve, reject) => { + request.head(url, (error, res) => { + if (error) { + return reject(error); + } + const information: DownloadInformation = { + mediaPath, + contentType: res.headers['content-type'], + contentSize: res.headers['content-length'], + }; + request(url).pipe(fs.createWriteStream(mediaPath)).on('close', () => resolve(information)); + }); + }); + }; + + 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 diff --git a/src/server/apis/google/typings/albums.ts b/src/server/apis/google/typings/albums.ts deleted file mode 100644 index f3025567d..000000000 --- a/src/server/apis/google/typings/albums.ts +++ /dev/null @@ -1,150 +0,0 @@ -export namespace Album { - - export type Query = (AddEnrichment | BatchAddMediaItems | BatchRemoveMediaItems | Create | Get | List | Share | Unshare); - - export enum Action { - AddEnrichment = "addEnrichment", - BatchAddMediaItems = "batchAddMediaItems", - BatchRemoveMediaItems = "batchRemoveMediaItems", - Create = "create", - Get = "get", - List = "list", - Share = "share", - Unshare = "unshare" - } - - export interface AddEnrichment { - action: Action.AddEnrichment; - albumId: string; - body: { - newEnrichmentItem: NewEnrichmentItem; - albumPosition: MediaRelativeAlbumPosition; - }; - } - - export interface BatchAddMediaItems { - action: Action.BatchAddMediaItems; - albumId: string; - body: { - mediaItemIds: string[]; - }; - } - - export interface BatchRemoveMediaItems { - action: Action.BatchRemoveMediaItems; - albumId: string; - body: { - mediaItemIds: string[]; - }; - } - - export interface Create { - action: Action.Create; - body: { - album: Template; - }; - } - - export interface Get { - action: Action.Get; - albumId: string; - } - - export interface List { - action: Action.List; - parameters: ListOptions; - } - - export interface ListOptions { - pageSize: number; - pageToken: string; - excludeNonAppCreatedData: boolean; - } - - export interface Share { - action: Action.Share; - albumId: string; - body: { - sharedAlbumOptions: SharedOptions; - }; - } - - export interface Unshare { - action: Action.Unshare; - albumId: string; - } - - export interface Template { - title: string; - } - - export interface Model { - id: string; - title: string; - productUrl: string; - isWriteable: boolean; - shareInfo: ShareInfo; - mediaItemsCount: string; - coverPhotoBaseUrl: string; - coverPhotoMediaItemId: string; - } - - export interface ShareInfo { - sharedAlbumOptions: SharedOptions; - shareableUrl: string; - shareToken: string; - isJoined: boolean; - isOwned: boolean; - } - - export interface SharedOptions { - isCollaborative: boolean; - isCommentable: boolean; - } - - export enum PositionType { - POSITION_TYPE_UNSPECIFIED, - FIRST_IN_ALBUM, - LAST_IN_ALBUM, - AFTER_MEDIA_ITEM, - AFTER_ENRICHMENT_ITEM - } - - export type Position = GeneralAlbumPosition | MediaRelativeAlbumPosition | EnrichmentRelativeAlbumPosition; - - interface GeneralAlbumPosition { - position: PositionType.FIRST_IN_ALBUM | PositionType.LAST_IN_ALBUM | PositionType.POSITION_TYPE_UNSPECIFIED; - } - - interface MediaRelativeAlbumPosition { - position: PositionType.AFTER_MEDIA_ITEM; - relativeMediaItemId: string; - } - - interface EnrichmentRelativeAlbumPosition { - position: PositionType.AFTER_ENRICHMENT_ITEM; - relativeEnrichmentItemId: string; - } - - export interface Location { - locationName: string; - latlng: { - latitude: number, - longitude: number - }; - } - - export interface NewEnrichmentItem { - textEnrichment: { - text: string; - }; - locationEnrichment: { - location: Location - }; - mapEnrichment: { - origin: { location: Location }, - destination: { location: Location } - }; - } - -}
\ No newline at end of file |