frontend > app
This commit is contained in:
114
app/utils/ApiClient.js
Normal file
114
app/utils/ApiClient.js
Normal file
@@ -0,0 +1,114 @@
|
||||
// @flow
|
||||
import _ from 'lodash';
|
||||
import invariant from 'invariant';
|
||||
import stores from 'stores';
|
||||
|
||||
type Options = {
|
||||
baseUrl?: string,
|
||||
};
|
||||
|
||||
class ApiClient {
|
||||
baseUrl: string;
|
||||
userAgent: string;
|
||||
|
||||
constructor(options: Options = {}) {
|
||||
this.baseUrl = options.baseUrl || '/api';
|
||||
this.userAgent = 'AtlasFrontend';
|
||||
}
|
||||
|
||||
fetch = (
|
||||
path: string,
|
||||
method: string,
|
||||
data: ?Object,
|
||||
options: Object = {}
|
||||
) => {
|
||||
let body;
|
||||
let modifiedPath;
|
||||
|
||||
if (method === 'GET') {
|
||||
if (data) {
|
||||
modifiedPath = `${path}?${data && this.constructQueryString(data)}`;
|
||||
} else {
|
||||
modifiedPath = path;
|
||||
}
|
||||
} else if (method === 'POST' || method === 'PUT') {
|
||||
body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
// Construct headers
|
||||
const headers = new Headers({
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
if (stores.auth.authenticated) {
|
||||
invariant(stores.auth.token, 'JWT token not set properly');
|
||||
headers.set('Authorization', `Bearer ${stores.auth.token}`);
|
||||
}
|
||||
|
||||
// Construct request
|
||||
// $FlowFixMe don't care much about this right now
|
||||
const request = fetch(this.baseUrl + (modifiedPath || path), {
|
||||
method,
|
||||
body,
|
||||
headers,
|
||||
redirect: 'follow',
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
// Handle request promises and return a new promise
|
||||
return new Promise((resolve, reject) => {
|
||||
request
|
||||
.then(response => {
|
||||
// Handle successful responses
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response;
|
||||
}
|
||||
|
||||
// Handle 401, log out user
|
||||
if (response.status === 401) {
|
||||
stores.auth.logout();
|
||||
window.location = '/';
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle failed responses
|
||||
const error = {};
|
||||
error.statusCode = response.status;
|
||||
error.response = response;
|
||||
throw error;
|
||||
})
|
||||
.then(response => {
|
||||
return response && response.json();
|
||||
})
|
||||
.then(json => {
|
||||
resolve(json);
|
||||
})
|
||||
.catch(error => {
|
||||
error.response.json().then(json => {
|
||||
error.data = json;
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
get = (path: string, data: ?Object, options?: Object) => {
|
||||
return this.fetch(path, 'GET', data, options);
|
||||
};
|
||||
|
||||
post = (path: string, data: ?Object, options?: Object) => {
|
||||
return this.fetch(path, 'POST', data, options);
|
||||
};
|
||||
|
||||
// Helpers
|
||||
constructQueryString = (data: Object) => {
|
||||
return _.map(data, (v, k) => {
|
||||
return `${encodeURIComponent(k)}=${encodeURIComponent(v)}`;
|
||||
}).join('&');
|
||||
};
|
||||
}
|
||||
|
||||
export default ApiClient;
|
||||
|
||||
// In case you don't want to always initiate, just import with `import { client } ...`
|
||||
export const client = new ApiClient();
|
||||
6
app/utils/__mocks__/ApiClient.js
Normal file
6
app/utils/__mocks__/ApiClient.js
Normal file
@@ -0,0 +1,6 @@
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
client: {
|
||||
post: jest.fn(() => Promise.resolve),
|
||||
},
|
||||
};
|
||||
18
app/utils/getDataTransferFiles.js
Normal file
18
app/utils/getDataTransferFiles.js
Normal file
@@ -0,0 +1,18 @@
|
||||
// @flow
|
||||
export default function getDataTransferFiles(event: SyntheticEvent) {
|
||||
let dataTransferItemsList = [];
|
||||
if (event.dataTransfer) {
|
||||
const dt = event.dataTransfer;
|
||||
if (dt.files && dt.files.length) {
|
||||
dataTransferItemsList = dt.files;
|
||||
} else if (dt.items && dt.items.length) {
|
||||
// During the drag even the dataTransfer.files is null
|
||||
// but Chrome implements some drag store, which is accesible via dataTransfer.items
|
||||
dataTransferItemsList = dt.items;
|
||||
}
|
||||
} else if (event.target && event.target.files) {
|
||||
dataTransferItemsList = event.target.files;
|
||||
}
|
||||
// Convert from DataTransferItemsList to the native Array
|
||||
return Array.prototype.slice.call(dataTransferItemsList);
|
||||
}
|
||||
6
app/utils/random.js
Normal file
6
app/utils/random.js
Normal file
@@ -0,0 +1,6 @@
|
||||
// @flow
|
||||
const randomInteger = (min: number, max: number) => {
|
||||
return Math.floor(Math.random() * (max - min + 1) + min);
|
||||
};
|
||||
|
||||
export { randomInteger };
|
||||
97
app/utils/routeHelpers.js
Normal file
97
app/utils/routeHelpers.js
Normal file
@@ -0,0 +1,97 @@
|
||||
// @flow
|
||||
import Document from 'models/Document';
|
||||
import Collection from 'models/Collection';
|
||||
|
||||
export function homeUrl(): string {
|
||||
return '/dashboard';
|
||||
}
|
||||
|
||||
export function starredUrl(): string {
|
||||
return '/starred';
|
||||
}
|
||||
|
||||
export function newCollectionUrl(): string {
|
||||
return '/collections/new';
|
||||
}
|
||||
|
||||
export function collectionUrl(collectionId: string): string {
|
||||
return `/collections/${collectionId}`;
|
||||
}
|
||||
|
||||
export function documentUrl(doc: Document): string {
|
||||
return doc.url;
|
||||
}
|
||||
|
||||
export function slackAuth(
|
||||
state: string,
|
||||
scopes: string[] = [
|
||||
'identity.email',
|
||||
'identity.basic',
|
||||
'identity.avatar',
|
||||
'identity.team',
|
||||
],
|
||||
redirectUri: string = `${BASE_URL}/auth/slack`
|
||||
): string {
|
||||
const baseUrl = 'https://slack.com/oauth/authorize';
|
||||
const params = {
|
||||
client_id: SLACK_KEY,
|
||||
scope: scopes ? scopes.join(' ') : '',
|
||||
redirect_uri: redirectUri,
|
||||
state,
|
||||
};
|
||||
|
||||
const urlParams = Object.keys(params)
|
||||
.map(key => `${key}=${encodeURIComponent(params[key])}`)
|
||||
.join('&');
|
||||
|
||||
return `${baseUrl}?${urlParams}`;
|
||||
}
|
||||
|
||||
export function documentNewUrl(doc: Document): string {
|
||||
const newUrl = `${doc.collection.url}/new`;
|
||||
if (doc.parentDocumentId) {
|
||||
return `${newUrl}?parentDocument=${doc.parentDocumentId}`;
|
||||
}
|
||||
return newUrl;
|
||||
}
|
||||
|
||||
export function documentEditUrl(doc: Document): string {
|
||||
return `${doc.url}/edit`;
|
||||
}
|
||||
|
||||
export function documentMoveUrl(doc: Document): string {
|
||||
return `${doc.url}/move`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace full url's document part with the new one in case
|
||||
* the document slug has been updated
|
||||
*/
|
||||
export function updateDocumentUrl(oldUrl: string, newUrl: string): string {
|
||||
// Update url to match the current one
|
||||
const urlParts = oldUrl.trim().split('/');
|
||||
const actions = urlParts.slice(3);
|
||||
if (actions[0]) {
|
||||
return [newUrl, actions].join('/');
|
||||
}
|
||||
return newUrl;
|
||||
}
|
||||
|
||||
export function newDocumentUrl(collection: Collection): string {
|
||||
return `${collection.url}/new`;
|
||||
}
|
||||
|
||||
export function searchUrl(query?: string): string {
|
||||
if (query) return `/search/${query}`;
|
||||
return `/search`;
|
||||
}
|
||||
|
||||
export function notFoundUrl(): string {
|
||||
return '/404';
|
||||
}
|
||||
|
||||
export const matchDocumentSlug =
|
||||
':documentSlug([0-9a-zA-Z-]*-[a-zA-z0-9]{10,15})';
|
||||
|
||||
export const matchDocumentEdit = `/doc/${matchDocumentSlug}/edit`;
|
||||
export const matchDocumentMove = `/doc/${matchDocumentSlug}/move`;
|
||||
44
app/utils/uploadFile.js
Normal file
44
app/utils/uploadFile.js
Normal file
@@ -0,0 +1,44 @@
|
||||
// @flow
|
||||
import { client } from './ApiClient';
|
||||
import invariant from 'invariant';
|
||||
|
||||
type File = {
|
||||
blob: boolean,
|
||||
type: string,
|
||||
size: number,
|
||||
name: string,
|
||||
file: string,
|
||||
};
|
||||
|
||||
export default async function uploadFile(file: File) {
|
||||
const response = await client.post('/user.s3Upload', {
|
||||
kind: file.type,
|
||||
size: file.size,
|
||||
filename: file.name,
|
||||
});
|
||||
|
||||
invariant(response, 'Response should be available');
|
||||
|
||||
const data = response.data;
|
||||
const asset = data.asset;
|
||||
const formData = new FormData();
|
||||
|
||||
for (const key in data.form) {
|
||||
formData.append(key, data.form[key]);
|
||||
}
|
||||
|
||||
if (file.blob) {
|
||||
formData.append('file', file.file);
|
||||
} else {
|
||||
// $FlowFixMe
|
||||
formData.append('file', file);
|
||||
}
|
||||
|
||||
const options: Object = {
|
||||
method: 'post',
|
||||
body: formData,
|
||||
};
|
||||
await fetch(data.uploadUrl, options);
|
||||
|
||||
return asset;
|
||||
}
|
||||
Reference in New Issue
Block a user