UNPKG

10.1 kBJavaScriptView Raw
1'use strict';
2
3const util = require('util');
4const moment = require('moment-timezone');
5const content = require('./content/utils');
6const routes = require('./routes/utils');
7const _ = require('lodash');
8const Promise = require('bluebird');
9
10moment.tz.setDefault('UTC');
11
12/*
13 * Filters an array of objects based on the value of a key in the objects
14 *
15 * @param {string} key - The key in the object to check
16 * @param {string} value - The value to check against
17 * @param {array} arr - An array of {Object}s
18 *
19 * @returns {object|boolean} - Will return the first filtered object, or `false` if no objects match
20 */
21const singleItem = (key, value, arr) => {
22 const filtered = arr.filter(type => {
23 if (type[key] === value) {
24 return true;
25 }
26
27 return false;
28 });
29
30 if (filtered.length === 0) {
31 return false;
32 }
33
34 return filtered[0];
35};
36
37/*
38 * Logs out full object
39 *
40 * @param {object|string} object - The object to be logged
41 */
42/* istanbul ignore next */
43const log = (object) => {
44 // Mean to console.log out, so disabling
45 console.log(util.inspect(object, false, null)); // eslint-disable-line no-console
46};
47
48/*
49 * Formats string date components in to ISO date
50 *
51 * @param {string} date - The date to be transformed
52 * @param {string} time - The time to be transformed
53 * @param {string} zone - The timezone the date and time are in (e.g. America/New_York)
54 *
55 * @returns {string} - An ISO formatted date in GMT
56 */
57const isoTime = (date, time, zone) => {
58 if (date === '' || date === undefined || time === '' || time === undefined || zone === '' || zone === undefined) {
59 return null;
60 }
61
62 const converted = moment.tz(`${date} ${time}`, zone);
63
64 return converted.toISOString();
65};
66
67/*
68 * @typedef FormattedDate
69 * @type object
70 *
71 * @property {string} date - The date, formatted YYYY-MM-DD (e.g. 2016-05-25)
72 * @property {string} time - The time, formatted HH:mm (e.g. 13:01)
73 * @property {string} zone - The time zone, formatted Z (e.g. America/New_York)
74 */
75
76/*
77 * Formats ISO date in to requisite components
78 *
79 * @param {string} date - ISO UTC formatted date
80 * @param {string} zone - Timezone to retrieve date in
81 *
82 * @returns {FormattedDate} - The individually formatted date components
83 */
84const inputTime = (date, zone) => {
85 let tz = zone;
86
87 if (date === '' || date === undefined || date === null) {
88 return null;
89 }
90
91 if (tz === undefined || tz === null) {
92 tz = 'UTC';
93 }
94
95 // America/New_York is hard coded throughout the system right now, need to format correctly
96 return {
97 date: moment(date).tz('America/New_York').format('YYYY-MM-DD'),
98 time: moment(date).tz('America/New_York').format('HH:mm'),
99 zone: 'America/New_York',
100 };
101};
102
103const time = {
104 iso: isoTime,
105 input: inputTime,
106};
107
108
109/*
110 * Generate Config Object for Only
111 */
112const config = values => {
113 const result = {};
114
115 Object.keys(values).map(value => {
116 const split = value.split('--');
117 const plugin = split[0];
118 const input = split[1];
119
120 // Repeat
121 // const index = split[2];
122
123 if (!result.hasOwnProperty(plugin)) {
124 result[plugin] = {};
125 }
126
127 result[plugin][input] = {
128 value: values[value],
129 };
130 });
131
132 return result;
133};
134
135/*
136 * @typedef RequestBody
137 * @type object
138 *
139 * @property {string | array} input - inputName--inputType : value
140 */
141
142/*
143 * @typedef FormattedBody
144 * @type object
145 *
146 * @property {string} FormattedBody.name - Name of the input
147 * @property {object | array} FormattedBody.name.type - Type of the input
148 * @property {string} FormattedBody.name.type.value - Value of the input
149 */
150
151/*
152 * Formats raw data from form to object
153 *
154 * @param {RequestBody} body - inputName--inputType: value
155 *
156 *
157 * @returns {object} - formatted object of data
158 */
159const format = body => {
160 const data = {};
161 const single = Object.keys(body).filter(input => {
162 if ((input === 'sunset-date') || (input === 'sunset-time') || (input === 'sunrise-date') || (input === 'sunrise-time')) {
163 data[input] = { 'value': body[input] };
164
165 return false;
166 }
167 if (input.split('--').length < 3) {
168 return true;
169 }
170
171 return false;
172 });
173 const multiple = Object.keys(body).filter(input => {
174 if (input.split('--').length >= 3) {
175 return true;
176 }
177
178 return false;
179 });
180
181 single.forEach(input => {
182 const inputs = input.split('--');
183 const inputName = inputs[0];
184 const inputType = inputs[1];
185 if (!data.hasOwnProperty(inputName)) {
186 // Creates an object for single instance
187 data[inputName] = {};
188 }
189 data[inputName][inputType] = { 'value': body[input] };
190 });
191
192 multiple.forEach(input => {
193 const inputs = input.split('--');
194 const inputName = inputs[0];
195 const inputType = inputs[1];
196 const index = inputs[2];
197 if (!data.hasOwnProperty(inputName)) {
198 // Creates an array for multiple instances
199 data[inputName] = [];
200 }
201 if (!data[inputName][index]) {
202 // Create an object for instance
203 data[inputName][index] = {};
204 }
205
206 // Sets value of input
207 data[inputName][index][inputType] = { 'value': body[input] };
208 });
209
210 // Deletes the empty instances
211 Object.keys(data).forEach(key => {
212 if (Array.isArray(data[key])) {
213 data[key] = data[key].filter(input => {
214 return Object.keys(input).filter(type => {
215 if (input[type].value !== null && input[type].value !== '' && input[type].value !== []) {
216 return true;
217 }
218
219 return false;
220 }).length > 0;
221 });
222
223 // Delete empty arrays
224 if (data[key].length === 0) {
225 delete data[key];
226 }
227 }
228 });
229
230 return data;
231};
232
233/*
234 * @typedef Reference
235 * @type object
236 *
237 * @property {type} Reference.id - Id of the content type
238 * @property {attr} Reference.attr - Attribute Index
239 * @property {input} Reference.input - Input of the reference
240 * @property {length} Reference.length - Number of instances
241 * @property {ct.id} Reference.ct.id - Id of the referenced content type
242 * @property {ct.index} Reference.ct.index - Position of the reference content type
243 */
244
245/*
246 * Inititalizes reference
247 *
248 * @param {object} types - content types object
249 *
250 *
251 * * @returns {object} - content type and reference data
252 */
253const references = (types) => {
254 const cts = _.cloneDeep(types);
255 const refs = [];
256 const position = {};
257
258 // Saves position of each content type
259 cts.forEach((type, index) => {
260 if (!position.hasOwnProperty(type.id)) {
261 position[type.id] = index;
262 }
263 });
264
265 // Iterates through each content type and attributes
266 cts.forEach(ct => {
267 ct.attributes.forEach((attr, index) => {
268 let inputs;
269 let length;
270
271 // Get the number of instances if repeatable
272 if (Array.isArray(attr.inputs)) {
273 inputs = attr.inputs[0];
274 length = attr.inputs.length;
275 }
276 else {
277 inputs = attr.inputs;
278 }
279
280 // Iterate through each inputs
281 Object.keys(inputs).forEach(input => {
282 // Checks if input is a reference
283 if (inputs[input].hasOwnProperty('reference') && inputs[input].reference === true) {
284 // Throw error if contentType is not defined
285 if (!_.get(inputs[input], 'settings.contentType')) {
286 throw new Error('Reference must have a content type');
287 }
288
289 if (!position.hasOwnProperty(inputs[input].settings.contentType)) {
290 throw new Error(`Content Type ${inputs[input].settings.contentType} is not valid`);
291 }
292
293 if (_.get(inputs[input], 'settings.view') === 'radio') {
294 inputs[input].type = 'radio';
295 }
296
297 // Defines and push reference object
298 refs.push({
299 type: ct.id,
300 attr: index,
301 input,
302 length,
303 ct: inputs[input].settings.contentType,
304 });
305 }
306 });
307 });
308 });
309
310 return {
311 cts,
312 references: refs,
313 };
314};
315
316
317/*
318 * Fills in content type data from database
319 *
320 * @param {object} types - content types object
321 * @param {object} type - content type
322 * @param {array} refs - reference data
323 * @param {object} database - database object
324 *
325 *
326 * @returns {object} - content type object with reference data
327 */
328const fill = (types, type, refs, database) => {
329 const ct = _.cloneDeep(type);
330
331 if (!ct) {
332 return new Promise(resolve => {
333 resolve(ct);
334 });
335 }
336 const ref = refs.filter(reference => {
337 return ct.id === reference.type;
338 });
339
340 return Promise.map(ref, input => {
341 // subquery for select
342 const where = database
343 .max('revision')
344 .from(`content-type--${input.ct}`)
345 .groupBy('id');
346
347 // Get the records from content-type
348 return database
349 .select('id', 'revision', 'value')
350 .from(`content-type--${input.ct}`)
351 .whereIn('revision', where).then(rws => {
352 // stores referred type
353 const refCT = types.find((val) => {
354 return val.id === input.ct;
355 });
356 const rows = routes.identifier(rws, refCT);
357 const options = rows.map(row => {
358 return {
359 value: row.id,
360 label: row.identifier,
361 };
362 });
363
364 // If repeatables set options for each instance
365 if (input.length) {
366 for (let i = 0; i < input.length; i++) {
367 const inp = ct.attributes[input.attr].inputs[i][input.input];
368
369 if (inp.type === 'select') {
370 options.unshift({
371 value: '',
372 label: inp.settings.placeholder,
373 });
374 }
375 inp.options = options;
376 }
377 }
378 else {
379 const inp = ct.attributes[input.attr].inputs[input.input];
380
381 if (inp.type === 'select') {
382 options.unshift({
383 value: '',
384 label: inp.settings.placeholder,
385 });
386 }
387 inp.options = options;
388 }
389 });
390 }).then(() => {
391 return ct;
392 });
393};
394
395module.exports = {
396 content,
397 routes,
398 singleItem,
399 log,
400 time,
401 config,
402 format,
403 fill,
404 references,
405};