chore(server): Move api-server in to it's own DIR
This commit is contained in:
committed by
mrugesh mohapatra
parent
9fba6bce4c
commit
46a217d0a5
314
api-server/common/utils/ajax-stream.js
Normal file
314
api-server/common/utils/ajax-stream.js
Normal file
@ -0,0 +1,314 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
* Microsoft Open Technologies would like to thank its contributors, a list
|
||||
* of whom are at http://rx.codeplex.com/wikipage?title=Contributors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you
|
||||
* may not use this file except in compliance with the License. You may
|
||||
* obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
* implied. See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*/
|
||||
|
||||
import debugFactory from 'debug';
|
||||
import { Observable, AnonymousObservable, helpers } from 'rx';
|
||||
|
||||
const debug = debugFactory('fcc:ajax$');
|
||||
const root = typeof window !== 'undefined' ? window : {};
|
||||
|
||||
// Gets the proper XMLHttpRequest for support for older IE
|
||||
function getXMLHttpRequest() {
|
||||
if (root.XMLHttpRequest) {
|
||||
return new root.XMLHttpRequest();
|
||||
} else {
|
||||
var progId;
|
||||
try {
|
||||
var progIds = [
|
||||
'Msxml2.XMLHTTP',
|
||||
'Microsoft.XMLHTTP',
|
||||
'Msxml2.XMLHTTP.4.0'
|
||||
];
|
||||
for (var i = 0; i < 3; i++) {
|
||||
try {
|
||||
progId = progIds[i];
|
||||
if (new root.ActiveXObject(progId)) {
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// purposely do nothing
|
||||
helpers.noop(e);
|
||||
}
|
||||
}
|
||||
return new root.ActiveXObject(progId);
|
||||
} catch (e) {
|
||||
throw new Error('XMLHttpRequest is not supported by your browser');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get CORS support even for older IE
|
||||
function getCORSRequest() {
|
||||
var xhr = new root.XMLHttpRequest();
|
||||
if ('withCredentials' in xhr) {
|
||||
return xhr;
|
||||
} else if (root.XDomainRequest) {
|
||||
return new XDomainRequest();
|
||||
} else {
|
||||
throw new Error('CORS is not supported by your browser');
|
||||
}
|
||||
}
|
||||
|
||||
function parseXhrResponse(responseType, xhr) {
|
||||
switch (responseType) {
|
||||
case 'json':
|
||||
if ('response' in xhr) {
|
||||
return xhr.responseType ?
|
||||
xhr.response :
|
||||
JSON.parse(xhr.response || xhr.responseText || 'null');
|
||||
} else {
|
||||
return JSON.parse(xhr.responseText || 'null');
|
||||
}
|
||||
case 'xml':
|
||||
return xhr.responseXML;
|
||||
case 'text':
|
||||
default:
|
||||
return ('response' in xhr) ? xhr.response : xhr.responseText;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeAjaxSuccessEvent(e, xhr, settings) {
|
||||
return {
|
||||
response: parseXhrResponse(settings.responseType || xhr.responseType, xhr),
|
||||
status: xhr.status,
|
||||
responseType: xhr.responseType,
|
||||
xhr: xhr,
|
||||
originalEvent: e
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeAjaxErrorEvent(e, xhr, type) {
|
||||
return {
|
||||
type: type,
|
||||
status: xhr.status,
|
||||
xhr: xhr,
|
||||
originalEvent: e
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates an observable for an Ajax request with either a settings object
|
||||
* with url, headers, etc or a string for a URL.
|
||||
*
|
||||
* @example
|
||||
* source = Rx.DOM.ajax('/products');
|
||||
* source = Rx.DOM.ajax( url: 'products', method: 'GET' });
|
||||
*
|
||||
* interface Options {
|
||||
* url: String, // URL of the request
|
||||
* body?: Object, // The body of the request
|
||||
* method? = 'GET' : 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
|
||||
* async? = true: Boolean, // Whether the request is async
|
||||
* headers?: Object, // optional headers
|
||||
* crossDomain?: true // if a cross domain request, else false
|
||||
* }
|
||||
*
|
||||
* ajax$(url?: String, options: Options) => Observable[XMLHttpRequest]
|
||||
*/
|
||||
export function ajax$(options) {
|
||||
var settings = {
|
||||
method: 'GET',
|
||||
crossDomain: false,
|
||||
async: true,
|
||||
headers: {},
|
||||
responseType: 'text',
|
||||
createXHR: function() {
|
||||
return this.crossDomain ? getCORSRequest() : getXMLHttpRequest();
|
||||
},
|
||||
normalizeError: normalizeAjaxErrorEvent,
|
||||
normalizeSuccess: normalizeAjaxSuccessEvent
|
||||
};
|
||||
|
||||
if (typeof options === 'string') {
|
||||
settings.url = options;
|
||||
} else {
|
||||
for (var prop in options) {
|
||||
if (hasOwnProperty.call(options, prop)) {
|
||||
settings[prop] = options[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var normalizeError = settings.normalizeError;
|
||||
var normalizeSuccess = settings.normalizeSuccess;
|
||||
|
||||
if (!settings.crossDomain && !settings.headers['X-Requested-With']) {
|
||||
settings.headers['X-Requested-With'] = 'XMLHttpRequest';
|
||||
}
|
||||
settings.hasContent = typeof settings.body !== 'undefined';
|
||||
|
||||
return new AnonymousObservable(function(observer) {
|
||||
var isDone = false;
|
||||
var xhr;
|
||||
|
||||
var processResponse = function(xhr, e) {
|
||||
var status = xhr.status === 1223 ? 204 : xhr.status;
|
||||
if ((status >= 200 && status <= 300) || status === 0 || status === '') {
|
||||
try {
|
||||
observer.onNext(normalizeSuccess(e, xhr, settings));
|
||||
observer.onCompleted();
|
||||
} catch (err) {
|
||||
observer.onError(err);
|
||||
}
|
||||
} else {
|
||||
observer.onError(normalizeError(e, xhr, 'error'));
|
||||
}
|
||||
isDone = true;
|
||||
};
|
||||
|
||||
try {
|
||||
xhr = settings.createXHR();
|
||||
} catch (err) {
|
||||
observer.onError(err);
|
||||
}
|
||||
|
||||
try {
|
||||
if (settings.user) {
|
||||
xhr.open(
|
||||
settings.method,
|
||||
settings.url,
|
||||
settings.async,
|
||||
settings.user,
|
||||
settings.password
|
||||
);
|
||||
} else {
|
||||
xhr.open(settings.method, settings.url, settings.async);
|
||||
}
|
||||
|
||||
var headers = settings.headers;
|
||||
for (var header in headers) {
|
||||
if (hasOwnProperty.call(headers, header)) {
|
||||
xhr.setRequestHeader(header, headers[header]);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!xhr.upload ||
|
||||
(!('withCredentials' in xhr) && root.XDomainRequest)
|
||||
) {
|
||||
xhr.onload = function(e) {
|
||||
if (settings.progressObserver) {
|
||||
settings.progressObserver.onNext(e);
|
||||
settings.progressObserver.onCompleted();
|
||||
}
|
||||
processResponse(xhr, e);
|
||||
};
|
||||
|
||||
if (settings.progressObserver) {
|
||||
xhr.onprogress = function(e) {
|
||||
settings.progressObserver.onNext(e);
|
||||
};
|
||||
}
|
||||
|
||||
xhr.onerror = function(e) {
|
||||
if (settings.progressObserver) {
|
||||
settings.progressObserver.onError(e);
|
||||
}
|
||||
observer.onError(normalizeError(e, xhr, 'error'));
|
||||
isDone = true;
|
||||
};
|
||||
|
||||
xhr.onabort = function(e) {
|
||||
if (settings.progressObserver) {
|
||||
settings.progressObserver.onError(e);
|
||||
}
|
||||
observer.onError(normalizeError(e, xhr, 'abort'));
|
||||
isDone = true;
|
||||
};
|
||||
} else {
|
||||
|
||||
xhr.onreadystatechange = function(e) {
|
||||
if (xhr.readyState === 4) {
|
||||
processResponse(xhr, e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
debug(
|
||||
'ajax$ sending content',
|
||||
settings.hasContent && settings.body
|
||||
);
|
||||
xhr.send(settings.hasContent && settings.body || null);
|
||||
} catch (err) {
|
||||
observer.onError(err);
|
||||
}
|
||||
|
||||
return function() {
|
||||
if (!isDone && xhr.readyState !== 4) { xhr.abort(); }
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Creates an observable sequence from an Ajax POST Request with the body.
|
||||
// post$(url: String, body: Object) => Observable[Any]
|
||||
export function post$(url, body) {
|
||||
try {
|
||||
body = JSON.stringify(body);
|
||||
} catch (e) {
|
||||
return Observable.throw(e);
|
||||
}
|
||||
|
||||
return ajax$({ url, body, method: 'POST' });
|
||||
}
|
||||
|
||||
// postJSON$(url: String, body: Object) => Observable[Object]
|
||||
export function postJSON$(url, body) {
|
||||
try {
|
||||
body = JSON.stringify(body);
|
||||
} catch (e) {
|
||||
return Observable.throw(e);
|
||||
}
|
||||
|
||||
return ajax$({
|
||||
url,
|
||||
body,
|
||||
method: 'POST',
|
||||
responseType: 'json',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json'
|
||||
},
|
||||
normalizeError: (e, xhr) => parseXhrResponse('json', xhr)
|
||||
})
|
||||
.map(({ response }) => response);
|
||||
}
|
||||
|
||||
// Creates an observable sequence from an Ajax GET Request with the body.
|
||||
// get$(url: String) => Obserable[Any]
|
||||
export function get$(url) {
|
||||
return ajax$({ url: url });
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an observable sequence from JSON from an Ajax request
|
||||
*
|
||||
* @param {String} url The URL to GET
|
||||
* @returns {Observable} The observable sequence which contains the parsed JSON
|
||||
*/
|
||||
// getJSON$(url: String) => Observable[Object];
|
||||
export function getJSON$(url) {
|
||||
return ajax$({
|
||||
url: url,
|
||||
responseType: 'json',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json'
|
||||
},
|
||||
normalizeError: (e, xhr) => parseXhrResponse('json', xhr)
|
||||
}).map(({ response }) => response);
|
||||
}
|
9
api-server/common/utils/constantStrings.json
Normal file
9
api-server/common/utils/constantStrings.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"aboutUrl": "https://www.freecodecamp.org/about",
|
||||
"defaultProfileImage": "https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png",
|
||||
"donateUrl": "https://www.freecodecamp.org/donate",
|
||||
"forumUrl": "https://forum.freecodecamp.org",
|
||||
"githubUrl": "https://github.com/freecodecamp/freecodecamp",
|
||||
"RSA": "https://forum.freecodecamp.org/t/the-read-search-ask-methodology-for-getting-unstuck/137307",
|
||||
"homeURL": "https://www.freecodecamp.org"
|
||||
}
|
38
api-server/common/utils/encode-decode.js
Normal file
38
api-server/common/utils/encode-decode.js
Normal file
@ -0,0 +1,38 @@
|
||||
import _ from 'lodash/fp';
|
||||
|
||||
// we don't store loop protect disable key
|
||||
export const removeNoprotect = _.replace(/noprotect/gi, '');
|
||||
|
||||
export const encodeScriptTags = _.flow(
|
||||
_.replace(/<script>/gi, 'fccss'),
|
||||
_.replace(/<\/script>/gi, 'fcces')
|
||||
);
|
||||
|
||||
export const decodeScriptTags = _.flow(
|
||||
_.replace(/fccss/gi, '<script>'),
|
||||
_.replace(/fcces/gi, '</script>')
|
||||
);
|
||||
|
||||
export const encodeFormAction = _.replace(
|
||||
// look for attributes in a form
|
||||
/<form[^>]*>/,
|
||||
// val is the string within the opening form tag
|
||||
// look for an `action` attribute, replace it with a fcc tag
|
||||
_.replace(/action(\s*?)=/, 'fccfaa$1=')
|
||||
);
|
||||
|
||||
export const decodeFormAction = _.replace(
|
||||
/<form[^>]*>/,
|
||||
_.replace(/fccfaa(\s*?)=/, 'action$1=')
|
||||
);
|
||||
|
||||
export const encodeFcc = _.flow(
|
||||
removeNoprotect,
|
||||
encodeFormAction,
|
||||
encodeScriptTags
|
||||
);
|
||||
|
||||
export const decodeFcc = _.flow(
|
||||
decodeFormAction,
|
||||
decodeScriptTags
|
||||
);
|
70
api-server/common/utils/encode-decode.test.js
Normal file
70
api-server/common/utils/encode-decode.test.js
Normal file
@ -0,0 +1,70 @@
|
||||
import test from 'tape';
|
||||
import {
|
||||
encodeScriptTags,
|
||||
decodeScriptTags,
|
||||
encodeFormAction,
|
||||
decodeFormAction,
|
||||
encodeFcc,
|
||||
decodeFcc
|
||||
} from './encode-decode.js';
|
||||
|
||||
const scriptDecoded = `
|
||||
<script>console.log('foo')</script>
|
||||
`;
|
||||
const scriptEncoded = `
|
||||
fccssconsole.log('foo')fcces
|
||||
`;
|
||||
test('encodeScriptTags', t => {
|
||||
t.plan(1);
|
||||
t.equal(
|
||||
encodeScriptTags(scriptDecoded),
|
||||
scriptEncoded
|
||||
);
|
||||
});
|
||||
|
||||
test('decodeScriptTags', t => {
|
||||
t.plan(1);
|
||||
t.equal(
|
||||
decodeScriptTags(scriptEncoded),
|
||||
scriptDecoded
|
||||
);
|
||||
});
|
||||
|
||||
const formDecoded = `
|
||||
<form action ='path'>foo</form>
|
||||
`;
|
||||
const formEncoded = `
|
||||
<form fccfaa ='path'>foo</form>
|
||||
`;
|
||||
|
||||
test('encodeFormAction', t => {
|
||||
t.plan(1);
|
||||
t.equal(
|
||||
encodeFormAction(formDecoded),
|
||||
formEncoded
|
||||
);
|
||||
});
|
||||
|
||||
test('decodeFormAction', t => {
|
||||
t.plan(1);
|
||||
t.equal(
|
||||
decodeFormAction(formEncoded),
|
||||
formDecoded
|
||||
);
|
||||
});
|
||||
|
||||
test('encodeFcc', t => {
|
||||
t.plan(1);
|
||||
t.equal(
|
||||
encodeFcc('//noprotect' + scriptDecoded + formDecoded),
|
||||
'//' + scriptEncoded + formEncoded
|
||||
);
|
||||
});
|
||||
|
||||
test('decodeFcc', t => {
|
||||
t.plan(1);
|
||||
t.equal(
|
||||
decodeFcc(scriptEncoded + formEncoded),
|
||||
scriptDecoded + formDecoded
|
||||
);
|
||||
});
|
10
api-server/common/utils/flash.js
Normal file
10
api-server/common/utils/flash.js
Normal file
@ -0,0 +1,10 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
export const alertTypes = _.keyBy([
|
||||
'success',
|
||||
'info',
|
||||
'warning',
|
||||
'danger'
|
||||
], _.identity);
|
||||
|
||||
export const normalizeAlertType = alertType => alertTypes[alertType] || 'info';
|
17
api-server/common/utils/index.js
Normal file
17
api-server/common/utils/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
import { pick } from 'lodash';
|
||||
|
||||
export function dashify(str) {
|
||||
return ('' + str)
|
||||
.toLowerCase()
|
||||
.replace(/\s/g, '-')
|
||||
.replace(/[^a-z0-9\-\.]/gi, '')
|
||||
.replace(/\:/g, '');
|
||||
}
|
||||
// todo: unify with server/utils/index.js:dasherize
|
||||
const dasherize = dashify;
|
||||
export { dasherize };
|
||||
|
||||
export const fixCompletedChallengeItem = obj => pick(
|
||||
obj,
|
||||
[ 'id', 'completedDate', 'solution', 'githubLink', 'challengeType', 'files' ]
|
||||
);
|
74
api-server/common/utils/jsonp$.js
Normal file
74
api-server/common/utils/jsonp$.js
Normal file
@ -0,0 +1,74 @@
|
||||
import { AnonymousObservable, Disposable } from 'rx';
|
||||
|
||||
const root = typeof window !== 'undefined' ? window : {};
|
||||
const trash = 'document' in root && root.document.createElement('div');
|
||||
|
||||
function destroy(element) {
|
||||
trash.appendChild(element);
|
||||
trash.innerHTML = '';
|
||||
}
|
||||
|
||||
export function jsonp$(options) {
|
||||
let id = 0;
|
||||
if (typeof options === 'string') {
|
||||
options = { url: options };
|
||||
}
|
||||
|
||||
return new AnonymousObservable(function(o) {
|
||||
const settings = {
|
||||
jsonp: 'JSONPCallback',
|
||||
async: true,
|
||||
jsonpCallback: 'rxjsjsonpCallbackscallback_' + (id++).toString(36),
|
||||
...options
|
||||
};
|
||||
|
||||
let script = root.document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.async = settings.async;
|
||||
script.src = settings.url.replace(settings.jsonp, settings.jsonpCallback);
|
||||
|
||||
root[settings.jsonpCallback] = function(data) {
|
||||
root[settings.jsonpCallback].called = true;
|
||||
root[settings.jsonpCallback].data = data;
|
||||
};
|
||||
|
||||
const handler = function(e) {
|
||||
if (e.type === 'load' && !root[settings.jsonpCallback].called) {
|
||||
e = { type: 'error' };
|
||||
}
|
||||
const status = e.type === 'error' ? 400 : 200;
|
||||
const data = root[settings.jsonpCallback].data;
|
||||
|
||||
if (status === 200) {
|
||||
o.onNext({
|
||||
status: status,
|
||||
responseType: 'jsonp',
|
||||
response: data,
|
||||
originalEvent: e
|
||||
});
|
||||
|
||||
o.onCompleted();
|
||||
} else {
|
||||
o.onError({
|
||||
type: 'error',
|
||||
status: status,
|
||||
originalEvent: e
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
script.onload = script.onreadystatechanged = script.onerror = handler;
|
||||
|
||||
const head = root.document.getElementsByTagName('head')[0] ||
|
||||
root.document.documentElement;
|
||||
|
||||
head.insertBefore(script, head.firstChild);
|
||||
|
||||
return Disposable.create(() => {
|
||||
script.onload = script.onreadystatechanged = script.onerror = null;
|
||||
|
||||
destroy(script);
|
||||
script = null;
|
||||
});
|
||||
});
|
||||
}
|
88
api-server/common/utils/legacyProjectData.js
Normal file
88
api-server/common/utils/legacyProjectData.js
Normal file
@ -0,0 +1,88 @@
|
||||
const legacyFrontEndProjects = {
|
||||
challenges: [
|
||||
// build-a-personal-portfolio-webpage
|
||||
'bd7158d8c242eddfaeb5bd13',
|
||||
// build-a-random-quote-machine
|
||||
'bd7158d8c442eddfaeb5bd13',
|
||||
// build-a-pomodoro-clock
|
||||
'bd7158d8c442eddfaeb5bd0f',
|
||||
// build-a-javascript-calculator
|
||||
'bd7158d8c442eddfaeb5bd17',
|
||||
// show-the-local-weather
|
||||
'bd7158d8c442eddfaeb5bd10',
|
||||
// use-the-twitchtv-json-api
|
||||
'bd7158d8c442eddfaeb5bd1f',
|
||||
// stylize-stories-on-camper-news
|
||||
'bd7158d8c442eddfaeb5bd18',
|
||||
// build-a-wikipedia-viewer
|
||||
'bd7158d8c442eddfaeb5bd19',
|
||||
// build-a-tic-tac-toe-game
|
||||
'bd7158d8c442eedfaeb5bd1c',
|
||||
// build-a-simon-game
|
||||
'bd7158d8c442eddfaeb5bd1c'
|
||||
],
|
||||
title: 'Legacy Front End Projects',
|
||||
superBlock: 'legacy-front-end'
|
||||
};
|
||||
|
||||
const legacyBackEndProjects = {
|
||||
challenges: [
|
||||
// timestamp microservice
|
||||
'bd7158d8c443edefaeb5bdef',
|
||||
// request-header-parser-microservice
|
||||
'bd7158d8c443edefaeb5bdff',
|
||||
// url-shortener-microservice
|
||||
'bd7158d8c443edefaeb5bd0e',
|
||||
// image-search-abstraction-layer
|
||||
'bd7158d8c443edefaeb5bdee',
|
||||
// file-metadata-microservice
|
||||
'bd7158d8c443edefaeb5bd0f',
|
||||
// build-a-voting-app
|
||||
'bd7158d8c443eddfaeb5bdef',
|
||||
// build-a-nightlife-coordination-app
|
||||
'bd7158d8c443eddfaeb5bdff',
|
||||
// chart-the-stock-market
|
||||
'bd7158d8c443eddfaeb5bd0e',
|
||||
// manage-a-book-trading-club
|
||||
'bd7158d8c443eddfaeb5bd0f',
|
||||
// build-a-pinterest-clone
|
||||
'bd7158d8c443eddfaeb5bdee'
|
||||
],
|
||||
title: 'Legacy Back End Projects',
|
||||
superBlock: 'legacy-back-end'
|
||||
};
|
||||
|
||||
const legacyDataVisProjects = {
|
||||
challenges: [
|
||||
// build-a-markdown-previewer
|
||||
'bd7157d8c242eddfaeb5bd13',
|
||||
// build-a-camper-leaderboard
|
||||
'bd7156d8c242eddfaeb5bd13',
|
||||
// build-a-recipe-box
|
||||
'bd7155d8c242eddfaeb5bd13',
|
||||
// build-the-game-of-life
|
||||
'bd7154d8c242eddfaeb5bd13',
|
||||
// build-a-roguelike-dungeon-crawler-game
|
||||
'bd7153d8c242eddfaeb5bd13',
|
||||
// visualize-data-with-a-bar-chart
|
||||
'bd7168d8c242eddfaeb5bd13',
|
||||
// visualize-data-with-a-scatterplot-graph
|
||||
'bd7178d8c242eddfaeb5bd13',
|
||||
// visualize-data-with-a-heat-map
|
||||
'bd7188d8c242eddfaeb5bd13',
|
||||
// show-national-contiguity-with-a-force-directed-graph
|
||||
'bd7198d8c242eddfaeb5bd13',
|
||||
// map-data-across-the-globe
|
||||
'bd7108d8c242eddfaeb5bd13'
|
||||
],
|
||||
title: 'Legacy Data Visualization Projects',
|
||||
superBlock: 'legacy-data-visualization'
|
||||
};
|
||||
|
||||
const legacyProjects = [
|
||||
legacyFrontEndProjects,
|
||||
legacyBackEndProjects,
|
||||
legacyDataVisProjects
|
||||
];
|
||||
|
||||
export default legacyProjects;
|
74
api-server/common/utils/map.js
Normal file
74
api-server/common/utils/map.js
Normal file
@ -0,0 +1,74 @@
|
||||
import emptyProtector from '../app/utils/empty-protector';
|
||||
|
||||
export function checkMapData(
|
||||
{
|
||||
entities: {
|
||||
challenge,
|
||||
block,
|
||||
superBlock
|
||||
},
|
||||
result: { superBlocks }
|
||||
}
|
||||
) {
|
||||
if (
|
||||
!challenge ||
|
||||
!block ||
|
||||
!superBlock ||
|
||||
!superBlocks ||
|
||||
!superBlocks.length
|
||||
) {
|
||||
throw new Error(
|
||||
'entities not found, db may not be properly seeded'
|
||||
);
|
||||
}
|
||||
}
|
||||
// getFirstChallenge(
|
||||
// map: {
|
||||
// entities: { challenge: Object, block: Object, superBlock: Object },
|
||||
// result: [...superBlockDashedName: String]
|
||||
// }
|
||||
// ) => Challenge|Void
|
||||
export function getFirstChallenge({
|
||||
entities: { superBlock, block, challenge },
|
||||
result: { superBlocks }
|
||||
}) {
|
||||
return challenge[
|
||||
emptyProtector(block[
|
||||
emptyProtector(superBlock[
|
||||
superBlocks[0]
|
||||
]).blocks[0]
|
||||
]).challenges[0]
|
||||
];
|
||||
}
|
||||
|
||||
// let challengeDashedName: String;
|
||||
// createNameIdMap({
|
||||
// challenge: {
|
||||
// [...challengeDashedName ]: Challenge
|
||||
// }) => {
|
||||
// challengeIdToName: {
|
||||
// [ ...challengeId ]: challengeDashedName
|
||||
// }
|
||||
// };
|
||||
export function createNameIdMap({ challenge }) {
|
||||
return {
|
||||
challengeIdToName: Object.keys(challenge)
|
||||
.reduce((map, challengeName) => {
|
||||
map[challenge[challengeName].id] =
|
||||
challenge[challengeName].dashedName;
|
||||
return map;
|
||||
}, {})
|
||||
};
|
||||
}
|
||||
// addNameIdMap(
|
||||
// map: { entities; Object, ...rest }
|
||||
// ) => { ...rest, entities: Object };
|
||||
export function addNameIdMap({ entities, ...rest }) {
|
||||
return {
|
||||
...rest,
|
||||
entities: {
|
||||
...entities,
|
||||
...createNameIdMap(entities)
|
||||
}
|
||||
};
|
||||
}
|
225
api-server/common/utils/polyvinyl.js
Normal file
225
api-server/common/utils/polyvinyl.js
Normal file
@ -0,0 +1,225 @@
|
||||
// originally based off of https://github.com/gulpjs/vinyl
|
||||
import invariant from 'invariant';
|
||||
import { Observable } from 'rx';
|
||||
import castToObservable from '../app/utils/cast-to-observable.js';
|
||||
|
||||
|
||||
// createFileStream(
|
||||
// files: [...PolyVinyl]
|
||||
// ) => Observable[...Observable[...PolyVinyl]]
|
||||
export function createFileStream(files = []) {
|
||||
return Observable.of(
|
||||
Observable.from(files)
|
||||
);
|
||||
}
|
||||
|
||||
// Observable::pipe(
|
||||
// project(
|
||||
// file: PolyVinyl
|
||||
// ) => PolyVinyl|Observable[PolyVinyl]|Promise[PolyVinyl]
|
||||
// ) => Observable[...Observable[...PolyVinyl]]
|
||||
export function pipe(project) {
|
||||
const source = this;
|
||||
return source.map(
|
||||
files => files.flatMap(file => castToObservable(project(file)))
|
||||
);
|
||||
}
|
||||
|
||||
// interface PolyVinyl {
|
||||
// source: String,
|
||||
// contents: String,
|
||||
// name: String,
|
||||
// ext: String,
|
||||
// path: String,
|
||||
// key: String,
|
||||
// head: String,
|
||||
// tail: String,
|
||||
// history: [...String],
|
||||
// error: Null|Object|Error
|
||||
// }
|
||||
|
||||
// createPoly({
|
||||
// name: String,
|
||||
// ext: String,
|
||||
// contents: String,
|
||||
// history?: [...String],
|
||||
// }) => PolyVinyl, throws
|
||||
export function createPoly({
|
||||
name,
|
||||
ext,
|
||||
contents,
|
||||
history,
|
||||
...rest
|
||||
} = {}) {
|
||||
invariant(
|
||||
typeof name === 'string',
|
||||
'name must be a string but got %s',
|
||||
name
|
||||
);
|
||||
|
||||
invariant(
|
||||
typeof ext === 'string',
|
||||
'ext must be a string, but was %s',
|
||||
ext
|
||||
);
|
||||
|
||||
invariant(
|
||||
typeof contents === 'string',
|
||||
'contents must be a string but got %s',
|
||||
contents
|
||||
);
|
||||
|
||||
return {
|
||||
...rest,
|
||||
history: Array.isArray(history) ? history : [ name + ext ],
|
||||
name,
|
||||
ext,
|
||||
path: name + '.' + ext,
|
||||
key: name + ext,
|
||||
contents,
|
||||
error: null
|
||||
};
|
||||
}
|
||||
|
||||
// isPoly(poly: Any) => Boolean
|
||||
export function isPoly(poly) {
|
||||
return poly &&
|
||||
typeof poly.contents === 'string' &&
|
||||
typeof poly.name === 'string' &&
|
||||
typeof poly.ext === 'string' &&
|
||||
Array.isArray(poly.history);
|
||||
}
|
||||
|
||||
// checkPoly(poly: Any) => Void, throws
|
||||
export function checkPoly(poly) {
|
||||
invariant(
|
||||
isPoly(poly),
|
||||
'function should receive a PolyVinyl, but got %s',
|
||||
poly
|
||||
);
|
||||
}
|
||||
|
||||
// isEmpty(poly: PolyVinyl) => Boolean, throws
|
||||
export function isEmpty(poly) {
|
||||
checkPoly(poly);
|
||||
return !!poly.contents;
|
||||
}
|
||||
|
||||
// setContent(contents: String, poly: PolyVinyl) => PolyVinyl
|
||||
// setContent will loose source if set
|
||||
export function setContent(contents, poly) {
|
||||
checkPoly(poly);
|
||||
return {
|
||||
...poly,
|
||||
contents,
|
||||
source: null
|
||||
};
|
||||
}
|
||||
|
||||
// setExt(ext: String, poly: PolyVinyl) => PolyVinyl
|
||||
export function setExt(ext, poly) {
|
||||
checkPoly(poly);
|
||||
const newPoly = {
|
||||
...poly,
|
||||
ext,
|
||||
path: poly.name + '.' + ext,
|
||||
key: poly.name + ext
|
||||
};
|
||||
newPoly.history = [ ...poly.history, newPoly.path ];
|
||||
return newPoly;
|
||||
}
|
||||
|
||||
// setName(name: String, poly: PolyVinyl) => PolyVinyl
|
||||
export function setName(name, poly) {
|
||||
checkPoly(poly);
|
||||
const newPoly = {
|
||||
...poly,
|
||||
name,
|
||||
path: name + '.' + poly.ext,
|
||||
key: name + poly.ext
|
||||
};
|
||||
newPoly.history = [ ...poly.history, newPoly.path ];
|
||||
return newPoly;
|
||||
}
|
||||
|
||||
// setError(error: Object, poly: PolyVinyl) => PolyVinyl
|
||||
export function setError(error, poly) {
|
||||
invariant(
|
||||
typeof error === 'object',
|
||||
'error must be an object or null, but got %',
|
||||
error
|
||||
);
|
||||
checkPoly(poly);
|
||||
return {
|
||||
...poly,
|
||||
error
|
||||
};
|
||||
}
|
||||
|
||||
// clearHeadTail(poly: PolyVinyl) => PolyVinyl
|
||||
export function clearHeadTail(poly) {
|
||||
checkPoly(poly);
|
||||
return {
|
||||
...poly,
|
||||
head: '',
|
||||
tail: ''
|
||||
};
|
||||
}
|
||||
|
||||
// appendToTail (tail: String, poly: PolyVinyl) => PolyVinyl
|
||||
export function appendToTail(tail, poly) {
|
||||
checkPoly(poly);
|
||||
return {
|
||||
...poly,
|
||||
tail: poly.tail.concat(tail)
|
||||
};
|
||||
}
|
||||
|
||||
// compileHeadTail(padding: String, poly: PolyVinyl) => PolyVinyl
|
||||
export function compileHeadTail(padding = '', poly) {
|
||||
return clearHeadTail(transformContents(
|
||||
() => [ poly.head, poly.contents, poly.tail ].join(padding),
|
||||
poly
|
||||
));
|
||||
}
|
||||
|
||||
// transformContents(
|
||||
// wrap: (contents: String) => String,
|
||||
// poly: PolyVinyl
|
||||
// ) => PolyVinyl
|
||||
// transformContents will keep a copy of the original
|
||||
// code in the `source` property. If the original polyvinyl
|
||||
// already contains a source, this version will continue as
|
||||
// the source property
|
||||
export function transformContents(wrap, poly) {
|
||||
const newPoly = setContent(
|
||||
wrap(poly.contents),
|
||||
poly
|
||||
);
|
||||
// if no source exist, set the original contents as source
|
||||
newPoly.source = poly.source || poly.contents;
|
||||
return newPoly;
|
||||
}
|
||||
|
||||
// transformHeadTailAndContents(
|
||||
// wrap: (source: String) => String,
|
||||
// poly: PolyVinyl
|
||||
// ) => PolyVinyl
|
||||
export function transformHeadTailAndContents(wrap, poly) {
|
||||
return {
|
||||
...transformContents(
|
||||
wrap,
|
||||
poly
|
||||
),
|
||||
head: wrap(poly.head),
|
||||
tail: wrap(poly.tail)
|
||||
};
|
||||
}
|
||||
|
||||
export function testContents(predicate, poly) {
|
||||
return !!predicate(poly.contents);
|
||||
}
|
||||
|
||||
export function updateFileFromSpec(spec, poly) {
|
||||
return setContent(poly.contents, createPoly(spec));
|
||||
}
|
44
api-server/common/utils/services-creator.js
Normal file
44
api-server/common/utils/services-creator.js
Normal file
@ -0,0 +1,44 @@
|
||||
import { Observable, Disposable } from 'rx';
|
||||
import Fetchr from 'fetchr';
|
||||
|
||||
function callbackObserver(observer) {
|
||||
return (err, res) => {
|
||||
if (err) {
|
||||
return observer.onError(err);
|
||||
}
|
||||
|
||||
observer.onNext(res);
|
||||
return observer.onCompleted();
|
||||
};
|
||||
}
|
||||
|
||||
export default function servicesCreator(options) {
|
||||
const services = new Fetchr(options);
|
||||
function readService$({ service: resource, params, config }) {
|
||||
return Observable.create(observer => {
|
||||
services.read(
|
||||
resource,
|
||||
params,
|
||||
config,
|
||||
callbackObserver(observer)
|
||||
);
|
||||
return Disposable.create(() => observer.dispose());
|
||||
});
|
||||
}
|
||||
function createService$({ service: resource, params, body, config }) {
|
||||
return Observable.create(observer => {
|
||||
services.create(
|
||||
resource,
|
||||
params,
|
||||
body,
|
||||
config,
|
||||
callbackObserver(observer)
|
||||
);
|
||||
return Disposable.create(() => observer.dispose());
|
||||
});
|
||||
}
|
||||
return {
|
||||
readService$,
|
||||
createService$
|
||||
};
|
||||
}
|
10
api-server/common/utils/themes.js
Normal file
10
api-server/common/utils/themes.js
Normal file
@ -0,0 +1,10 @@
|
||||
export const themes = {
|
||||
night: 'night',
|
||||
default: 'default'
|
||||
};
|
||||
|
||||
export const invertTheme = currentTheme => (
|
||||
!currentTheme || currentTheme === themes.default ?
|
||||
themes.night :
|
||||
themes.default
|
||||
);
|
15
api-server/common/utils/wait-for-epics.js
Normal file
15
api-server/common/utils/wait-for-epics.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { Observable } from 'rx';
|
||||
import debug from 'debug';
|
||||
|
||||
const log = debug('redux-epic:waitForEpics');
|
||||
|
||||
// waitForEpics(epicMiddleware: EpicMiddleware) => Observable[Void]
|
||||
export default function waitForEpics(epicMiddleware) {
|
||||
return Observable.defer(() => {
|
||||
log('calling actions onCompleted');
|
||||
epicMiddleware.end();
|
||||
return Observable.merge(epicMiddleware);
|
||||
})
|
||||
.last({ defaultValue: null })
|
||||
.map(() => epicMiddleware.restart());
|
||||
}
|
Reference in New Issue
Block a user