aboutsummaryrefslogtreecommitdiff
path: root/src/server/apis
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/apis')
-rw-r--r--src/server/apis/google/GoogleApiServerUtils.ts28
-rw-r--r--src/server/apis/google/GooglePhotosServerUtils.ts68
-rw-r--r--src/server/apis/google/GooglePhotosUploadUtils.ts122
-rw-r--r--src/server/apis/google/typings/albums.ts150
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