<commit_before>/**
 * Created by tushar.mathur on 22/01/16.
 */

'use strict'

import PATH from 'path'
import URL from 'url'
import {Observable as O} from 'rx'
import R from 'ramda'
import * as Rx from './RxFP'
import {mux, demux} from 'muxer'
import {MTDError, FILE_SIZE_UNKNOWN} from './Error'

const first = R.nth(0)
const second = R.nth(1)
export const trace = R.curry((msg, value) => {
  console.log(msg, value)
  return value
})
export const demuxFP = R.curry((list, $) => demux($, ...list))
export const demuxFPH = R.curry((list, $) => R.head(demux($, ...list)))
export const BUFFER_SIZE = 512
export const NormalizePath = (path) => PATH.resolve(process.cwd(), path)
export const GenerateFileName = (x) => {
  return R.last(URL.parse(x).pathname.split('/')) || Date.now()
}
export const ResolvePath = R.compose(NormalizePath, GenerateFileName)
export const SplitRange = (totalBytes, range) => {
  const delta = Math.round(totalBytes / range)
  const start = R.times((x) => x * delta, range)
  const end = R.times((x) => (x + 1) * delta - 1, range)
  end[range - 1] = totalBytes
  return R.zip(start, end)
}
export const CreateRangeHeader = ([start, end]) => `bytes=${start}-${end}`
export const SetRangeHeader = ({request, range}) => {
  return R.set(
    R.lensPath(['headers', 'range']),
    CreateRangeHeader(range),
    R.omit(['threads', 'offsets'], request)
  )
}
export const CreateRequestParams = ({meta, index}) => {
  const range = [meta.offsets[index], second(meta.threads[index])]
  return SetRangeHeader({request: meta, range})
}
export const ToBuffer = R.curry((size, str) => {
  var buffer = CreateFilledBuffer(size)
  buffer.write(str)
  return buffer
})
export const CreateFilledBuffer = (size = BUFFER_SIZE, fill = ' ') => {
  const buffer = new Buffer(size)
  buffer.fill(fill)
  return buffer
}
export const MergeDefaultOptions = (options) => R.mergeAll([
  {mtdPath: options.path + '.mtd', range: 3, metaWrite: 300},
  options
])

// TODO: Use R.lens instead
export const GetOffset = R.curry((meta, index) => meta.offsets[index])
export const GetThread = R.curry((meta, index) => meta.threads[index])
export const GetThreadStart = R.curryN(2, R.compose(R.nth(0), GetThread))
export const GetThreadEnd = R.curryN(2, R.compose(R.nth(1), GetThread))
export const GetThreadCount = R.compose(R.length, R.prop('threads'))
export const TimesCount = R.times(R.identity)

/*
 * STREAM BASED
 */
export const GetBufferWriteOffset = ({buffer$, initialOffset}) => {
  const accumulator = ([_buffer, _offset], buffer) => [buffer, _buffer.length + _offset]
  return buffer$.scan(accumulator, [{length: 0}, initialOffset])
}
export const SetBufferParams = ({buffer$, index, meta}) => {
  const initialOffset = GetOffset(meta, index)
  const addParams = R.compose(Rx.map(R.append(index)), GetBufferWriteOffset)
  return addParams({buffer$, initialOffset})
}

/**
 * Makes an HTTP request using the {HttpRequest} function and appends the
 * buffer response with appropriate write position and thread index.
 * @function
 * @param {Function} HttpRequest - function that takes in meta and index
 * @param {Object} r - a dict of meta and selected thread index
 * @param {Object} r.meta - the download meta info
 * @param {Object} r.index - index of the selected thread
 * @returns {Observable} a muxed {buffer$, response$}
 */
export const RequestThread = R.curry((HttpRequest, {meta, index}) => {
  const pluck = demuxFPH(['data$', 'response$'])
  const {response$, data$} = pluck(HttpRequest({meta, index}))
  const buffer$ = SetBufferParams({buffer$: data$, meta, index})
  return mux({buffer$, response$})
})
export const ToJSON$ = source$ => source$.map(JSON.stringify.bind(JSON))
export const ToBuffer$ = source$ => source$.map(ToBuffer(BUFFER_SIZE))
export const JSToBuffer$ = R.compose(ToBuffer$, ToJSON$)
export const BufferToJS$ = buffer$ => {
  return buffer$.map(buffer => JSON.parse(buffer.toString()))
}
export const RemoteFileSize$ = ({HTTP, options}) => {
  return HTTP.requestHead(options)
    .pluck('headers', 'content-length')
    .map((x) => parseInt(x, 10))
}
export const LocalFileSize$ = ({FILE, fd$}) => {
  return FILE.fstat(fd$.map(R.of)).pluck('size')
}
export const PickFirst = R.map(first)
export const CreateMeta$ = ({size$, options}) => {
  return size$.map((totalBytes) => {
    if (!isFinite(totalBytes)) throw new MTDError(FILE_SIZE_UNKNOWN)
    const threads = SplitRange(totalBytes, options.range)
    return R.merge(options, {totalBytes, threads, offsets: PickFirst(threads)})
  })
}
export const ReadFileAt$ = ({FILE, fd$, position$, size = BUFFER_SIZE}) => {
  const readParams$ = O.combineLatest(position$, fd$)
  const buffer = CreateFilledBuffer(size)
  const toParam = ([position, fd]) => [fd, buffer, 0, buffer.length, position]
  return FILE.read(readParams$.map(toParam))
}
export const MetaPosition$ = ({size$}) => size$.map(R.add(-BUFFER_SIZE))
export const CreateWriteBufferAtParams = ({fd$, buffer$, position$}) => {
  const toParam = ([buffer, fd, position]) => [fd, buffer, 0, buffer.length, position]
  return O.combineLatest(buffer$, fd$, position$.first()).map(toParam)
}
export const CreateWriteBufferParams = ({fd$, buffer$, position$}) => {
  const toParams = ([fd, buffer, position]) => [fd, buffer, 0, buffer.length, position]
  return O.combineLatest(fd$, O.zip(buffer$, position$))
    .map(R.compose(toParams, R.unnest))
}
export const SetMetaOffsets = ({meta$, written$, thread$}) => {
  const offsetLens = thread => R.compose(R.lensProp('offsets'), R.lensIndex(thread))
  const start$ = meta$.map(meta => ({meta, len: 0, thread: 0})).first()
  const source$ = O.merge(
    start$,
    O.zip(written$, thread$)
      .map(R.zipObj(['len', 'thread']))
      .withLatestFrom(meta$.map(R.objOf('meta')))
      .map(R.mergeAll)
  )

  const accumulator = (previous, current) => {
    const thread = current.thread
    const pMeta = previous.meta
    const oldVal = pMeta.offsets[thread]
    const lens = offsetLens(thread)
    const meta = R.set(lens, R.add(oldVal, current.len), pMeta)
    return R.merge(current, {meta})
  }
  return source$
    .scan(accumulator)
    .skip(1)
    .pluck('meta')
}
export const ReadJSON$ = R.compose(BufferToJS$, Rx.map(second), ReadFileAt$)
export const IsOffsetInRange = R.curry((meta, i) => {
  const start = R.lte(GetThreadStart(meta, i))
  const end = R.gt(GetThreadEnd(meta, i))
  const inRange = R.allPass([start, end])
  return inRange(GetOffset(meta, i))
})
export const FlattenMeta$ = Rx.flatMap((meta) => {
  const MergeMeta = R.map(R.compose(R.merge({meta}), R.objOf('index')))
  const IsValid = R.filter(IsOffsetInRange(meta))
  return MergeMeta(IsValid(TimesCount(GetThreadCount(meta))))
})
export const RxFlatMapReplay = R.curryN(2, R.compose(Rx.shareReplay(1), Rx.flatMap))
export const RxThrottleComplete = (window$, $, sh) => {
  const selector = window => O.merge($.throttle(window, sh), $.last())
  return window$.first().flatMap(selector)
}
export const RemoveMeta = ({FILE, meta$, fd$}) => {
  const size$ = meta$.pluck('totalBytes')
  return FILE.truncate(O.combineLatest(fd$, size$).take(1))
}
export const ResetFileName = ({FILE, meta$}) => {
  const params$ = meta$.map(meta => [meta.mtdPath, meta.path]).take(1)
  return FILE.rename(params$)
}
export const IsCompleted$ = ({meta$}) => {
  const equals = R.apply(R.equals)
  const offsetsA = R.prop('offsets')
  const offsetsB = R.compose(R.map(second), R.prop('threads'))
  const isComplete = R.compose(equals, R.ap([offsetsA, offsetsB]), R.of)
  return meta$.map(isComplete).distinctUntilChanged()
}
export const FinalizeDownload = ({FILE, fd$, meta$}) => {
  const metaRemoved$ = RemoveMeta({FILE, meta$, fd$})
  const renamed$ = metaRemoved$.flatMap(() => ResetFileName({FILE, meta$}))
  return mux({metaRemoved$, renamed$})
}

/**
 * Creates request params and then makes an HTTP request based on
 * meta info being passed and the selected thread
 * @function
 * @param {HTTP} HTTP - HTTP transformer
 * @param {Object} t - a dictionary of {meta} and {index}
 * @param {Object} t.meta - meta data retrieved from .mtd file
 * @param {Number} t.index - index of the thread that needs to be requested for
 * @returns {Observable} response$$ - multiplexed response stream
 */
export const RequestWithParams = R.uncurryN(
  2, HTTP => R.compose(HTTP.request, CreateRequestParams)
)

export const HttpRequest = R.compose(
  RxFlatMapReplay, RequestThread, RequestWithParams
)
export const HttpRequestMeta$ = ({HTTP, meta$}) => {
  return R.compose(
    demuxFPH(['buffer$', 'response$']),
    HttpRequest(HTTP),
    FlattenMeta$
  )(meta$)
}
export const DownloadFromMTDFile = ({FILE, HTTP, mtdPath}) => {
  /**
   * Open file to read+append
   */
  const fd$ = FILE.open(O.just([mtdPath, 'r+']))

  /**
   * Retrieve File size on disk
   */
  const size$ = LocalFileSize$({FILE, fd$})

  /**
   * Retrieve Meta info
   */
  const metaPosition$ = MetaPosition$({size$})
  const meta$ = ReadJSON$({FILE, fd$, position$: metaPosition$})

  /**
   * Make a HTTP request for each thread
   */
  const {response$, buffer$} = HttpRequestMeta$({HTTP, meta$})

  /**
   * Create write params and save buffer+offset to disk
   */
  const saveBuffer$ = FILE.write(CreateWriteBufferParams({
    FILE,
    fd$,
    buffer$: buffer$.map(first),
    position$: buffer$.map(second)
  }))

  /**
   * Update META info
   */
  const nMeta$ = SetMetaOffsets({
    meta$,
    written$: saveBuffer$.map(first),
    thread$: buffer$.map(R.nth(2))
  })

  /**
   * Persist META to disk
   */
  const metaWritten$ = FILE.write(CreateWriteBufferAtParams({
    fd$,
    buffer$: JSToBuffer$(RxThrottleComplete(meta$.pluck('metaWrite'), nMeta$)),
    position$: size$
  }))
  return mux({
    metaWritten$, localFileSize$: size$, fdR$: fd$, metaPosition$,
    meta$: O.merge(nMeta$, meta$),
    response$,
    completed$: IsCompleted$({meta$})
  })
}
export const CreateMTDFile = ({FILE, HTTP, options}) => {
  /**
   * Create a new file
   */
  const fd$ = FILE.open(O.just([options.mtdPath, 'w']))

  /**
   * Retreive file size on remote server
   */
  const size$ = RemoteFileSize$({HTTP, options})

  /**
   * Create initial meta data
   */
  const meta$ = CreateMeta$({options, size$})

  /**
   * Create a new file with meta info appended at the end
   */
  const written$ = FILE.write(CreateWriteBufferAtParams({
    FILE,
    fd$: fd$,
    buffer$: JSToBuffer$(meta$),
    position$: size$
  }))
  return mux({written$, meta$, remoteFileSize$: size$, fdW$: fd$})
}
<commit_msg>refactor(utils): rename variables<commit_after>/**
 * Created by tushar.mathur on 22/01/16.
 */

'use strict'

import PATH from 'path'
import URL from 'url'
import {Observable as O} from 'rx'
import R from 'ramda'
import * as Rx from './RxFP'
import {mux, demux} from 'muxer'
import {MTDError, FILE_SIZE_UNKNOWN} from './Error'

const first = R.nth(0)
const second = R.nth(1)
export const trace = R.curry((msg, value) => {
  console.log(msg, value)
  return value
})
export const demuxFP = R.curry((list, $) => demux($, ...list))
export const demuxFPH = R.curry((list, $) => R.head(demux($, ...list)))
export const BUFFER_SIZE = 512
export const NormalizePath = (path) => PATH.resolve(process.cwd(), path)
export const GenerateFileName = (x) => {
  return R.last(URL.parse(x).pathname.split('/')) || Date.now()
}
export const ResolvePath = R.compose(NormalizePath, GenerateFileName)
export const SplitRange = (totalBytes, range) => {
  const delta = Math.round(totalBytes / range)
  const start = R.times((x) => x * delta, range)
  const end = R.times((x) => (x + 1) * delta - 1, range)
  end[range - 1] = totalBytes
  return R.zip(start, end)
}
export const CreateRangeHeader = ([start, end]) => `bytes=${start}-${end}`
export const SetRangeHeader = ({request, range}) => {
  return R.set(
    R.lensPath(['headers', 'range']),
    CreateRangeHeader(range),
    R.omit(['threads', 'offsets'], request)
  )
}
export const CreateRequestParams = ({meta, index}) => {
  const range = [meta.offsets[index], second(meta.threads[index])]
  return SetRangeHeader({request: meta, range})
}
export const ToBuffer = R.curry((size, str) => {
  var buffer = CreateFilledBuffer(size)
  buffer.write(str)
  return buffer
})
export const CreateFilledBuffer = (size = BUFFER_SIZE, fill = ' ') => {
  const buffer = new Buffer(size)
  buffer.fill(fill)
  return buffer
}
export const MergeDefaultOptions = (options) => R.mergeAll([
  {mtdPath: options.path + '.mtd', range: 3, metaWrite: 300},
  options
])

// TODO: Use R.lens instead
export const GetOffset = R.curry((meta, index) => meta.offsets[index])
export const GetThread = R.curry((meta, index) => meta.threads[index])
export const GetThreadStart = R.curryN(2, R.compose(R.nth(0), GetThread))
export const GetThreadEnd = R.curryN(2, R.compose(R.nth(1), GetThread))
export const GetThreadCount = R.compose(R.length, R.prop('threads'))
export const TimesCount = R.times(R.identity)

/*
 * STREAM BASED
 */
export const GetBufferWriteOffset = ({buffer$, initialOffset}) => {
  const accumulator = ([_buffer, _offset], buffer) => [buffer, _buffer.length + _offset]
  return buffer$.scan(accumulator, [{length: 0}, initialOffset])
}
export const SetBufferParams = ({buffer$, index, meta}) => {
  const initialOffset = GetOffset(meta, index)
  const addParams = R.compose(Rx.map(R.append(index)), GetBufferWriteOffset)
  return addParams({buffer$, initialOffset})
}

/**
 * Makes an HTTP request using the {HttpRequest} function and appends the
 * buffer response with appropriate write position and thread index.
 * @function
 * @param {Function} HttpRequest - function that takes in meta and index
 * @param {Object} r - a dict of meta and selected thread index
 * @param {Object} r.meta - the download meta info
 * @param {Object} r.index - index of the selected thread
 * @returns {Observable} a muxed {buffer$, response$}
 */
export const RequestThread = R.curry((HttpRequest, {meta, index}) => {
  const pluck = demuxFPH(['data$', 'response$'])
  const {response$, data$} = pluck(HttpRequest({meta, index}))
  const buffer$ = SetBufferParams({buffer$: data$, meta, index})
  return mux({buffer$, response$})
})
export const ToJSON$ = source$ => source$.map(JSON.stringify.bind(JSON))
export const ToBuffer$ = source$ => source$.map(ToBuffer(BUFFER_SIZE))
export const JSToBuffer$ = R.compose(ToBuffer$, ToJSON$)
export const BufferToJS$ = buffer$ => {
  return buffer$.map(buffer => JSON.parse(buffer.toString()))
}
export const RemoteFileSize$ = ({HTTP, options}) => {
  return HTTP.requestHead(options)
    .pluck('headers', 'content-length')
    .map((x) => parseInt(x, 10))
}
export const LocalFileSize$ = ({FILE, fd$}) => {
  return FILE.fstat(fd$.map(R.of)).pluck('size')
}
export const PickFirst = R.map(first)
export const CreateMeta$ = ({size$, options}) => {
  return size$.map((totalBytes) => {
    if (!isFinite(totalBytes)) throw new MTDError(FILE_SIZE_UNKNOWN)
    const threads = SplitRange(totalBytes, options.range)
    return R.merge(options, {totalBytes, threads, offsets: PickFirst(threads)})
  })
}
export const ReadFileAt$ = ({FILE, fd$, position$, size = BUFFER_SIZE}) => {
  const readParams$ = O.combineLatest(position$, fd$)
  const buffer = CreateFilledBuffer(size)
  const toParam = ([position, fd]) => [fd, buffer, 0, buffer.length, position]
  return FILE.read(readParams$.map(toParam))
}
export const MetaPosition$ = ({size$}) => size$.map(R.add(-BUFFER_SIZE))
export const CreateWriteBufferAtParams = ({fd$, buffer$, position$}) => {
  const toParam = ([buffer, fd, position]) => [fd, buffer, 0, buffer.length, position]
  return O.combineLatest(buffer$, fd$, position$.first()).map(toParam)
}
export const CreateWriteBufferParams = ({fd$, buffer$, position$}) => {
  const toParams = ([fd, buffer, position]) => [fd, buffer, 0, buffer.length, position]
  return O.combineLatest(fd$, O.zip(buffer$, position$))
    .map(R.compose(toParams, R.unnest))
}
export const SetMetaOffsets = ({meta$, written$, thread$}) => {
  const offsetLens = thread => R.compose(R.lensProp('offsets'), R.lensIndex(thread))
  const start$ = meta$.map(meta => ({meta, len: 0, thread: 0})).first()
  const source$ = O.merge(
    start$,
    O.zip(written$, thread$)
      .map(R.zipObj(['len', 'thread']))
      .withLatestFrom(meta$.map(R.objOf('meta')))
      .map(R.mergeAll)
  )

  const accumulator = (previous, current) => {
    const thread = current.thread
    const pMeta = previous.meta
    const oldVal = pMeta.offsets[thread]
    const lens = offsetLens(thread)
    const meta = R.set(lens, R.add(oldVal, current.len), pMeta)
    return R.merge(current, {meta})
  }
  return source$
    .scan(accumulator)
    .skip(1)
    .pluck('meta')
}
export const ReadJSON$ = R.compose(BufferToJS$, Rx.map(second), ReadFileAt$)
export const IsOffsetInRange = R.curry((meta, i) => {
  const start = R.lte(GetThreadStart(meta, i))
  const end = R.gt(GetThreadEnd(meta, i))
  const inRange = R.allPass([start, end])
  return inRange(GetOffset(meta, i))
})
export const FlattenMeta$ = Rx.flatMap((meta) => {
  const MergeMeta = R.map(R.compose(R.merge({meta}), R.objOf('index')))
  const IsValid = R.filter(IsOffsetInRange(meta))
  return MergeMeta(IsValid(TimesCount(GetThreadCount(meta))))
})
export const RxFlatMapReplay = R.curryN(2, R.compose(Rx.shareReplay(1), Rx.flatMap))
export const RxThrottleComplete = (window$, $, sh) => {
  const selector = window => O.merge($.throttle(window, sh), $.last())
  return window$.first().flatMap(selector)
}
export const RemoveMeta = ({FILE, meta$, fd$}) => {
  const size$ = meta$.pluck('totalBytes')
  return FILE.truncate(O.combineLatest(fd$, size$).take(1))
}
export const ResetFileName = ({FILE, meta$}) => {
  const params$ = meta$.map(meta => [meta.mtdPath, meta.path]).take(1)
  return FILE.rename(params$)
}
export const IsCompleted$ = ({meta$}) => {
  const equals = R.apply(R.equals)
  const offsetsA = R.prop('offsets')
  const offsetsB = R.compose(R.map(second), R.prop('threads'))
  const isComplete = R.compose(equals, R.ap([offsetsA, offsetsB]), R.of)
  return meta$.map(isComplete).distinctUntilChanged()
}
export const FinalizeDownload = ({FILE, fd$, meta$}) => {
  const metaRemoved$ = RemoveMeta({FILE, meta$, fd$})
  const renamed$ = metaRemoved$.flatMap(() => ResetFileName({FILE, meta$}))
  return mux({metaRemoved$, renamed$})
}

/**
 * Creates request params and then makes an HTTP request based on
 * meta info being passed and the selected thread
 * @function
 * @param {HTTP} HTTP - HTTP transformer
 * @param {Object} t - a dictionary of {meta} and {index}
 * @param {Object} t.meta - meta data retrieved from .mtd file
 * @param {Number} t.index - index of the thread that needs to be requested for
 * @returns {Observable} response$$ - multiplexed response stream
 */
export const RequestWithParams = R.uncurryN(
  2, HTTP => R.compose(HTTP.request, CreateRequestParams)
)

export const HttpRequest = R.compose(
  RxFlatMapReplay, RequestThread, RequestWithParams
)
export const HttpRequestMeta$ = ({HTTP, meta$}) => {
  return R.compose(
    demuxFPH(['buffer$', 'response$']),
    HttpRequest(HTTP),
    FlattenMeta$
  )(meta$)
}
export const DownloadFromMTDFile = ({FILE, HTTP, mtdPath}) => {
  /**
   * Open file to read+append
   */
  const fd$ = FILE.open(O.just([mtdPath, 'r+']))

  /**
   * Retrieve File size on disk
   */
  const size$ = LocalFileSize$({FILE, fd$})

  /**
   * Retrieve Meta info
   */
  const metaPosition$ = MetaPosition$({size$})
  const meta$ = ReadJSON$({FILE, fd$, position$: metaPosition$})

  /**
   * Make a HTTP request for each thread
   */
  const {response$, buffer$} = HttpRequestMeta$({HTTP, meta$})

  /**
   * Create write params and save buffer+offset to disk
   */
  const bufferWritten$ = FILE.write(CreateWriteBufferParams({
    FILE,
    fd$,
    buffer$: buffer$.map(first),
    position$: buffer$.map(second)
  }))

  /**
   * Update META info
   */
  const nMeta$ = SetMetaOffsets({
    meta$,
    written$: bufferWritten$.map(first),
    thread$: buffer$.map(R.nth(2))
  })

  /**
   * Persist META to disk
   */
  const metaWritten$ = FILE.write(CreateWriteBufferAtParams({
    fd$,
    buffer$: JSToBuffer$(RxThrottleComplete(meta$.pluck('metaWrite'), nMeta$)),
    position$: size$
  }))
  return mux({
    metaWritten$, localFileSize$: size$, fdR$: fd$, metaPosition$,
    meta$: O.merge(nMeta$, meta$),
    response$,
    completed$: IsCompleted$({meta$})
  })
}
export const CreateMTDFile = ({FILE, HTTP, options}) => {
  /**
   * Create a new file
   */
  const fd$ = FILE.open(O.just([options.mtdPath, 'w']))

  /**
   * Retreive file size on remote server
   */
  const size$ = RemoteFileSize$({HTTP, options})

  /**
   * Create initial meta data
   */
  const meta$ = CreateMeta$({options, size$})

  /**
   * Create a new file with meta info appended at the end
   */
  const written$ = FILE.write(CreateWriteBufferAtParams({
    FILE,
    fd$: fd$,
    buffer$: JSToBuffer$(meta$),
    position$: size$
  }))
  return mux({written$, meta$, remoteFileSize$: size$, fdW$: fd$})
}
<|endoftext|>


<commit_before>/**
 * Generate graphQL for Vulcan schema fields
 */
import { isIntlField } from '../intl.js';
import relations from './relations.js';

// get GraphQL type for a given schema and field name
const getGraphQLType = (schema, fieldName, isInput = false) => {
  const field = schema[fieldName];
  const type = field.type.singleType;
  const typeName =
    typeof type === 'object' ? 'Object' : typeof type === 'function' ? type.name : type;

  if (field.isIntlData) {
    return isInput ? '[IntlValueInput]' : '[IntlValue]';
  }

  switch (typeName) {
    case 'String':
      return 'String';

    case 'Boolean':
      return 'Boolean';

    case 'Number':
      return 'Float';

    case 'SimpleSchema.Integer':
      return 'Int';

    // for arrays, look for type of associated schema field or default to [String]
    case 'Array':
      const arrayItemFieldName = `${fieldName}.$`;
      // note: make sure field has an associated array
      if (schema[arrayItemFieldName]) {
        // try to get array type from associated array
        const arrayItemType = getGraphQLType(schema, arrayItemFieldName);
        return arrayItemType ? `[${arrayItemType}]` : null;
      }
      return null;

    case 'Object':
      return 'JSON';

    case 'Date':
      return 'Date';

    default:
      return null;
  }
};

// for a given schema, return main type fields, selector fields,
// unique selector fields, orderBy fields, creatable fields, and updatable fields
export const getSchemaFields = (schema, typeName) => {
  const fields = {
    mainType: [],
    create: [],
    update: [],
    selector: [],
    selectorUnique: [],
    orderBy: [],
    readable: [],
  };
  const resolvers = [];

  Object.keys(schema).forEach(fieldName => {
    const field = schema[fieldName];
    const fieldType = getGraphQLType(schema, fieldName);
    const inputFieldType = getGraphQLType(schema, fieldName, true);

    const {
      canRead,
      canCreate,
      canUpdate,
      viewableBy,
      insertableBy,
      editableBy,
      resolveAs,
      description,
      selectable,
      unique,
    } = field;
    // only include fields that are viewable/insertable/editable and don't contain "$" in their name
    // note: insertable/editable fields must be included in main schema in case they're returned by a mutation
    // OpenCRUD backwards compatibility
    if (
      (canRead || canCreate || canUpdate || viewableBy || insertableBy || editableBy) &&
      fieldName.indexOf('$') === -1
    ) {
      const fieldDescription = description;
      const fieldDirective = isIntlField(field) ? '@intl' : '';
      const fieldArguments = isIntlField(field) ? [{ name: 'locale', type: 'String' }] : [];

      // if field is readable, make it filterable/orderable too
      if (canRead || viewableBy) {
        fields.readable.push({
          name: fieldName,
          type: fieldType,
        });
      }

      // if field has a resolveAs, push it to schema
      if (resolveAs) {
        // get resolver name from resolveAs object, or else default to field name
        const resolverName = resolveAs.fieldName || fieldName;

        // use specified GraphQL type or else convert schema type
        const fieldGraphQLType = resolveAs.type || fieldType;

        // if resolveAs is an object, first push its type definition
        // include arguments if there are any
        // note: resolved fields are not internationalized
        fields.mainType.push({
          description: resolveAs.description,
          name: resolverName,
          args: resolveAs.arguments,
          type: fieldGraphQLType,
        });

        // then build actual resolver object and pass it to addGraphQLResolvers
        const resolver = {
          [typeName]: {
            [resolverName]: (document, args, context, info) => {
              const { Users, currentUser } = context;
              // check that current user has permission to access the original non-resolved field
              const canReadField = Users.canReadField(currentUser, field, document);
              const { resolver, relation } = resolveAs;
              if (canReadField) {
                if (resolver) {
                  return resolver(document, args, context, info);
                } else {
                  return relations[relation]({
                    document,
                    args,
                    context,
                    info,
                    fieldName,
                    typeName: fieldGraphQLType,
                  });
                }
              } else {
                return null;
              }
            },
          },
        };
        resolvers.push(resolver);

        // if addOriginalField option is enabled, also add original field to schema
        if (resolveAs.addOriginalField && fieldType) {
          fields.mainType.push({
            description: fieldDescription,
            name: fieldName,
            args: fieldArguments,
            type: fieldType,
            directive: fieldDirective,
          });
        }
      } else {
        // try to guess GraphQL type
        if (fieldType) {
          fields.mainType.push({
            description: fieldDescription,
            name: fieldName,
            args: fieldArguments,
            type: fieldType,
            directive: fieldDirective,
          });
        }
      }

      // OpenCRUD backwards compatibility
      if (canCreate || insertableBy) {
        fields.create.push({
          name: fieldName,
          type: inputFieldType,
          required: !field.optional,
        });
      }
      // OpenCRUD backwards compatibility
      if (canUpdate || editableBy) {
        fields.update.push({
          name: fieldName,
          type: inputFieldType,
        });
      }

      // if field is i18nized, add foo_intl field containing all languages
      // NOTE: not necessary anymore because intl fields are added by addIntlFields() in collections.js
      // TODO: delete if not needed
      // if (isIntlField(field)) {
      //   // fields.mainType.push({
      //   //   name: `${fieldName}_intl`,
      //   //   type: '[IntlValue]',
      //   // });
      //   fields.create.push({
      //     name: `${fieldName}_intl`,
      //     type: '[IntlValueInput]',
      //   });
      //   fields.update.push({
      //     name: `${fieldName}_intl`,
      //     type: '[IntlValueInput]',
      //   });
      // }

      if (selectable) {
        fields.selector.push({
          name: fieldName,
          type: inputFieldType,
        });
      }

      if (selectable && unique) {
        fields.selectorUnique.push({
          name: fieldName,
          type: inputFieldType,
        });
      }
    }
  });

  return {
    fields,
    resolvers,
  };
};
<commit_msg>Let resolveAs take an array of resolver field definitions as well as a single item<commit_after>/**
 * Generate graphQL for Vulcan schema fields
 */
import { isIntlField } from '../intl.js';
import relations from './relations.js';

// get GraphQL type for a given schema and field name
const getGraphQLType = (schema, fieldName, isInput = false) => {
  const field = schema[fieldName];
  const type = field.type.singleType;
  const typeName =
    typeof type === 'object' ? 'Object' : typeof type === 'function' ? type.name : type;

  if (field.isIntlData) {
    return isInput ? '[IntlValueInput]' : '[IntlValue]';
  }

  switch (typeName) {
    case 'String':
      return 'String';

    case 'Boolean':
      return 'Boolean';

    case 'Number':
      return 'Float';

    case 'SimpleSchema.Integer':
      return 'Int';

    // for arrays, look for type of associated schema field or default to [String]
    case 'Array':
      const arrayItemFieldName = `${fieldName}.$`;
      // note: make sure field has an associated array
      if (schema[arrayItemFieldName]) {
        // try to get array type from associated array
        const arrayItemType = getGraphQLType(schema, arrayItemFieldName);
        return arrayItemType ? `[${arrayItemType}]` : null;
      }
      return null;

    case 'Object':
      return 'JSON';

    case 'Date':
      return 'Date';

    default:
      return null;
  }
};

// for a given schema, return main type fields, selector fields,
// unique selector fields, orderBy fields, creatable fields, and updatable fields
export const getSchemaFields = (schema, typeName) => {
  const fields = {
    mainType: [],
    create: [],
    update: [],
    selector: [],
    selectorUnique: [],
    orderBy: [],
    readable: [],
  };
  const resolvers = [];

  Object.keys(schema).forEach(fieldName => {
    const field = schema[fieldName];
    const fieldType = getGraphQLType(schema, fieldName);
    const inputFieldType = getGraphQLType(schema, fieldName, true);

    const {
      canRead,
      canCreate,
      canUpdate,
      viewableBy,
      insertableBy,
      editableBy,
      description,
      selectable,
      unique,
    } = field;
    // only include fields that are viewable/insertable/editable and don't contain "$" in their name
    // note: insertable/editable fields must be included in main schema in case they're returned by a mutation
    // OpenCRUD backwards compatibility
    if (
      (canRead || canCreate || canUpdate || viewableBy || insertableBy || editableBy) &&
      fieldName.indexOf('$') === -1
    ) {
      const fieldDescription = description;
      const fieldDirective = isIntlField(field) ? '@intl' : '';
      const fieldArguments = isIntlField(field) ? [{ name: 'locale', type: 'String' }] : [];

      // if field is readable, make it filterable/orderable too
      if (canRead || viewableBy) {
        fields.readable.push({
          name: fieldName,
          type: fieldType,
        });
      }

      // if field has a resolveAs, push it to schema
      // note: resolveAs can be an array containing multiple resolver definitions
      if (field.resolveAs) {

        const resolveAsArray = Array.isArray(field.resolveAs) ? field.resolveAs : [field.resolveAs];

        resolveAsArray.forEach(resolveAs => {
          // get resolver name from resolveAs object, or else default to field name
          const resolverName = resolveAs.fieldName || fieldName;

          // use specified GraphQL type or else convert schema type
          const fieldGraphQLType = resolveAs.type || fieldType;

          // if resolveAs is an object, first push its type definition
          // include arguments if there are any
          // note: resolved fields are not internationalized
          fields.mainType.push({
            description: resolveAs.description,
            name: resolverName,
            args: resolveAs.arguments,
            type: fieldGraphQLType,
          });

          // then build actual resolver object and pass it to addGraphQLResolvers
          const resolver = {
            [typeName]: {
              [resolverName]: (document, args, context, info) => {
                const { Users, currentUser } = context;
                // check that current user has permission to access the original non-resolved field
                const canReadField = Users.canReadField(currentUser, field, document);
                const { resolver, relation } = resolveAs;
                if (canReadField) {
                  if (resolver) {
                    return resolver(document, args, context, info);
                  } else {
                    return relations[relation]({
                      document,
                      args,
                      context,
                      info,
                      fieldName,
                      typeName: fieldGraphQLType,
                    });
                  }
                } else {
                  return null;
                }
              },
            },
          };
          resolvers.push(resolver);

          // if addOriginalField option is enabled, also add original field to schema
          if (resolveAs.addOriginalField && fieldType) {
            fields.mainType.push({
              description: fieldDescription,
              name: fieldName,
              args: fieldArguments,
              type: fieldType,
              directive: fieldDirective,
            });
          }
        });
      } else {
        // try to guess GraphQL type
        if (fieldType) {
          fields.mainType.push({
            description: fieldDescription,
            name: fieldName,
            args: fieldArguments,
            type: fieldType,
            directive: fieldDirective,
          });
        }
      }

      // OpenCRUD backwards compatibility
      if (canCreate || insertableBy) {
        fields.create.push({
          name: fieldName,
          type: inputFieldType,
          required: !field.optional,
        });
      }
      // OpenCRUD backwards compatibility
      if (canUpdate || editableBy) {
        fields.update.push({
          name: fieldName,
          type: inputFieldType,
        });
      }

      // if field is i18nized, add foo_intl field containing all languages
      // NOTE: not necessary anymore because intl fields are added by addIntlFields() in collections.js
      // TODO: delete if not needed
      // if (isIntlField(field)) {
      //   // fields.mainType.push({
      //   //   name: `${fieldName}_intl`,
      //   //   type: '[IntlValue]',
      //   // });
      //   fields.create.push({
      //     name: `${fieldName}_intl`,
      //     type: '[IntlValueInput]',
      //   });
      //   fields.update.push({
      //     name: `${fieldName}_intl`,
      //     type: '[IntlValueInput]',
      //   });
      // }

      if (selectable) {
        fields.selector.push({
          name: fieldName,
          type: inputFieldType,
        });
      }

      if (selectable && unique) {
        fields.selectorUnique.push({
          name: fieldName,
          type: inputFieldType,
        });
      }
    }
  });

  return {
    fields,
    resolvers,
  };
};
<|endoftext|>


<commit_before>'use strict';

// Module dependencies
var should = require('should'),
    mongoose = require('mongoose'),
    GitDev = mongoose.model('GitDev'),
    nock = require('nock'),
    gitstats_controller = require('../controllers/gitstats.js'),
    git_user = {
        'id': 1234,
        'login': 'My_Login'
    },
    git_repos = [
      {repo:
        {'id': 123,
         'name': 'my_first_repo'
        }
      },
      {repo: {
         'id': 124,
         'name': 'my_second_repo'
        }
      }
    ];

// Globals
var gitdev,
    git_user_mock;

describe('<Controller Tests>', function() {
    describe('Git Developer Lookup', function() {

        beforeEach( function(done) {
            git_user_mock = nock('https://api.github.com');
            git_user_mock.filteringPath(function(path) {
                return path.split('?')[0];
            })
            .get('/users/my_login')
            .reply(200, git_user)
            .get('/users/my_login/repos')
            .reply(200, git_repos);
            done();
        });

        it('Should be able to save a developer', function(done) {
            gitstats_controller.git_developer_lookup({body: {username: 'my_login'}},
                {
                    json: function(data) {
                        // Test User creation
                        data.user.id.should.equal(1234);
                        data.user.login.should.equal('My_Login');
                        data.user.login_lower.should.equal('my_login');
                        // Test Repos portion
                        data.repos[0].id.should.equal(123);
                        data.repos[1].id.should.equal(124);

                        // Make sure that both mocks were actually used
                        git_user_mock.isDone().should.equal(true);

                        done();
                    }
            });
        });

        it('Saving a developer a second time within 24 hours should not trigger github api requests', function(done) {
            gitstats_controller.git_developer_lookup({body: {username: 'my_login'}},
                {
                    json: function(data) {
                        // Test User creation
                        data.user.id.should.equal(1234);
                        data.user.login.should.equal('My_Login');
                        data.user.login_lower.should.equal('my_login');
                        // Test Repos portion
                        data.repos[0].id.should.equal(123);
                        data.repos[1].id.should.equal(124);

                        // Make sure that both mocks were actually used
                        git_user_mock.isDone().should.equal(true);

                        // Setup a new mock
                        git_user_mock.filteringPath(function(path) {
                                return path.split('?')[0];
                            })
                            .get('/users/my_login')
                            .reply(200, git_user)
                            .get('/users/my_login/repos')
                            .reply(200, git_repos);

                        gitstats_controller.git_developer_lookup({body: {username: 'my_login'}},
                            {
                                json: function(data) {
                                    // Test User creation
                                    data.user.id.should.equal(1234);
                                    data.user.login.should.equal('My_Login');
                                    data.user.login_lower.should.equal('my_login');
                                    // Test Repos portion
                                    data.repos[0].id.should.equal(123);
                                    data.repos[1].id.should.equal(124);

                                    git_user_mock.isDone().should.equal(false);
                                    git_user_mock.pendingMocks().length.should.equal(2);
                                    done();
                                }
                        });
                    }
            });
        });

        it('Saving a developer outside of 24 hours should trigger github api requests and update developer', function(done) {
            gitstats_controller.git_developer_lookup({body: {username: 'my_login'}},
                {
                    json: function(data) {
                        // Test User creation
                        data.user.id.should.equal(1234);
                        data.user.login.should.equal('My_Login');
                        data.user.login_lower.should.equal('my_login');
                        // Test Repos portion
                        data.repos[0].id.should.equal(123);
                        data.repos[1].id.should.equal(124);

                        // Make sure that both mocks were actually used
                        git_user_mock.isDone().should.equal(true);

                        var query = GitDev.where({'user.id' : 1234});
                        query.findOne( function(err, gitDeveloper) {
                            if (!err) {
                                var now = new Date(),
                                    dateMinus2Day = now.setDate(now.getDate()-2);
                                gitDeveloper.updated_at = dateMinus2Day;
                                gitDeveloper.save(function(err) {
                                    if (!err) {
                                        var git_user_update = {
                                            'id': 1234,
                                            'login': 'My_Login',
                                            'name': 'Big Bad Developer'
                                        },
                                        git_repos_update = [
                                            {repo:
                                              {'id': 123,
                                              'name': 'my_first_repo1'
                                              }
                                            }
                                        ];

                                        // Setup a new mock
                                        git_user_mock.filteringPath(function(path) {
                                                return path.split('?')[0];
                                            })
                                            .get('/users/my_login')
                                            .reply(200, git_user_update)
                                            .get('/users/my_login/repos')
                                            .reply(200, git_repos_update);

                                        gitstats_controller.git_developer_lookup({body: {username: 'my_login'}},
                                            {
                                                json: function(data) {
                                                    // Test User creation
                                                    data.user.id.should.equal(1234);
                                                    data.user.login.should.equal('My_Login');
                                                    data.user.login_lower.should.equal('my_login');
                                                    data.user.name.should.equal('Big Bad Developer');
                                                    // Test Repos portion
                                                    data.repos[0].id.should.equal(123);
                                                    data.repos[0].name.should.equal('my_first_repo1');
                                                    data.repos.length.should.equal(1);

                                                    git_user_mock.isDone().should.equal(true);
                                                    done();
                                                }
                                        });
                                    }
                                });
                            }
                        });
                    }
            });
        });

        afterEach( function(done) {

            // clean up any remaining mocks
            nock.cleanAll();

            var query = GitDev.where({'user.id' : 1234});
            query.findOne( function(err, gitDeveloper) {
                if (!err) {
                    if (gitDeveloper) {
                        gitDeveloper.remove();
                    }
                    done();
                } else {
                    console.log(err);
                }
            });
        });

        it('A developer who is not found on github should return a status', function(done) {
            nock.cleanAll();
            git_user_mock = nock('https://api.github.com');
            git_user_mock.filteringPath(function(path) {
                return path.split('?')[0];
            })
            .get('/users/my_login')
            .reply(404, undefined)
            .get('/users/my_login/repos')
            .reply(404, undefined);

            gitstats_controller.git_developer_lookup({body: {username: 'my_login'}},
                {
                    json: function(data) {
                        // Test User creation
                        data.status.should.equal('user not found');

                        // Make sure that both mocks were actually used
                        git_user_mock.isDone().should.equal(true);
                        nock.cleanAll();
                        done();
                    }
            });
        });

    });
});

// Test Suites
describe('<Unit Test>', function() {
    describe('Model GitDev', function() {
        beforeEach(function(done) {

            gitdev = new GitDev({
                user: git_user,
                repos: git_repos
            });

            done();
        });

        describe('Method Save', function() {
            it('Should be able to save new Git Developer', function(done) {
                return gitdev.save(function(err) {
                    should.not.exist(err);
                    gitdev.user.id.should.equal(1234);
                    gitdev.updated_at.should.not.have.length(0);
                    done();
                });
            });

            it('Should not allow duplicate users to exist - throws error', function(done) {
                return gitdev.save(function(err) {
                    var gitdev2 = new GitDev({
                        user: git_user,
                        repos: git_repos
                    });

                    gitdev2.save(function(err) {
                        should.exist(err);
                        err.code.should.equal(11000);
                        done();
                    });
                });
            });

        });

        afterEach( function(done) {
            gitdev.remove();
            done();
        });

    });
});<commit_msg>update tests for new user repo format<commit_after>'use strict';

// Module dependencies
var should = require('should'),
    mongoose = require('mongoose'),
    GitDev = mongoose.model('GitDev'),
    nock = require('nock'),
    gitstats_controller = require('../controllers/gitstats.js'),
    git_user = {
        'id': 1234,
        'login': 'My_Login'
    },
    git_repos = [
      {repo:
        {'id': 123,
         'name': 'my_first_repo'
        }
      },
      {repo: {
         'id': 124,
         'name': 'my_second_repo'
        }
      }
    ];

// Globals
var gitdev,
    git_user_mock;

describe('<Controller Tests>', function() {
    describe('Git Developer Lookup', function() {

        beforeEach( function(done) {
            git_user_mock = nock('https://api.github.com');
            git_user_mock.filteringPath(function(path) {
                return path.split('?')[0];
            })
            .get('/users/my_login')
            .reply(200, git_user)
            .get('/users/my_login/repos')
            .reply(200, git_repos);
            done();
        });

        it('Should be able to save a developer', function(done) {
            gitstats_controller.git_developer_lookup({body: {username: 'my_login'}},
                {
                    json: function(data) {
                        // Test User creation
                        data.user.id.should.equal(1234);
                        data.user.login.should.equal('My_Login');
                        data.user.login_lower.should.equal('my_login');
                        // Test Repos portion
                        data.repos[0].repo.id.should.equal(123);
                        data.repos[1].repo.id.should.equal(124);

                        // Make sure that both mocks were actually used
                        git_user_mock.isDone().should.equal(true);

                        done();
                    }
            });
        });

        it('Saving a developer a second time within 24 hours should not trigger github api requests', function(done) {
            gitstats_controller.git_developer_lookup({body: {username: 'my_login'}},
                {
                    json: function(data) {
                        // Test User creation
                        data.user.id.should.equal(1234);
                        data.user.login.should.equal('My_Login');
                        data.user.login_lower.should.equal('my_login');
                        // Test Repos portion
                        data.repos[0].repo.id.should.equal(123);
                        data.repos[1].repo.id.should.equal(124);

                        // Make sure that both mocks were actually used
                        git_user_mock.isDone().should.equal(true);

                        // Setup a new mock
                        git_user_mock.filteringPath(function(path) {
                                return path.split('?')[0];
                            })
                            .get('/users/my_login')
                            .reply(200, git_user)
                            .get('/users/my_login/repos')
                            .reply(200, git_repos);

                        gitstats_controller.git_developer_lookup({body: {username: 'my_login'}},
                            {
                                json: function(data) {
                                    // Test User creation
                                    data.user.id.should.equal(1234);
                                    data.user.login.should.equal('My_Login');
                                    data.user.login_lower.should.equal('my_login');
                                    // Test Repos portion
                                    data.repos[0].repo.id.should.equal(123);
                                    data.repos[1].repo.id.should.equal(124);

                                    git_user_mock.isDone().should.equal(false);
                                    git_user_mock.pendingMocks().length.should.equal(2);
                                    done();
                                }
                        });
                    }
            });
        });

        it('Saving a developer outside of 24 hours should trigger github api requests and update developer', function(done) {
            gitstats_controller.git_developer_lookup({body: {username: 'my_login'}},
                {
                    json: function(data) {
                        // Test User creation
                        data.user.id.should.equal(1234);
                        data.user.login.should.equal('My_Login');
                        data.user.login_lower.should.equal('my_login');
                        // Test Repos portion
                        data.repos[0].repo.id.should.equal(123);
                        data.repos[1].repo.id.should.equal(124);

                        // Make sure that both mocks were actually used
                        git_user_mock.isDone().should.equal(true);

                        var query = GitDev.where({'user.id' : 1234});
                        query.findOne( function(err, gitDeveloper) {
                            if (!err) {
                                var now = new Date(),
                                    dateMinus2Day = now.setDate(now.getDate()-2);
                                gitDeveloper.updated_at = dateMinus2Day;
                                gitDeveloper.save(function(err) {
                                    if (!err) {
                                        var git_user_update = {
                                            'id': 1234,
                                            'login': 'My_Login',
                                            'name': 'Big Bad Developer'
                                        },
                                        git_repos_update = [
                                            {repo:
                                              {'id': 123,
                                              'name': 'my_first_repo1'
                                              }
                                            }
                                        ];

                                        // Setup a new mock
                                        git_user_mock.filteringPath(function(path) {
                                                return path.split('?')[0];
                                            })
                                            .get('/users/my_login')
                                            .reply(200, git_user_update)
                                            .get('/users/my_login/repos')
                                            .reply(200, git_repos_update);

                                        gitstats_controller.git_developer_lookup({body: {username: 'my_login'}},
                                            {
                                                json: function(data) {
                                                    // Test User creation
                                                    data.user.id.should.equal(1234);
                                                    data.user.login.should.equal('My_Login');
                                                    data.user.login_lower.should.equal('my_login');
                                                    data.user.name.should.equal('Big Bad Developer');
                                                    // Test Repos portion
                                                    data.repos[0].repo.id.should.equal(123);
                                                    data.repos[0].repo.name.should.equal('my_first_repo1');
                                                    data.repos.length.should.equal(1);

                                                    git_user_mock.isDone().should.equal(true);
                                                    done();
                                                }
                                        });
                                    }
                                });
                            }
                        });
                    }
            });
        });

        afterEach( function(done) {

            // clean up any remaining mocks
            nock.cleanAll();

            var query = GitDev.where({'user.id' : 1234});
            query.findOne( function(err, gitDeveloper) {
                if (!err) {
                    if (gitDeveloper) {
                        gitDeveloper.remove();
                    }
                    done();
                } else {
                    console.log(err);
                }
            });
        });

        it('A developer who is not found on github should return a status', function(done) {
            nock.cleanAll();
            git_user_mock = nock('https://api.github.com');
            git_user_mock.filteringPath(function(path) {
                return path.split('?')[0];
            })
            .get('/users/my_login')
            .reply(404, undefined)
            .get('/users/my_login/repos')
            .reply(404, undefined);

            gitstats_controller.git_developer_lookup({body: {username: 'my_login'}},
                {
                    json: function(data) {
                        // Test User creation
                        data.status.should.equal('user not found');

                        // Make sure that both mocks were actually used
                        git_user_mock.isDone().should.equal(true);
                        nock.cleanAll();
                        done();
                    }
            });
        });

    });
});

// Test Suites
describe('<Unit Test>', function() {
    describe('Model GitDev', function() {
        beforeEach(function(done) {

            gitdev = new GitDev({
                user: git_user,
                repos: git_repos
            });

            done();
        });

        describe('Method Save', function() {
            it('Should be able to save new Git Developer', function(done) {
                return gitdev.save(function(err) {
                    should.not.exist(err);
                    gitdev.user.id.should.equal(1234);
                    gitdev.updated_at.should.not.have.length(0);
                    done();
                });
            });

            it('Should not allow duplicate users to exist - throws error', function(done) {
                return gitdev.save(function(err) {
                    var gitdev2 = new GitDev({
                        user: git_user,
                        repos: git_repos
                    });

                    gitdev2.save(function(err) {
                        should.exist(err);
                        err.code.should.equal(11000);
                        done();
                    });
                });
            });

        });

        afterEach( function(done) {
            gitdev.remove();
            done();
        });

    });
});<|endoftext|>


<commit_before>const Gibberish = require( 'gibberish-dsp' )
const Ugen      = require( './ugen.js' )

const Busses = {
  create( Audio ) {
    const busses = {}

    const busDescription = {
      properties:Gibberish.Bus.defaults,
      methods:null,
      name:'Bus',
      category:'misc'
    }

    busses.Bus = Ugen( Gibberish.Bus, busDescription, Audio )

    const bus2Description = {
      properties:Gibberish.Bus2.defaults,
      methods:null,
      name:'Bus2',
      category:'misc'
    }

    busses.Bus2 = Ugen( Gibberish.Bus2, bus2Description, Audio )
    return busses
  }
}

module.exports = Busses
<commit_msg>busses can now accept objects as arguments via sugar<commit_after>const Gibberish = require( 'gibberish-dsp' )
const Ugen      = require( './ugen.js' )

const Busses = {
  create( Audio ) {
    const busses = {}

    const busDescription = {
      properties:Gibberish.Bus.defaults,
      methods:null,
      name:'Bus',
      category:'misc'
    }

    busses.__Bus = Ugen( Gibberish.Bus, busDescription, Audio )
    busses.Bus = function( ...args ) {
      let props
      if( args.length > 1 || args.length === 1 && typeof args[0] !== 'string' ) {
        props = { inputs:args }
      }else if( args.length === 1 ) {
        props = args[0]
      }

      return props !== undefined ? busses.__Bus( props ) : busses.__Bus()
    }

    const bus2Description = {
      properties:Gibberish.Bus2.defaults,
      methods:null,
      name:'Bus2',
      category:'misc'
    }

    busses.__Bus2 = Ugen( Gibberish.Bus2, bus2Description, Audio )
    busses.Bus2 = function( ...args ) {
      let props
      if( args.length > 1 || (args.length === 1 && typeof args[0] !== 'string' && args[0].type !== 'ensemble' )) {
        props = { inputs:args }
      }else if( args.length === 1 ) {
        props = args[0]
      }

      return props !== undefined ? busses.__Bus2( props ) : busses.__Bus2()
    }

    return busses
  }
}

module.exports = Busses
<|endoftext|>


<commit_before>export default {
  uploads: {
    title: 'Upload a custom dataset',
    body:
      '
Drop a file in the designated area to analyze or subscribe to it. The recommended maximum file size is 1MB. Anything larger than that may not work properly.

NOTE: This feature counts alerts or hectares inside of polygons; therefore, only polygon data is supported, not point and line data. Please ensure that your file only contains polygon data. A maximum of 1,000 feautures can be analysed in a single upload, contained within a single layer. Multiple layers are not supported.

List of supported file formats:

Unzipped: .csv, .json, .geojson, .kml, .kmz (.csv files must contain a geom column of shape data converted to well known text (WKT) format).

Zipped: .shp (zipped shapefiles must include .shp, .shx, .dbf, and .prj files)

'
  },
  lossDisclaimer: {
    title: 'Comparing Loss and Gain',
    body:
      '
Due to variation in research methodology and/or date of content, tree cover and tree cover loss and gain statistics cannot be compared against each other. Accordingly, “net” loss cannot be calculated by subtracting tree cover gain from tree cover loss, and current (or post-2000) tree cover cannot be determined by subtracting annual tree cover loss from year 2000 tree cover.

Please also be aware that “tree cover” does not equate to “forest cover.” “Tree cover” refers to the biophysical presence of trees, which may be a part of natural forests or tree plantations. Thus, loss of tree cover may occur for many reasons, including deforestation, fire, and logging within the course of sustainable forestry operations. Similarly, tree cover gain may indicate the growth of tree canopy within natural or managed forests.

Citation: Hansen, M. C., P. V. Potapov, R. Moore, M. Hancher, S. A. Turubanova, A. Tyukavina, D. Thau, S. V. Stehman, S. J. Goetz, T. R. Loveland, A. Kommareddy, A. Egorov, L. Chini, C. O. Justice, and J. R. G. Townshend. 2013. “High-Resolution Global Maps of 21st-Century Forest Cover Change.” Science 342 (15 November): 850–53. Data available on-line from: http://earthenginepartners.appspot.com/science-2013-global-forest.

Suggested citations for data as displayed on GFW: Hansen, M. C., P. V. Potapov, R. Moore, M. Hancher, S. A. Turubanova, A. Tyukavina, D. Thau, S. V. Stehman, S. J. Goetz, T. R. Loveland, A. Kommareddy, A. Egorov, L. Chini, C. O. Justice, and J. R. G. Townshend. 2013. “Hansen/UMD/Google/USGS/NASA Tree Cover Loss and Gain Area.” University of Maryland, Google, USGS, and NASA. Accessed through Global Forest Watch on  [date]. www.globalforestwatch.org.
'
  },
  webhookPreview: {
    title: 'Webhook URL.',
    body: `
What is this feature?

Webhooks are data sent on demand from one app (GFW) to another over HTTP(S) instead of through the command line in your computer, formatted in XML, JSON, or form-encoded serialization.

What does the payload look like?

  {
    layerSlug: "layer slug",
    alert_name: "area of interest name",
    selected_area: "area in hectares",
    unsubscribe_url: "url",
    area_url: "url of the area dashboard",
    area_map: "url of the area on the map",
    alert_date_begin: "begin date",
    alert_date_end: "end date"
  }

`
  }
};
<commit_msg>update webhook info modal<commit_after>export default {
  uploads: {
    title: 'Upload a custom dataset',
    body:
      '
Drop a file in the designated area to analyze or subscribe to it. The recommended maximum file size is 1MB. Anything larger than that may not work properly.

NOTE: This feature counts alerts or hectares inside of polygons; therefore, only polygon data is supported, not point and line data. Please ensure that your file only contains polygon data. A maximum of 1,000 feautures can be analysed in a single upload, contained within a single layer. Multiple layers are not supported.

List of supported file formats:

Unzipped: .csv, .json, .geojson, .kml, .kmz (.csv files must contain a geom column of shape data converted to well known text (WKT) format).

Zipped: .shp (zipped shapefiles must include .shp, .shx, .dbf, and .prj files)

'
  },
  lossDisclaimer: {
    title: 'Comparing Loss and Gain',
    body:
      '
Due to variation in research methodology and/or date of content, tree cover and tree cover loss and gain statistics cannot be compared against each other. Accordingly, “net” loss cannot be calculated by subtracting tree cover gain from tree cover loss, and current (or post-2000) tree cover cannot be determined by subtracting annual tree cover loss from year 2000 tree cover.

Please also be aware that “tree cover” does not equate to “forest cover.” “Tree cover” refers to the biophysical presence of trees, which may be a part of natural forests or tree plantations. Thus, loss of tree cover may occur for many reasons, including deforestation, fire, and logging within the course of sustainable forestry operations. Similarly, tree cover gain may indicate the growth of tree canopy within natural or managed forests.

Citation: Hansen, M. C., P. V. Potapov, R. Moore, M. Hancher, S. A. Turubanova, A. Tyukavina, D. Thau, S. V. Stehman, S. J. Goetz, T. R. Loveland, A. Kommareddy, A. Egorov, L. Chini, C. O. Justice, and J. R. G. Townshend. 2013. “High-Resolution Global Maps of 21st-Century Forest Cover Change.” Science 342 (15 November): 850–53. Data available on-line from: http://earthenginepartners.appspot.com/science-2013-global-forest.

Suggested citations for data as displayed on GFW: Hansen, M. C., P. V. Potapov, R. Moore, M. Hancher, S. A. Turubanova, A. Tyukavina, D. Thau, S. V. Stehman, S. J. Goetz, T. R. Loveland, A. Kommareddy, A. Egorov, L. Chini, C. O. Justice, and J. R. G. Townshend. 2013. “Hansen/UMD/Google/USGS/NASA Tree Cover Loss and Gain Area.” University of Maryland, Google, USGS, and NASA. Accessed through Global Forest Watch on  [date]. www.globalforestwatch.org.
'
  },
  webhookPreview: {
    title: 'Webhook URL.',
    body: `
What is this feature?

Webhooks are data sent on demand from one app (GFW) to another over HTTP(S) instead of through the command line in your computer, formatted in XML, JSON, or form-encoded serialization.

What does the payload look like?

  {
    layerSlug: "layer slug",
    alert_name: "area of interest name",
    alerts: "data for your area alert",
    alert_date_begin: "begin date",
    alert_date_end: "end date"
    alert_link: "url of the area on the map",
    dashboard_url: "url of the area dashboard",
    subscription_url: "url to My GFW for managing the area",
    unsubscribe_url: "link to unsubscribe from alerts",
  }

`
  }
};
<|endoftext|>


<commit_before>import datastore from '@google-cloud/datastore';
import makeDebug from 'debug';
import Proto from 'uberproto';
import { NotFound } from 'feathers-errors';
const debug = makeDebug('feathers-datastore');

const MAX_INDEX_SIZE = 1500;

class Datastore {
  constructor(options = {}) {
    const datastoreOpt = { projectId: options.projectId };

    if (options.credentials) {
      datastoreOpt.credentials = options.credentials;
    }
    this.store = datastore(datastoreOpt);

    this.id = options.id || 'id';
    this.kind = options.kind;
    this.events = options.events;
    this.autoIndex = options.autoIndex || false;

    // NOTE: This isn't nice, but it's the only way to give internal methods full
    //  unrestricted (no hooks) access to all methods
    [
      'find',
      'get',
      'create',
      'update',
      'patch',
      'remove'
    ].forEach(method => {
      this[method] = (...args) => this[`_${method}`](...args);
    });
  }

  extend(obj) {
    return Proto.extend(obj, this);
  }

  _get(id, params) {
    let key = this.makeKey(id, params);
    return this.store.get(key)
      .then(([ entity ]) => entity)
      .then(entity => this.entityToPlain(entity, true))
      .then(entity => {
        if (!entity) {
          throw new NotFound(`No record found for id '${id}'`);
        }

        return entity;
      });
  }

  _create(data, params = {}) {
    let entities = [],
        key;

    data = Array.isArray(data) ? data : [data];

    data.map(item => {
      if (item.hasOwnProperty(this.id)) {
        key = this.makeKey(item[this.id], params);
      } else {
        key = this.makeKey(undefined, params);
      }
      entities.push({
        key,
        data: item,
      });
    });

    // unbox if only 1 entity is inserted
    if (data.length === 1) {
      entities = entities[0];
    }

    // Convert entities to explicit format, to allow for indexing
    entities = this.makeExplicitEntity(entities, params);

    return this.store.insert(entities)
      .then(() => entities)
      .then(entity => this.entityToPlain(entity));
  }

  _update(id, data, params = {}) {
    let key = this.makeKey(id, params),
        entity = { key, data },
        { query = {} } = params,
        method = query.create ? 'upsert' : 'update';

    entity = this.makeExplicitEntity(entity, params);

    return this.store[method](entity)
      .then(() => entity)
      .then(entity => this.entityToPlain(entity))
      .catch(err => {
        // NOTE: Updating a not found entity will result in a bad request, rather than
        //  a not found, this gets around that, though in future should be made more
        //  secure
        if (err.code === 400 && err.message === 'no entity to update') {
          throw new NotFound(`No record found for id \'${id}'`);
        }

        throw err;
      });
  }

  _patch(id, data, params) {
    const get = Datastore.prototype._get.bind(this),
          find = Datastore.prototype._find.bind(this);

    return Promise.resolve()
      .then(() => id ? get(id, params) : find(params))
      .then(results => {
        let entities,
            makeNewEntity = (current, update) => {
              return {
                key: this.makeKey(current[ this.id ], params),
                data: Object.assign({}, current, update)
              };
            };

        if (Array.isArray(results)) {
          entities = results.map(current => makeNewEntity(current, data));
        } else {
          entities = makeNewEntity(results, data);
        }

        entities = this.makeExplicitEntity(entities, params);

        return this.store.update(entities)
          .then(() => entities);
      })
      .then(entity => this.entityToPlain(entity));
  }

  _find(params = {}) {
    params.query = params.query || {};

    let { ancestor, namespace, kind = this.kind, ...query } = params.query,
        dsQuery = this.store.createQuery(namespace, kind),
        filters;

    if (ancestor) {
      let ancestorKey = this.makeKey(ancestor, params);
      dsQuery = dsQuery.hasAncestor(ancestorKey);
    }

    filters = Object.entries(query)
      .reduce((filters, [key, value]) => {
        const opMap = {
          $gt: '>',
          $gte: '>=',
          $lt: '<',
          $lte: '<=',
          '=': '='
        };

        let special;

        // Normalize
        if (typeof value !== 'object' || value instanceof this.store.key().constructor || value === null) {
          value = { '=': value };
        }

        special = Object.entries(value)
          .filter(([ op ]) => opMap[op])
          .map(([ op, val ]) => {
            // Try convert it into a number
            if (typeof val === 'string') {
              let valAsNum = parseFloat(val);

              if (!isNaN(valAsNum)) {
                // Its a number, assign it to original val
                val = valAsNum;
              }
            }

            return [ key, opMap[op], val ];
          });

        return [...filters, ...special];
      }, []);

    dsQuery = filters.reduce((q, filter) => q.filter(...filter), dsQuery);

    return dsQuery.run()
      .then(([ e ]) => e)
      .then(entity => this.entityToPlain(entity, true))
      .then(data => {
        if (ancestor) {
          return data.filter(({ id }) => id !== ancestor);
        }

        return data;
      });
  }

  _remove(id, params) {
    const get = Datastore.prototype._get.bind(this),
          find = Datastore.prototype._find.bind(this);

    return Promise.resolve()
      .then(() => id ? get(id, params) : find(params))
      .then(results => {
        let keys;

        if (results === null) {
          return null;
        }

        if (Array.isArray(results)) {
          keys = results.map(({ [ this.id ]: id }) => this.makeKey(id, params));
        } else {
          keys = this.makeKey(results[ this.id ], params);
        }

        return this.store.delete(keys)
          .then(() => results);
      });
  }

  makeKey(id, params = {}) {
    let { query = {} } = params,
        key;

    if (Array.isArray(id) || typeof id === 'object') {
      key = this.store.key(id);
    } else {
      // Try fetching a number
      let idAsNum = parseInt(id);
      if (!isNaN(idAsNum)) {
        id = idAsNum;
      }

      key = this.store.key([ this.kind, id ]);
    }

    if (query.namespace) {
      key.namespace = query.namespace;
    }

    return key;
  }

  entityToPlain(entity, alreadyFlat = false) {
    const ID_PROP = this.id;
    let data;

    if (Array.isArray(entity)) {
      return entity.map(e => this.entityToPlain(e, alreadyFlat));
    }

    if (!entity) {
      return entity;
    }

    data = alreadyFlat ? entity : entity.data;

    if (Array.isArray(data)) {
      // In explicit syntax, should deconstruct
      data = data.reduce((flat, { name, value }) => {
        flat[name] = value;
        return flat;
      }, {});
    }

    return Object.assign({}, data, { [ ID_PROP ]: Datastore.getKey(entity).path.slice(-1)[0] });
  }

  makeExplicitEntity(entity, options = {}) {
    const { dontIndex = [], autoIndex = this.autoIndex } = options.query || {};

    function isBig(value) {
      let valueType = typeof value;

      if (valueType === 'string' || value instanceof Buffer) {
        return Buffer.from(value).length > MAX_INDEX_SIZE;
      } else if (valueType === 'object' && value !== null) {
        // Must be an object, recursively build response
        return Object.keys(value)
          .map(key => value[key])
          .some(isBig);
      }

      // Number, boolean, undefined and null we either don't care about or are
      //  guaranteed < 1500
      return false;
    }

    function expandData(data) {
      const toBasicResponse = (name) => ({ name, value: data[name] }),
            addExclusions = (response) => {
              if (dontIndex.includes(response.name)) {
                response.excludeFromIndexes = true;
              } else if (autoIndex) {
                response.excludeFromIndexes = isBig(response.value);
              }

              return response;
            };

      return Object.keys(data)
        .map(toBasicResponse)
        .map(addExclusions);
    }

    if (Array.isArray(entity)) {
      return entity.map((e) => this.makeExplicitEntity(e, options), this);
    }

    return {
      key: Datastore.getKey(entity),
      data: expandData(entity.data)
    };
  }

  static getKey(entity) {
    return entity[datastore.KEY] || entity.key;
  }
}

export default function init(options) {
  debug('Initializing feathers-datastore plugin');
  return new Datastore(options);
}

init.Service = Datastore;
<commit_msg>fixed 1 sized array param being unboxed<commit_after>import datastore from '@google-cloud/datastore';
import makeDebug from 'debug';
import Proto from 'uberproto';
import { NotFound } from 'feathers-errors';
const debug = makeDebug('feathers-datastore');

const MAX_INDEX_SIZE = 1500;

class Datastore {
  constructor(options = {}) {
    const datastoreOpt = { projectId: options.projectId };

    if (options.credentials) {
      datastoreOpt.credentials = options.credentials;
    }
    this.store = datastore(datastoreOpt);

    this.id = options.id || 'id';
    this.kind = options.kind;
    this.events = options.events;
    this.autoIndex = options.autoIndex || false;

    // NOTE: This isn't nice, but it's the only way to give internal methods full
    //  unrestricted (no hooks) access to all methods
    [
      'find',
      'get',
      'create',
      'update',
      'patch',
      'remove'
    ].forEach(method => {
      this[method] = (...args) => this[`_${method}`](...args);
    });
  }

  extend(obj) {
    return Proto.extend(obj, this);
  }

  _get(id, params) {
    let key = this.makeKey(id, params);
    return this.store.get(key)
      .then(([ entity ]) => entity)
      .then(entity => this.entityToPlain(entity, true))
      .then(entity => {
        if (!entity) {
          throw new NotFound(`No record found for id '${id}'`);
        }

        return entity;
      });
  }

  _create(data, params = {}) {
    let entities = [],
        key,
        wasArray;

    wasArray = Array.isArray(data);
    data = wasArray ? data : [data];

    data.map(item => {
      if (item.hasOwnProperty(this.id)) {
        key = this.makeKey(item[this.id], params);
      } else {
        key = this.makeKey(undefined, params);
      }
      entities.push({
        key,
        data: item,
      });
    });

    // unbox if data param was an object
    if (!wasArray) {
      entities = entities[0];
    }

    // Convert entities to explicit format, to allow for indexing
    entities = this.makeExplicitEntity(entities, params);

    return this.store.insert(entities)
      .then(() => entities)
      .then(entity => this.entityToPlain(entity));
  }

  _update(id, data, params = {}) {
    let key = this.makeKey(id, params),
        entity = { key, data },
        { query = {} } = params,
        method = query.create ? 'upsert' : 'update';

    entity = this.makeExplicitEntity(entity, params);

    return this.store[method](entity)
      .then(() => entity)
      .then(entity => this.entityToPlain(entity))
      .catch(err => {
        // NOTE: Updating a not found entity will result in a bad request, rather than
        //  a not found, this gets around that, though in future should be made more
        //  secure
        if (err.code === 400 && err.message === 'no entity to update') {
          throw new NotFound(`No record found for id \'${id}'`);
        }

        throw err;
      });
  }

  _patch(id, data, params) {
    const get = Datastore.prototype._get.bind(this),
          find = Datastore.prototype._find.bind(this);

    return Promise.resolve()
      .then(() => id ? get(id, params) : find(params))
      .then(results => {
        let entities,
            makeNewEntity = (current, update) => {
              return {
                key: this.makeKey(current[ this.id ], params),
                data: Object.assign({}, current, update)
              };
            };

        if (Array.isArray(results)) {
          entities = results.map(current => makeNewEntity(current, data));
        } else {
          entities = makeNewEntity(results, data);
        }

        entities = this.makeExplicitEntity(entities, params);

        return this.store.update(entities)
          .then(() => entities);
      })
      .then(entity => this.entityToPlain(entity));
  }

  _find(params = {}) {
    params.query = params.query || {};

    let { ancestor, namespace, kind = this.kind, ...query } = params.query,
        dsQuery = this.store.createQuery(namespace, kind),
        filters;

    if (ancestor) {
      let ancestorKey = this.makeKey(ancestor, params);
      dsQuery = dsQuery.hasAncestor(ancestorKey);
    }

    filters = Object.entries(query)
      .reduce((filters, [key, value]) => {
        const opMap = {
          $gt: '>',
          $gte: '>=',
          $lt: '<',
          $lte: '<=',
          '=': '='
        };

        let special;

        // Normalize
        if (typeof value !== 'object' || value instanceof this.store.key().constructor || value === null) {
          value = { '=': value };
        }

        special = Object.entries(value)
          .filter(([ op ]) => opMap[op])
          .map(([ op, val ]) => {
            // Try convert it into a number
            if (typeof val === 'string') {
              let valAsNum = parseFloat(val);

              if (!isNaN(valAsNum)) {
                // Its a number, assign it to original val
                val = valAsNum;
              }
            }

            return [ key, opMap[op], val ];
          });

        return [...filters, ...special];
      }, []);

    dsQuery = filters.reduce((q, filter) => q.filter(...filter), dsQuery);

    return dsQuery.run()
      .then(([ e ]) => e)
      .then(entity => this.entityToPlain(entity, true))
      .then(data => {
        if (ancestor) {
          return data.filter(({ id }) => id !== ancestor);
        }

        return data;
      });
  }

  _remove(id, params) {
    const get = Datastore.prototype._get.bind(this),
          find = Datastore.prototype._find.bind(this);

    return Promise.resolve()
      .then(() => id ? get(id, params) : find(params))
      .then(results => {
        let keys;

        if (results === null) {
          return null;
        }

        if (Array.isArray(results)) {
          keys = results.map(({ [ this.id ]: id }) => this.makeKey(id, params));
        } else {
          keys = this.makeKey(results[ this.id ], params);
        }

        return this.store.delete(keys)
          .then(() => results);
      });
  }

  makeKey(id, params = {}) {
    let { query = {} } = params,
        key;

    if (Array.isArray(id) || typeof id === 'object') {
      key = this.store.key(id);
    } else {
      // Try fetching a number
      let idAsNum = parseInt(id);
      if (!isNaN(idAsNum)) {
        id = idAsNum;
      }

      key = this.store.key([ this.kind, id ]);
    }

    if (query.namespace) {
      key.namespace = query.namespace;
    }

    return key;
  }

  entityToPlain(entity, alreadyFlat = false) {
    const ID_PROP = this.id;
    let data;

    if (Array.isArray(entity)) {
      return entity.map(e => this.entityToPlain(e, alreadyFlat));
    }

    if (!entity) {
      return entity;
    }

    data = alreadyFlat ? entity : entity.data;

    if (Array.isArray(data)) {
      // In explicit syntax, should deconstruct
      data = data.reduce((flat, { name, value }) => {
        flat[name] = value;
        return flat;
      }, {});
    }

    return Object.assign({}, data, { [ ID_PROP ]: Datastore.getKey(entity).path.slice(-1)[0] });
  }

  makeExplicitEntity(entity, options = {}) {
    const { dontIndex = [], autoIndex = this.autoIndex } = options.query || {};

    function isBig(value) {
      let valueType = typeof value;

      if (valueType === 'string' || value instanceof Buffer) {
        return Buffer.from(value).length > MAX_INDEX_SIZE;
      } else if (valueType === 'object' && value !== null) {
        // Must be an object, recursively build response
        return Object.keys(value)
          .map(key => value[key])
          .some(isBig);
      }

      // Number, boolean, undefined and null we either don't care about or are
      //  guaranteed < 1500
      return false;
    }

    function expandData(data) {
      const toBasicResponse = (name) => ({ name, value: data[name] }),
            addExclusions = (response) => {
              if (dontIndex.includes(response.name)) {
                response.excludeFromIndexes = true;
              } else if (autoIndex) {
                response.excludeFromIndexes = isBig(response.value);
              }

              return response;
            };

      return Object.keys(data)
        .map(toBasicResponse)
        .map(addExclusions);
    }

    if (Array.isArray(entity)) {
      return entity.map((e) => this.makeExplicitEntity(e, options), this);
    }

    return {
      key: Datastore.getKey(entity),
      data: expandData(entity.data)
    };
  }

  static getKey(entity) {
    return entity[datastore.KEY] || entity.key;
  }
}

export default function init(options) {
  debug('Initializing feathers-datastore plugin');
  return new Datastore(options);
}

init.Service = Datastore;
<|endoftext|>


<commit_before>Blackjack.Utils = {
    score: function(cards) {
        var sum = 0, ace;

        for (var i = 0, value; i < cards.length; i+=1) {
            if (cards[i].rank === 'J' || cards[i].rank === 'Q' || cards[i].rank === 'K') {
                value = 10;
            } else if (cards[i].rank === 'A') {
                value = 1;
                ace = true;
            } else {
                value = parseInt(cards[i].rank);
            }

            sum += value;
        }

        if (ace && sum < 12) {
            sum += 10;
        }

        return sum;
    }
}<commit_msg>Add some documentation for the utils library<commit_after>/**
 * A utility library for all related blackjack functions.
 *
 * This source code is licensed under the MIT-style license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * Author: Chris Zieba <zieba.chris@gmail.com>
 */

'use strict';

var Blackjack = Blackjack || {};

Blackjack.Utils = {
    /**
     * Calculates the score total of a blackjack hand.
     * An ace is treated as 11 until the score is above
     * 21 then it is used as a 1 instead. Returns an
     * integer value of the score of the hand.
     *
     * @param {Array} cards
     * @return {Integer} sum
     */
    score: function(cards) {
        var sum = 0;

        // A flag to determine whether the hand has an ace
        var ace;

        for (var i = 0, value; i < cards.length; i+=1) {
            if (cards[i].rank === 'J' || cards[i].rank === 'Q' || cards[i].rank === 'K') {
                value = 10;
            } else if (cards[i].rank === 'A') {
                value = 1;
                ace = true;
            } else {
                value = parseInt(cards[i].rank);
            }

            sum += value;
        }

        // Treat the ace as an 11 if the hand will not bust
        if (ace && sum < 12) {
            sum += 10;
        }

        return sum;
    }
}<|endoftext|>


<commit_before>import { getAllTestsInClass } from '../helpers/viewHelpers.js';

var objToArray = function(obj) {
  return Object.keys(obj).map(function(key) {
    return obj[key];
  });
};

exports.processData = function(classId) {
  return getAllTestsInClass(classId).then(function(allTestsFromClass) {
    var allTestData = {};
    // console.log('allTestsFromClass: ', allTestsFromClass);

    for (var key in allTestsFromClass.data[0].answerKeys) {
      allTestData[key] = {id: allTestsFromClass.data[0].answerKeys[key].keyId,
                          name: allTestsFromClass.data[0].answerKeys[key].keyName,  // This will be name in the future.
                          correctAnswers: objToArray(JSON.parse(allTestsFromClass.data[0].answerKeys[key].answers)),
                          studentTestResults: [],
                          studentTestScores: []
                         };
    };

    allTestsFromClass.data[0].students.forEach(function(student) {
      var studentId = student.StudentId;
      var studentName = student.StudentName;

      student.tests.forEach(function(item) {
        var test = {};
        // test.testId = item.id;
        test.student = {id: studentId, name: studentName};
        test.studentScore = Math.round(item.result * 100);
        test.studentAnswers = objToArray(JSON.parse(item.studentAnswers));
        test.imageUrl = item.URL;
        test.answerKeyId = item.answerKeyId;

        for (var key in allTestData) {
          if (Number(allTestData[key].id) === Number(test.answerKeyId)) {
            allTestData[key].studentTestResults.push(test);
            allTestData[key].studentTestScores.push(test.studentScore);
          }
        }
      });
    });
    // console.log('Final allTestData object after processing: ', allTestData);
    return allTestData;
  });
};

exports.prepStudentAnswersForTest = function(allTestData) {
  console.log('Im Inside prepStudentAnswersForTest and allTestData is: ', allTestData);
  return allTestData[0].studentTestResults.map(function(item) {
    return item.studentAnswers;
  });
}

// exports.getCorrectAnswerForTestQuestion = function(allTestData, testId, questionOfInterest) {
//   //        indexOfTestInfo
//   console.log('1: ', gaussData[testId - 1]);
//   console.log('2: ', gaussData[testId - 1].correctAnswers);
//   console.log('3: ', gaussData[testId - 1].correctAnswers[questionOfInterest - 1]);
//   return allTestData[testId - 1].correctAnswers[questionOfInterest - 1][0];
// }

exports.responseFrequency = function(studentAnswersForTest, questionOfInterest) {
  // console.log('studentAnswersForTest: ', studentAnswersForTest);

  var totalFrequencyCount = [];
  for (var i = 0; i < studentAnswersForTest[0].length; i++) {
    var singleFrequencyCount = [
      {name: 'A', value: 0},
      {name: 'B', value: 0},
      {name: 'C', value: 0},
      {name: 'D', value: 0},
      {name: 'E', value: 0}
    ];
    totalFrequencyCount[i] = singleFrequencyCount;
  }

  studentAnswersForTest.forEach(function(answerSet) {
    answerSet.forEach(function(answer, index) {
      if (answer[0] === 'a') {         // Maybe use .contains here instead for questions with more than one answer?
        totalFrequencyCount[index][0].value++;
      } else if (answer[0] === 'b') {  // Maybe use .contains here instead for questions with more than one answer?
        totalFrequencyCount[index][1].value++;
      } else if (answer[0] === 'c') {  // Maybe use .contains here instead for questions with more than one answer?
        totalFrequencyCount[index][2].value++;
      } else if (answer[0] === 'd') {  // Maybe use .contains here instead for questions with more than one answer?
        totalFrequencyCount[index][3].value++;
      } else if (answer[0] === 'e') {  // Maybe use .contains here instead for questions with more than one answer?
        totalFrequencyCount[index][4].value++;
      }
    });
  });
    // console.log('------', totalFrequencyCount);
  return totalFrequencyCount[questionOfInterest - 1];
}

// // --------------------------------------------------------------------------------
// // Formulas for Statistical Variables:
// // --------------------------------------------------------------------------------

// Sum
exports.sum = (arrayOfGrades) => d3.sum(arrayOfGrades, 'Accessor Function Here!');

// Mean
exports.mean = (arrayOfGrades) => d3.mean(arrayOfGrades, 'Accessor Function Here!');

// Median
exports.median = (arrayOfGrades) => d3.median(arrayOfGrades, 'Accessor Function Here!');

// Standard Deviation
exports.stdDev = (arrayOfGrades) => d3.deviation(arrayOfGrades, 'Acessore Function Here!');

// Variance
exports.variance = (arrayOfGrades) => d3.variance(arrayOfGrades, 'Acessore Function Here!');

// Quantiles, where p is a number in the range [0, 1].
exports.quantile = (arrayOfGrades) => d3.quantile(arrayOfGrades, p, 'Acessore Function Here!');
// Example: p = 0.25 is the first quantile, Q1.
//          p = 0.75 is the third quantile, Q3.

// Test Item Difficulty
exports.singleItemDifficulty = function(allTestData, testId, responseFrequency, studentAnswersForEntireTest, questionOfInterest) {
  // Assume item is worth 1 point.
  var fullScoreForItem = 1;
  // var fullScoreForItem = (1 / studentAnswersForEntireTest[0].length) * 100;
  console.log('Question of Interest: ', questionOfInterest);
  var correctChoice = allTestData[testId - 1].correctAnswers[questionOfInterest - 1][0];
  console.log('Correct Choice: ', correctChoice);

  var numIncorrect = responseFrequency.reduce(function(acc, curr) {
    if (curr.name.toLowerCase() !== correctChoice) {
      acc += curr.value;
    }
    return acc;
  }, 0);
  // console.log('Number Incorrect: ', numIncorrect);

  // Replace studentAnswersForEntireTest with responseFrequency!!!!
  var averageScoreForItem = (studentAnswersForEntireTest.length - numIncorrect) / studentAnswersForEntireTest.length;
  // console.log('averageScoreForItem: ', averageScoreForItem);
  var itemDifficulty = averageScoreForItem / fullScoreForItem;
  console.log('itemDifficulty #', questionOfInterest, ': ', itemDifficulty);

  return itemDifficulty;
}

const testDifficulty = function(allTestData, testId, studentAnswersForEntireTest) {
      var fullScoreForItem = 1;
      var sumSingleItems = 0;
      var totalPointsForTest = 28;
      console.log('studentAnswersForEntireTest: ', studentAnswersForEntireTest);

      for (var i = 1; i < studentAnswersForEntireTest[0].length; i++) {
        var respFreq2 = responseFrequency(studentAnswersForEntireTest, i);
        sumSingleItems += singleItemDifficulty(allTestData, testId, respFreq2, studentAnswersForEntireTest, i) * fullScoreForItem;
      }

      var totalDifficulty = (1 / totalPointsForTest) * sumSingleItems;

      return totalDifficulty;
    }

// Reliability Coeffecient (Chronbach's Alpha)
exports.alpha = (k, arrayOfArraysOfStudentAnswers, arrayOfGrades) => {'sttuffff'};
// alpha = (k / (k - 1)) * (1 - (sum( sigma2(arrayOfGradesForSignleTestItem))) / sigma2(arrayOfExamGrades) )

<commit_msg>(cleanup) Minor cleanup (WIP)<commit_after>import { getAllTestsInClass } from '../helpers/viewHelpers.js';

var objToArray = function(obj) {
  return Object.keys(obj).map(function(key) {
    return obj[key];
  });
};

exports.processData = function(classId) {
  return getAllTestsInClass(classId).then(function(allTestsFromClass) {
    var allTestData = {};
    // console.log('allTestsFromClass: ', allTestsFromClass);

    for (var key in allTestsFromClass.data[0].answerKeys) {
      allTestData[key] = {id: allTestsFromClass.data[0].answerKeys[key].keyId,
                          name: allTestsFromClass.data[0].answerKeys[key].keyName,  // This will be name in the future.
                          correctAnswers: objToArray(JSON.parse(allTestsFromClass.data[0].answerKeys[key].answers)),
                          studentTestResults: [],
                          studentTestScores: []
                         };
    };

    allTestsFromClass.data[0].students.forEach(function(student) {
      var studentId = student.StudentId;
      var studentName = student.StudentName;

      student.tests.forEach(function(item) {
        var test = {};
        // test.testId = item.id;
        test.student = {id: studentId, name: studentName};
        test.studentScore = Math.round(item.result * 100);
        test.studentAnswers = objToArray(JSON.parse(item.studentAnswers));
        test.imageUrl = item.URL;
        test.answerKeyId = item.answerKeyId;

        for (var key in allTestData) {
          if (Number(allTestData[key].id) === Number(test.answerKeyId)) {
            allTestData[key].studentTestResults.push(test);
            allTestData[key].studentTestScores.push(test.studentScore);
          }
        }
      });
    });
    // console.log('Final allTestData object after processing: ', allTestData);
    return allTestData;
  });
};

exports.prepStudentAnswersForTest = function(allTestData) {
  console.log('Im Inside prepStudentAnswersForTest and allTestData is: ', allTestData);
  return allTestData[0].studentTestResults.map(function(item) {
    return item.studentAnswers;
  });
}

// exports.getCorrectAnswerForTestQuestion = function(allTestData, testId, questionOfInterest) {
//   //        indexOfTestInfo
//   console.log('1: ', gaussData[testId - 1]);
//   console.log('2: ', gaussData[testId - 1].correctAnswers);
//   console.log('3: ', gaussData[testId - 1].correctAnswers[questionOfInterest - 1]);
//   return allTestData[testId - 1].correctAnswers[questionOfInterest - 1][0];
// }

exports.responseFrequency = function(studentAnswersForTest, questionOfInterest) {
  // console.log('studentAnswersForTest: ', studentAnswersForTest);

  var totalFrequencyCount = [];
  for (var i = 0; i < studentAnswersForTest[0].length; i++) {
    var singleFrequencyCount = [
      {name: 'A', value: 0},
      {name: 'B', value: 0},
      {name: 'C', value: 0},
      {name: 'D', value: 0},
      {name: 'E', value: 0}
    ];
    totalFrequencyCount[i] = singleFrequencyCount;
  }

  studentAnswersForTest.forEach(function(answerSet) {
    answerSet.forEach(function(answer, index) {
      if (answer[0] === 'a') {         // Maybe use .contains here instead for questions with more than one answer?
        totalFrequencyCount[index][0].value++;
      } else if (answer[0] === 'b') {  // Maybe use .contains here instead for questions with more than one answer?
        totalFrequencyCount[index][1].value++;
      } else if (answer[0] === 'c') {  // Maybe use .contains here instead for questions with more than one answer?
        totalFrequencyCount[index][2].value++;
      } else if (answer[0] === 'd') {  // Maybe use .contains here instead for questions with more than one answer?
        totalFrequencyCount[index][3].value++;
      } else if (answer[0] === 'e') {  // Maybe use .contains here instead for questions with more than one answer?
        totalFrequencyCount[index][4].value++;
      }
    });
  });
    // console.log('------', totalFrequencyCount);
  return totalFrequencyCount[questionOfInterest - 1];
}

// // --------------------------------------------------------------------------------
// // Formulas for Statistical Variables:
// // --------------------------------------------------------------------------------

// Sum
exports.sum = (arrayOfGrades) => d3.sum(arrayOfGrades, 'Accessor Function Here!');

// Mean
exports.mean = (arrayOfGrades) => d3.mean(arrayOfGrades, 'Accessor Function Here!');

// Median
exports.median = (arrayOfGrades) => d3.median(arrayOfGrades, 'Accessor Function Here!');

// Standard Deviation
exports.stdDev = (arrayOfGrades) => d3.deviation(arrayOfGrades, 'Acessore Function Here!');

// Variance
exports.variance = (arrayOfGrades) => d3.variance(arrayOfGrades, 'Acessore Function Here!');

// Quantiles, where p is a number in the range [0, 1].
exports.quantile = (arrayOfGrades) => d3.quantile(arrayOfGrades, p, 'Acessore Function Here!');
// Example: p = 0.25 is the first quantile, Q1.
//          p = 0.75 is the third quantile, Q3.

// Test Item Difficulty
exports.singleItemDifficulty = function(allTestData, testId, responseFrequency, studentAnswersForEntireTest, questionOfInterest) {
  // Assume item is worth 1 point.
  var fullScoreForItem = 1;
  // var fullScoreForItem = (1 / studentAnswersForEntireTest[0].length) * 100;
  console.log('Question of Interest: ', questionOfInterest);
  var correctChoice = allTestData[testId - 1].correctAnswers[questionOfInterest - 1][0];
  console.log('Correct Choice: ', correctChoice);

  var numIncorrect = responseFrequency.reduce(function(acc, curr) {
    if (curr.name.toLowerCase() !== correctChoice) {
      acc += curr.value;
    }
    return acc;
  }, 0);
  // console.log('Number Incorrect: ', numIncorrect);

  // Replace studentAnswersForEntireTest with responseFrequency!!!!
  var averageScoreForItem = (studentAnswersForEntireTest.length - numIncorrect) / studentAnswersForEntireTest.length;
  // console.log('averageScoreForItem: ', averageScoreForItem);
  var itemDifficulty = averageScoreForItem / fullScoreForItem;
  console.log('itemDifficulty #', questionOfInterest, ': ', itemDifficulty);

  return itemDifficulty;
}

const testDifficulty = function(allTestData, testId, studentAnswersForEntireTest) {
  var fullScoreForItem = 1;
  var sumSingleItems = 0;
  var totalPointsForTest = 28;
  console.log('studentAnswersForEntireTest: ', studentAnswersForEntireTest);

  for (var i = 1; i < studentAnswersForEntireTest[0].length; i++) {
    var respFreq2 = responseFrequency(studentAnswersForEntireTest, i);
    sumSingleItems += singleItemDifficulty(allTestData, testId, respFreq2, studentAnswersForEntireTest, i) * fullScoreForItem;
  }

  var totalDifficulty = (1 / totalPointsForTest) * sumSingleItems;

  return totalDifficulty;
}

// Reliability Coeffecient (Chronbach's Alpha)
exports.alpha = (k, arrayOfArraysOfStudentAnswers, arrayOfGrades) => {'sttuffff'};
// alpha = (k / (k - 1)) * (1 - (sum( sigma2(arrayOfGradesForSignleTestItem))) / sigma2(arrayOfExamGrades) )

<|endoftext|>