<commit_before>import React from "react"
import _ from "lodash"
import { VictoryChart, VictoryBar, VictoryAxis, VictoryStack } from "victory"
import { VictoryLabel, ClipPath } from "victory"

// Helper to selectively show labels in Victory charts
export class ConditionalLabel extends React.Component {
  render() {
    const { testFn, ...passThroughProps } = this.props
    if (testFn(this.props)) {
      return <VictoryLabel {...passThroughProps} />
    }
    return null
  }
}

export class AnalyticsChart extends React.Component {
  props: {
    analyticsData: VideoAnalyticsData,
    getColorForChannel: Function,
    currentTime: number,
    style?: { [string]: mixed }
  }

  constructor(props) {
    super(props)
    this._namespace = Math.floor(1e6 * Math.random()) // used for ids in svg
    this.state = {
      dimensions: null
    }
  }
  componentDidMount() {
    this._setupResizeHandler()
    setTimeout(() => this._updateDimensions(), 200)
  }

  _setupResizeHandler() {
    this._resizeHandler = _.throttle(this.onResize.bind(this), 100)
    window.addEventListener("resize", this._resizeHandler)
  }

  onResize() {
    this._updateDimensions()
  }

  _tearDownResizeHandler() {
    window.removeEventListener("resize", this._resizeHandler)
  }

  _updateDimensions() {
    if (!this.rootRef) {
      return
    }
    const { width, height } = this.rootRef.getBoundingClientRect()
    this.setState({ dimensions: { width, height } })
  }

  componentWillUnmount() {
    this._tearDownResizeHandler()
  }

  render() {
    const { dimensions } = this.state
    const passThroughProps = _.omit(this.props, [
      "analyticsData",
      "currentTime",
      "duration",
      "getColorForChannel",
      "padding",
      "setVideoTime"
    ])
    return (
      <div
        ref={ref => {
          this.rootRef = ref
        }}
        {...passThroughProps}
      >
        {dimensions ? this.renderChart() : null}
      </div>
    )
  }

  renderChart() {
    const { dimensions } = this.state
    const { analyticsData, duration, getColorForChannel, padding } = this.props
    const viewsAtTimesByChannel = this._generateViewsAtTimesByChannel(
      analyticsData)
    const baseLabelStyle = {
      fill:       "#666",
      fontFamily: "'Roboto', 'sans-serif'",
      fontSize:   10
    }
    const chartBodyClipPathId = `${this._namespace}-chart-body-clipPath`
    const chartBodyBounds = this._getRelativeChartBodyBounds()
    return (
      <VictoryChart
        {...dimensions}
        domain={{ x: [0, duration / 60 || 1] }}
        domainPadding={{ x: [0, 0], y: [0, 0] }}
        padding={padding}
      >
        <VictoryAxis
          tickValues={analyticsData.times}
          tickFormat={(t) => {
            return `${t}m`
          }}
          style={{
            axis: {
              stroke: "black"
            },
            ticks: {
              stroke: "black",
              size:   2
            },
            tickLabels: {
              ...baseLabelStyle,
              textAnchor: "middle",
              padding:    2
            }
          }}
          crossAxis={true}
          tickLabelComponent={
            <ConditionalLabel
              testFn={props => {
                return props.datum % 5 === 0
              }}
            />
          }
        />
        <VictoryAxis
          dependentAxis
          label="views"
          offsetX={padding.left - 2}
          style={{
            axis: {
              padding: 100
            },
            axisLabel: {
              ...baseLabelStyle
            },
            grid: {
              stroke: "#eee"
            },
            ticks: {
              stroke: "black",
              size:   2
            },
            tickLabels: {
              ...baseLabelStyle,
              padding: 4
            }
          }}
          tickCount={10}
          tickLabelComponent={
            <ConditionalLabel
              testFn={props => {
                return props.index % 2 !== 0
              }}
            />
          }
        />

        <ClipPath clipId={chartBodyClipPathId}>
          <rect {...chartBodyBounds} />
        </ClipPath>

        <VictoryStack
          groupComponent={
            <g
              clipPath={`url(#${chartBodyClipPathId})`}
              className="chart-body"
            />
          }
        >
          {analyticsData.channels.map((channel, i) => {
            return (
              <VictoryBar
                key={i}
                data={viewsAtTimesByChannel[channel]}
                alignment="start"
                barRatio={1.0}
                x="time"
                y="views"
                style={{
                  data: {
                    fill: getColorForChannel(channel)
                  }
                }}
              />
            )
          })}
        </VictoryStack>
      </VictoryChart>
    )
  }

  _getRelativeChartBodyBounds() {
    const padding = this.props.padding
    const { dimensions } = this.state
    if (!dimensions) {
      return null
    }
    return {
      x:      padding.left,
      y:      padding.top,
      width:  dimensions.width - (padding.left + padding.right),
      height: dimensions.height - (padding.top + padding.bottom)
    }
  }

  _generateViewsAtTimesByChannel(analyticsData) {
    const viewsAtTimesByChannel = {}
    for (const channel of analyticsData.channels) {
      viewsAtTimesByChannel[channel] = []
    }
    for (const time of analyticsData.times) {
      const viewsAtTime = analyticsData.views_at_times[time] || {}
      for (const channel of analyticsData.channels) {
        viewsAtTimesByChannel[channel].push({
          time:  time,
          views: viewsAtTime[channel] || 0
        })
      }
    }
    return viewsAtTimesByChannel
  }
}

export default AnalyticsChart
<commit_msg>add additional empty check when rendering analytics chart<commit_after>import React from "react"
import _ from "lodash"
import { VictoryChart, VictoryBar, VictoryAxis, VictoryStack } from "victory"
import { VictoryLabel, ClipPath } from "victory"

// Helper to selectively show labels in Victory charts
export class ConditionalLabel extends React.Component {
  render() {
    const { testFn, ...passThroughProps } = this.props
    if (testFn(this.props)) {
      return <VictoryLabel {...passThroughProps} />
    }
    return null
  }
}

export class AnalyticsChart extends React.Component {
  props: {
    analyticsData: VideoAnalyticsData,
    getColorForChannel: Function,
    currentTime: number,
    style?: { [string]: mixed }
  }

  constructor(props) {
    super(props)
    this._namespace = Math.floor(1e6 * Math.random()) // used for ids in svg
    this.state = {
      dimensions: null
    }
  }
  componentDidMount() {
    this._setupResizeHandler()
    setTimeout(() => this._updateDimensions(), 200)
  }

  _setupResizeHandler() {
    this._resizeHandler = _.throttle(this.onResize.bind(this), 100)
    window.addEventListener("resize", this._resizeHandler)
  }

  onResize() {
    this._updateDimensions()
  }

  _tearDownResizeHandler() {
    window.removeEventListener("resize", this._resizeHandler)
  }

  _updateDimensions() {
    if (!this.rootRef) {
      return
    }
    const { width, height } = this.rootRef.getBoundingClientRect()
    this.setState({ dimensions: { width, height } })
  }

  componentWillUnmount() {
    this._tearDownResizeHandler()
  }

  render() {
    const { dimensions } = this.state
    const passThroughProps = _.omit(this.props, [
      "analyticsData",
      "currentTime",
      "duration",
      "getColorForChannel",
      "padding",
      "setVideoTime"
    ])
    return (
      <div
        ref={ref => {
          this.rootRef = ref
        }}
        {...passThroughProps}
      >
        {dimensions ? this.renderChart() : null}
      </div>
    )
  }

  renderChart() {
    const { dimensions } = this.state
    const { analyticsData, duration, getColorForChannel, padding } = this.props
    const viewsAtTimesByChannel = this._generateViewsAtTimesByChannel(
      analyticsData)
    const baseLabelStyle = {
      fill:       "#666",
      fontFamily: "'Roboto', 'sans-serif'",
      fontSize:   10
    }
    const chartBodyClipPathId = `${this._namespace}-chart-body-clipPath`
    const chartBodyBounds = this._getRelativeChartBodyBounds()
    return (
      <VictoryChart
        {...dimensions}
        domain={{ x: [0, duration / 60 || 1] }}
        domainPadding={{ x: [0, 0], y: [0, 0] }}
        padding={padding}
      >
        <VictoryAxis
          tickValues={analyticsData.times}
          tickFormat={(t) => {
            return `${t}m`
          }}
          style={{
            axis: {
              stroke: "black"
            },
            ticks: {
              stroke: "black",
              size:   2
            },
            tickLabels: {
              ...baseLabelStyle,
              textAnchor: "middle",
              padding:    2
            }
          }}
          crossAxis={true}
          tickLabelComponent={
            <ConditionalLabel
              testFn={props => {
                return props.datum % 5 === 0
              }}
            />
          }
        />
        <VictoryAxis
          dependentAxis
          label="views"
          offsetX={padding.left - 2}
          style={{
            axis: {
              padding: 100
            },
            axisLabel: {
              ...baseLabelStyle
            },
            grid: {
              stroke: "#eee"
            },
            ticks: {
              stroke: "black",
              size:   2
            },
            tickLabels: {
              ...baseLabelStyle,
              padding: 4
            }
          }}
          tickCount={10}
          tickLabelComponent={
            <ConditionalLabel
              testFn={props => {
                return props.index % 2 !== 0
              }}
            />
          }
        />

        <ClipPath clipId={chartBodyClipPathId}>
          <rect {...chartBodyBounds} />
        </ClipPath>

        <VictoryStack
          groupComponent={
            <g
              clipPath={`url(#${chartBodyClipPathId})`}
              className="chart-body"
            />
          }
        >
          {analyticsData.channels.map((channel, i) => {
            return (
              <VictoryBar
                key={i}
                data={viewsAtTimesByChannel[channel]}
                alignment="start"
                barRatio={1.0}
                x="time"
                y="views"
                style={{
                  data: {
                    fill: getColorForChannel(channel)
                  }
                }}
              />
            )
          })}
        </VictoryStack>
      </VictoryChart>
    )
  }

  _getRelativeChartBodyBounds() {
    const padding = this.props.padding
    if (!padding) {
      return null
    }
    const { dimensions } = this.state
    if (!dimensions) {
      return null
    }
    return {
      x:      padding.left,
      y:      padding.top,
      width:  dimensions.width - (padding.left + padding.right),
      height: dimensions.height - (padding.top + padding.bottom)
    }
  }

  _generateViewsAtTimesByChannel(analyticsData) {
    const viewsAtTimesByChannel = {}
    for (const channel of analyticsData.channels) {
      viewsAtTimesByChannel[channel] = []
    }
    for (const time of analyticsData.times) {
      const viewsAtTime = analyticsData.views_at_times[time] || {}
      for (const channel of analyticsData.channels) {
        viewsAtTimesByChannel[channel].push({
          time:  time,
          views: viewsAtTime[channel] || 0
        })
      }
    }
    return viewsAtTimesByChannel
  }
}

export default AnalyticsChart
<|endoftext|>


<commit_before>import { resolve, join } from 'path'
import { createHash } from 'crypto'
import webpack from 'webpack'
import glob from 'glob-promise'
import WriteFilePlugin from 'write-file-webpack-plugin'
import FriendlyErrorsWebpackPlugin from 'friendly-errors-webpack-plugin'
import CaseSensitivePathPlugin from 'case-sensitive-paths-webpack-plugin'
import UnlinkFilePlugin from './plugins/unlink-file-plugin'
import WatchPagesPlugin from './plugins/watch-pages-plugin'
import JsonPagesPlugin from './plugins/json-pages-plugin'
import getConfig from '../config'
import * as babelCore from 'babel-core'
import findBabelConfigLocation from './babel/find-config-location'

const documentPage = join('pages', '_document.js')
const defaultPages = [
  '_error.js',
  '_document.js'
]
const nextPagesDir = join(__dirname, '..', '..', 'pages')
const nextNodeModulesDir = join(__dirname, '..', '..', '..', 'node_modules')
const interpolateNames = new Map(defaultPages.map((p) => {
  return [join(nextPagesDir, p), `dist/pages/${p}`]
}))

export default async function createCompiler (dir, { dev = false, quiet = false } = {}) {
  dir = resolve(dir)
  const config = getConfig(dir)
  const defaultEntries = dev
    ? [join(__dirname, '..', '..', 'client/webpack-hot-middleware-client')] : []
  const mainJS = dev
    ? require.resolve('../../client/next-dev') : require.resolve('../../client/next')

  let minChunks

  const entry = async () => {
    const entries = { 'main.js': mainJS }

    const pages = await glob('pages/**/*.js', { cwd: dir })
    for (const p of pages) {
      entries[join('bundles', p)] = [...defaultEntries, `./${p}?entry`]
    }

    for (const p of defaultPages) {
      const entryName = join('bundles', 'pages', p)
      if (!entries[entryName]) {
        entries[entryName] = [...defaultEntries, join(nextPagesDir, p) + '?entry']
      }
    }

    // calculate minChunks of CommonsChunkPlugin for later use
    minChunks = Math.max(2, pages.filter((p) => p !== documentPage).length)

    return entries
  }

  const plugins = [
    new webpack.LoaderOptionsPlugin({
      options: {
        context: dir,
        customInterpolateName (url, name, opts) {
          return interpolateNames.get(this.resourcePath) || url
        }
      }
    }),
    new WriteFilePlugin({
      exitOnErrors: false,
      log: false,
      // required not to cache removed files
      useHashIndex: false
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'commons',
      filename: 'commons.js',
      minChunks (module, count) {
        // NOTE: it depends on the fact that the entry funtion is always called
        // before applying CommonsChunkPlugin
        return count >= minChunks
      }
    }),
    new JsonPagesPlugin(),
    new CaseSensitivePathPlugin()
  ]

  if (dev) {
    plugins.push(
      new webpack.HotModuleReplacementPlugin(),
      new webpack.NoEmitOnErrorsPlugin(),
      new UnlinkFilePlugin(),
      new WatchPagesPlugin(dir)
    )
    if (!quiet) {
      plugins.push(new FriendlyErrorsWebpackPlugin())
    }
  } else {
    plugins.push(
      new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify('production')
      }),
      new webpack.optimize.UglifyJsPlugin({
        compress: { warnings: false },
        sourceMap: false
      })
    )
  }

  const nodePathList = (process.env.NODE_PATH || '')
    .split(process.platform === 'win32' ? ';' : ':')
    .filter((p) => !!p)

  const mainBabelOptions = {
    babelrc: true,
    cacheDirectory: true,
    sourceMaps: dev ? 'both' : false,
    presets: []
  }

  const configLocation = findBabelConfigLocation(dir)
  if (configLocation) {
    console.log(`> Using external babel configuration`)
    console.log(`> location: "${configLocation}"`)
  } else {
    mainBabelOptions.presets.push(require.resolve('./babel/preset'))
  }

  const rules = (dev ? [{
    test: /\.js(\?[^?]*)?$/,
    loader: 'hot-self-accept-loader',
    include: [
      join(dir, 'pages'),
      nextPagesDir
    ]
  }, {
    test: /\.js(\?[^?]*)?$/,
    loader: 'react-hot-loader/webpack',
    exclude: /node_modules/
  }] : [])
  .concat([{
    test: /\.json$/,
    loader: 'json-loader'
  }, {
    test: /\.(js|json)(\?[^?]*)?$/,
    loader: 'emit-file-loader',
    include: [dir, nextPagesDir],
    exclude (str) {
      return /node_modules/.test(str) && str.indexOf(nextPagesDir) !== 0
    },
    options: {
      name: 'dist/[path][name].[ext]',
      // By default, our babel config does not transpile ES2015 module syntax because
      // webpack knows how to handle them. (That's how it can do tree-shaking)
      // But Node.js doesn't know how to handle them. So, we have to transpile them here.
      transform ({ content, sourceMap, interpolatedName }) {
        // Only handle .js files
        if (!(/\.js$/.test(interpolatedName))) {
          return { content, sourceMap }
        }
        const babelRuntimePath = require.resolve('babel-runtime/package')
          .replace(/[\\/]package\.json$/, '')
        const transpiled = babelCore.transform(content, {
          presets: [require.resolve('babel-preset-es2015')],
          sourceMaps: dev ? 'both' : false,
          // Here we need to resolve all modules to the absolute paths.
          // Earlier we did it with the babel-preset.
          // But since we don't transpile ES2015 in the preset this is not resolving.
          // That's why we need to do it here.
          // See more: https://github.com/zeit/next.js/issues/951
          plugins: [
            [
              require.resolve('babel-plugin-module-resolver'),
              {
                alias: {
                  'babel-runtime': babelRuntimePath,
                  react: require.resolve('react'),
                  'react-dom': require.resolve('react-dom'),
                  'react-dom/server': require.resolve('react-dom/server'),
                  'next/link': require.resolve('../../lib/link'),
                  'next/prefetch': require.resolve('../../lib/prefetch'),
                  'next/css': require.resolve('../../lib/css'),
                  'next/head': require.resolve('../../lib/head'),
                  'next/document': require.resolve('../../server/document'),
                  'next/router': require.resolve('../../lib/router')
                }
              }
            ]
          ],
          inputSourceMap: sourceMap
        })

        return {
          content: transpiled.code,
          sourceMap: transpiled.map
        }
      }
    }
  }, {
    loader: 'babel-loader',
    include: nextPagesDir,
    exclude (str) {
      return /node_modules/.test(str) && str.indexOf(nextPagesDir) !== 0
    },
    options: {
      babelrc: false,
      cacheDirectory: true,
      sourceMaps: dev ? 'both' : false,
      presets: [require.resolve('./babel/preset')]
    }
  }, {
    test: /\.js(\?[^?]*)?$/,
    loader: 'babel-loader',
    include: [dir],
    exclude (str) {
      return /node_modules/.test(str)
    },
    options: mainBabelOptions
  }])

  let webpackConfig = {
    context: dir,
    entry,
    output: {
      path: join(dir, '.next'),
      filename: '[name]',
      libraryTarget: 'commonjs2',
      publicPath: '/_webpack/',
      strictModuleExceptionHandling: true,
      devtoolModuleFilenameTemplate ({ resourcePath }) {
        const hash = createHash('sha1')
        hash.update(Date.now() + '')
        const id = hash.digest('hex').slice(0, 7)

        // append hash id for cache busting
        return `webpack:///${resourcePath}?${id}`
      }
    },
    resolve: {
      modules: [
        nextNodeModulesDir,
        'node_modules',
        ...nodePathList
      ]
    },
    resolveLoader: {
      modules: [
        nextNodeModulesDir,
        'node_modules',
        join(__dirname, 'loaders'),
        ...nodePathList
      ]
    },
    plugins,
    module: {
      rules
    },
    devtool: dev ? 'inline-source-map' : false,
    performance: { hints: false }
  }

  if (config.webpack) {
    console.log('> Using "webpack" config function defined in next.config.js.')
    webpackConfig = await config.webpack(webpackConfig, { dev })
  }
  return webpack(webpackConfig)
}
<commit_msg>Add styled-jsx to babel plugins (#970)<commit_after>import { resolve, join } from 'path'
import { createHash } from 'crypto'
import webpack from 'webpack'
import glob from 'glob-promise'
import WriteFilePlugin from 'write-file-webpack-plugin'
import FriendlyErrorsWebpackPlugin from 'friendly-errors-webpack-plugin'
import CaseSensitivePathPlugin from 'case-sensitive-paths-webpack-plugin'
import UnlinkFilePlugin from './plugins/unlink-file-plugin'
import WatchPagesPlugin from './plugins/watch-pages-plugin'
import JsonPagesPlugin from './plugins/json-pages-plugin'
import getConfig from '../config'
import * as babelCore from 'babel-core'
import findBabelConfigLocation from './babel/find-config-location'

const documentPage = join('pages', '_document.js')
const defaultPages = [
  '_error.js',
  '_document.js'
]
const nextPagesDir = join(__dirname, '..', '..', 'pages')
const nextNodeModulesDir = join(__dirname, '..', '..', '..', 'node_modules')
const interpolateNames = new Map(defaultPages.map((p) => {
  return [join(nextPagesDir, p), `dist/pages/${p}`]
}))

export default async function createCompiler (dir, { dev = false, quiet = false } = {}) {
  dir = resolve(dir)
  const config = getConfig(dir)
  const defaultEntries = dev
    ? [join(__dirname, '..', '..', 'client/webpack-hot-middleware-client')] : []
  const mainJS = dev
    ? require.resolve('../../client/next-dev') : require.resolve('../../client/next')

  let minChunks

  const entry = async () => {
    const entries = { 'main.js': mainJS }

    const pages = await glob('pages/**/*.js', { cwd: dir })
    for (const p of pages) {
      entries[join('bundles', p)] = [...defaultEntries, `./${p}?entry`]
    }

    for (const p of defaultPages) {
      const entryName = join('bundles', 'pages', p)
      if (!entries[entryName]) {
        entries[entryName] = [...defaultEntries, join(nextPagesDir, p) + '?entry']
      }
    }

    // calculate minChunks of CommonsChunkPlugin for later use
    minChunks = Math.max(2, pages.filter((p) => p !== documentPage).length)

    return entries
  }

  const plugins = [
    new webpack.LoaderOptionsPlugin({
      options: {
        context: dir,
        customInterpolateName (url, name, opts) {
          return interpolateNames.get(this.resourcePath) || url
        }
      }
    }),
    new WriteFilePlugin({
      exitOnErrors: false,
      log: false,
      // required not to cache removed files
      useHashIndex: false
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'commons',
      filename: 'commons.js',
      minChunks (module, count) {
        // NOTE: it depends on the fact that the entry funtion is always called
        // before applying CommonsChunkPlugin
        return count >= minChunks
      }
    }),
    new JsonPagesPlugin(),
    new CaseSensitivePathPlugin()
  ]

  if (dev) {
    plugins.push(
      new webpack.HotModuleReplacementPlugin(),
      new webpack.NoEmitOnErrorsPlugin(),
      new UnlinkFilePlugin(),
      new WatchPagesPlugin(dir)
    )
    if (!quiet) {
      plugins.push(new FriendlyErrorsWebpackPlugin())
    }
  } else {
    plugins.push(
      new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify('production')
      }),
      new webpack.optimize.UglifyJsPlugin({
        compress: { warnings: false },
        sourceMap: false
      })
    )
  }

  const nodePathList = (process.env.NODE_PATH || '')
    .split(process.platform === 'win32' ? ';' : ':')
    .filter((p) => !!p)

  const mainBabelOptions = {
    babelrc: true,
    cacheDirectory: true,
    sourceMaps: dev ? 'both' : false,
    presets: []
  }

  const configLocation = findBabelConfigLocation(dir)
  if (configLocation) {
    console.log(`> Using external babel configuration`)
    console.log(`> location: "${configLocation}"`)
  } else {
    mainBabelOptions.presets.push(require.resolve('./babel/preset'))
  }

  const rules = (dev ? [{
    test: /\.js(\?[^?]*)?$/,
    loader: 'hot-self-accept-loader',
    include: [
      join(dir, 'pages'),
      nextPagesDir
    ]
  }, {
    test: /\.js(\?[^?]*)?$/,
    loader: 'react-hot-loader/webpack',
    exclude: /node_modules/
  }] : [])
  .concat([{
    test: /\.json$/,
    loader: 'json-loader'
  }, {
    test: /\.(js|json)(\?[^?]*)?$/,
    loader: 'emit-file-loader',
    include: [dir, nextPagesDir],
    exclude (str) {
      return /node_modules/.test(str) && str.indexOf(nextPagesDir) !== 0
    },
    options: {
      name: 'dist/[path][name].[ext]',
      // By default, our babel config does not transpile ES2015 module syntax because
      // webpack knows how to handle them. (That's how it can do tree-shaking)
      // But Node.js doesn't know how to handle them. So, we have to transpile them here.
      transform ({ content, sourceMap, interpolatedName }) {
        // Only handle .js files
        if (!(/\.js$/.test(interpolatedName))) {
          return { content, sourceMap }
        }
        const babelRuntimePath = require.resolve('babel-runtime/package')
          .replace(/[\\/]package\.json$/, '')
        const transpiled = babelCore.transform(content, {
          presets: [require.resolve('babel-preset-es2015')],
          sourceMaps: dev ? 'both' : false,
          // Here we need to resolve all modules to the absolute paths.
          // Earlier we did it with the babel-preset.
          // But since we don't transpile ES2015 in the preset this is not resolving.
          // That's why we need to do it here.
          // See more: https://github.com/zeit/next.js/issues/951
          plugins: [
            [
              require.resolve('babel-plugin-module-resolver'),
              {
                alias: {
                  'babel-runtime': babelRuntimePath,
                  react: require.resolve('react'),
                  'react-dom': require.resolve('react-dom'),
                  'react-dom/server': require.resolve('react-dom/server'),
                  'next/link': require.resolve('../../lib/link'),
                  'next/prefetch': require.resolve('../../lib/prefetch'),
                  'next/css': require.resolve('../../lib/css'),
                  'next/head': require.resolve('../../lib/head'),
                  'next/document': require.resolve('../../server/document'),
                  'next/router': require.resolve('../../lib/router'),
                  'styled-jsx/style': require.resolve('styled-jsx/style')
                }
              }
            ]
          ],
          inputSourceMap: sourceMap
        })

        return {
          content: transpiled.code,
          sourceMap: transpiled.map
        }
      }
    }
  }, {
    loader: 'babel-loader',
    include: nextPagesDir,
    exclude (str) {
      return /node_modules/.test(str) && str.indexOf(nextPagesDir) !== 0
    },
    options: {
      babelrc: false,
      cacheDirectory: true,
      sourceMaps: dev ? 'both' : false,
      presets: [require.resolve('./babel/preset')]
    }
  }, {
    test: /\.js(\?[^?]*)?$/,
    loader: 'babel-loader',
    include: [dir],
    exclude (str) {
      return /node_modules/.test(str)
    },
    options: mainBabelOptions
  }])

  let webpackConfig = {
    context: dir,
    entry,
    output: {
      path: join(dir, '.next'),
      filename: '[name]',
      libraryTarget: 'commonjs2',
      publicPath: '/_webpack/',
      strictModuleExceptionHandling: true,
      devtoolModuleFilenameTemplate ({ resourcePath }) {
        const hash = createHash('sha1')
        hash.update(Date.now() + '')
        const id = hash.digest('hex').slice(0, 7)

        // append hash id for cache busting
        return `webpack:///${resourcePath}?${id}`
      }
    },
    resolve: {
      modules: [
        nextNodeModulesDir,
        'node_modules',
        ...nodePathList
      ]
    },
    resolveLoader: {
      modules: [
        nextNodeModulesDir,
        'node_modules',
        join(__dirname, 'loaders'),
        ...nodePathList
      ]
    },
    plugins,
    module: {
      rules
    },
    devtool: dev ? 'inline-source-map' : false,
    performance: { hints: false }
  }

  if (config.webpack) {
    console.log('> Using "webpack" config function defined in next.config.js.')
    webpackConfig = await config.webpack(webpackConfig, { dev })
  }
  return webpack(webpackConfig)
}
<|endoftext|>


<commit_before>const db = require('../../schemas');

class ProblemAbstract {
    constructor ({projectId}) {
        this.projectId = projectId;
        this.causes = this.constructor.CauseClasses.map(CauseClass => {
            return new CauseClass();
        });
    }

    static get title () {
        throw new Error('must be implemented');
    }

    static get goodDescription () {
        throw new Error('must be implemented');
    }

    static get badDescription () {
        throw new Error('must be implemented');
    }

    static get checkDurationSeconds () {
        return 5 * 60; // 5 minutes
    }

    static get CauseClasses () {
        return [];
    }

    async checKProblem () {
        throw new Error('must be implemented');
    }

    async findProjectProblem ({transaction} = {}) {
        return await db.EvaluationProjectProblem.findOne({where: {
            projectId: this.projectId,
            problemName: this.constructor.name
        },
            transaction
        });
    }

    async createProjectProblem ({transaction} = {}) {
        return await db.EvaluationProjectProblem.create({
            projectId: this.projectId,
            problemName: this.constructor.name,
            isOccurred: false
        }, {transaction});
    }

    async findOrCreateProjectProblem ({transaction} = {}) {
        const res = await this.findProjectProblem({transaction});
        if (res) { return res; }
        return await this.createProjectProblem({transaction});
    }

    async updateStatus ({isOccurred}, {transaction} = {}) {
        const projectProblem = await this.findOrCreateProjectProblem({transaction});

        await projectProblem.update({
            isOccurred
        }, {
            transaction
        });
    }

    async needCheckProblem () {
        const projectProblem = await this.findProjectProblem();
        if (!projectProblem) { return true; }
        const expire = Number(new Date(projectProblem.updatedAt)) + this.constructor.checkDurationSeconds * 1000;
        return Date.now() > expire;
    }
}

module.exports = ProblemAbstract;
<commit_msg>checking problem is not needed if problem is already occurred<commit_after>const db = require('../../schemas');

class ProblemAbstract {
    constructor ({projectId}) {
        this.projectId = projectId;
        this.causes = this.constructor.CauseClasses.map(CauseClass => {
            return new CauseClass();
        });
    }

    static get title () {
        throw new Error('must be implemented');
    }

    static get goodDescription () {
        throw new Error('must be implemented');
    }

    static get badDescription () {
        throw new Error('must be implemented');
    }

    static get checkDurationSeconds () {
        return 5 * 60; // 5 minutes
    }

    static get CauseClasses () {
        return [];
    }

    async checKProblem () {
        throw new Error('must be implemented');
    }

    async findProjectProblem ({transaction} = {}) {
        return await db.EvaluationProjectProblem.findOne({where: {
            projectId: this.projectId,
            problemName: this.constructor.name
        },
            transaction
        });
    }

    async createProjectProblem ({transaction} = {}) {
        return await db.EvaluationProjectProblem.create({
            projectId: this.projectId,
            problemName: this.constructor.name,
            isOccurred: false
        }, {transaction});
    }

    async findOrCreateProjectProblem ({transaction} = {}) {
        const res = await this.findProjectProblem({transaction});
        if (res) { return res; }
        return await this.createProjectProblem({transaction});
    }

    async updateStatus ({isOccurred}, {transaction} = {}) {
        const projectProblem = await this.findOrCreateProjectProblem({transaction});

        await projectProblem.update({
            isOccurred
        }, {
            transaction
        });
    }

    async needCheckProblem () {
        const projectProblem = await this.findProjectProblem();
        if (!projectProblem) { return true; }
        // 既に問題が発生していたら更新しない
        // 問題の解決確認はユーザーが行う
        if (projectProblem.isOccurred) { return false; }
        const expire = Number(new Date(projectProblem.updatedAt)) + this.constructor.checkDurationSeconds * 1000;
        return Date.now() > expire;
    }
}

module.exports = ProblemAbstract;
<|endoftext|>


<commit_before>"use strict"

const dns = require('dns');
const os = require('os-utils');
const diskspace = require('diskspace');
const fs = require("fs");
const path = require('path');
const exec = require('child_process').exec;

const { config } = require('../config');

let getInternetStatus = (req, res) => {
    dns.lookup('google.com', (err) => {
        if (err && err.code == "ENOTFOUND") {
            res.send({data : false});
        } else {
            res.send({data : true});
        }
    })
}

let getLastRefresh = (req, res) => {
    let meta = path.join(config.FS_ROOT, '.meta');
    fs.readFile(meta, 'utf-8', (err,data)=>{
        res.send({data});
    })
}

let getNumberOfUsersConnected = (req, res) => {
    let responseData = {
        retrieveSuccessful : true,
        numberOfUsers : undefined,
        msg : 'Successfully retrieved number of users connected.'
    }

    const cmd = path.join(__dirname, '../../CDN/getall_stations.sh');

    exec(cmd, { shell: '/bin/bash' }, (err, stdout, stderr) => {
        // console.log('Error: ' + err);
        // console.log('stdout: ' + stdout);
        // console.log('stderr: ' + stderr);

    	if(err) {
            responseData.retrieveSuccessful = false;
    		responseData.msg = err;
    	}  else {
            responseData.numberOfUsers = stdout.trim();
        }

        res.status(200).json(responseData);
    });
}

let getSystemMemory = (req, res) => {
    const total = os.totalmem();
    const free = os.freemem();
    const usage = os.freememPercentage()*100;
    const sysUpTime = os.sysUptime();
    res.send({
        total,
        free,
        usage,
        sysUpTime
    })
}

let getSystemSpace = (req, res) => {
    diskspace.check('/', function (err, result) {
        res.send(result)
    });
}

let getSystemCpu = (req,res) => {
    os.cpuUsage(function(v){
        v = v * 100;
        res.send({v})
    });
}

let getSystemVersion = (req,res) => {
    let cdn = path.join(__dirname,"../../CDN/version.txt");
    fs.readFile(cdn, 'utf-8', (err,data)=>{
      //  console.log(err);
        res.send({data});
    })
}

module.exports = {
    getInternetStatus,
    getLastRefresh,
    getNumberOfUsersConnected,
    getSystemMemory,
    getSystemSpace,
    getSystemCpu,
    getSystemVersion
}
<commit_msg>Fix internet status displayed on dashboard<commit_after>"use strict"

const dns = require('dns');
const os = require('os-utils');
const diskspace = require('diskspace');
const fs = require("fs");
const path = require('path');
const exec = require('child_process').exec;

const { config } = require('../config');

let getInternetStatus = (req, res) => {
    dns.lookup('google.com', (err) => {
        if (err) {
            res.send({data : false});
        } else {
            res.send({data : true});
        }
    })
}

let getLastRefresh = (req, res) => {
    let meta = path.join(config.FS_ROOT, '.meta');
    fs.readFile(meta, 'utf-8', (err,data)=>{
        res.send({data});
    })
}

let getNumberOfUsersConnected = (req, res) => {
    let responseData = {
        retrieveSuccessful : true,
        numberOfUsers : undefined,
        msg : 'Successfully retrieved number of users connected.'
    }

    const cmd = path.join(__dirname, '../../CDN/getall_stations.sh');

    exec(cmd, { shell: '/bin/bash' }, (err, stdout, stderr) => {
        // console.log('Error: ' + err);
        // console.log('stdout: ' + stdout);
        // console.log('stderr: ' + stderr);

    	if(err) {
            responseData.retrieveSuccessful = false;
    		responseData.msg = err;
    	}  else {
            responseData.numberOfUsers = stdout.trim();
        }

        res.status(200).json(responseData);
    });
}

let getSystemMemory = (req, res) => {
    const total = os.totalmem();
    const free = os.freemem();
    const usage = os.freememPercentage()*100;
    const sysUpTime = os.sysUptime();
    res.send({
        total,
        free,
        usage,
        sysUpTime
    })
}

let getSystemSpace = (req, res) => {
    diskspace.check('/', function (err, result) {
        res.send(result)
    });
}

let getSystemCpu = (req,res) => {
    os.cpuUsage(function(v){
        v = v * 100;
        res.send({v})
    });
}

let getSystemVersion = (req,res) => {
    let cdn = path.join(__dirname,"../../CDN/version.txt");
    fs.readFile(cdn, 'utf-8', (err,data)=>{
      //  console.log(err);
        res.send({data});
    })
}

module.exports = {
    getInternetStatus,
    getLastRefresh,
    getNumberOfUsersConnected,
    getSystemMemory,
    getSystemSpace,
    getSystemCpu,
    getSystemVersion
}
<|endoftext|>


<commit_before>export { Base };

/**
 * Count how many operations use a tensor behind this counter.
 *
 * @member {number} tensorId
 *   This tensor's identifier.
 *
 * @member {Base} previousInputOpCounter0
 *   The first TensorOpCounter which is just before this TensorOpCounter.
 *
 * @member {Base} previousInputOpCounter1
 *   The second TensorOpCounter which is just before this TensorOpCounter.
 *
 * @member {number} behindOperationCount
 *   This tensor is used by how many operations which is behind this operation. If zero, the tensor is not used by other operation.
 *
 *
 *
 */
class Base {

  constructor( tensorId, previousTensorOpCounter0, previousTensorOpCounter1 ) {
    this.tensorId = tensorId;
    this.previousTensorOpCounter0 = previousTensorOpCounter0;
    this.previousTensorOpCounter1 = previousTensorOpCounter1;
    this.behindOperationCount = 0;
  }

}
<commit_msg>Update TensorOpCounter.js<commit_after>export { Base, Bases };

/**
 * Count how many operations use a tensor behind this counter.
 *
 * @member {number} tensorId
 *   This tensor's identifier.
 *
 * @member {Base} input0
 *   The first TensorOpCounter which is just before this TensorOpCounter.
 *
 * @member {Base} input1
 *   The second TensorOpCounter which is just before this TensorOpCounter.
 *
 * @member {number} behindOperationCount
 *   This tensor is used by how many operations which is behind this operation. If zero, the tensor is not used by other operation.
 *
 *
 *
 */
class Base {

  constructor( tensorId, input0, input1 ) {
    this.tensorId = tensorId;
    this.input0 = input0;
    this.input1 = input1;
    this.behindOperationCount = 0;
  }

}
<|endoftext|>


<commit_before>'use strict';
import React from 'react';
import c from 'classnames';
import moment from 'moment';
import { Link } from 'react-router';

import { formatThousands } from '../utils/format';

var LocationCard = React.createClass({
  displayName: 'LocationCard',

  propTypes: {
    onDownloadClick: React.PropTypes.func,
    compact: React.PropTypes.bool,
    name: React.PropTypes.string,
    city: React.PropTypes.string,
    countryData: React.PropTypes.object,
    sourcesData: React.PropTypes.array,
    totalMeasurements: React.PropTypes.number,
    parametersList: React.PropTypes.array,
    lastUpdate: React.PropTypes.string,
    collectionStart: React.PropTypes.string
  },

  onDownloadClick: function (e) {
    e.preventDefault();
    this.props.onDownloadClick();
  },

  renderParameters: function () {
    return this.props.parametersList.map(o => o.name).join(', ');
  },

  render: function () {
    const { countryData } = this.props;
    let updated = moment(this.props.lastUpdate).fromNow();
    let started = moment(this.props.collectionStart).format('YYYY/MM/DD');
    let sourcesData = this.props.sourcesData;

    let sources = [];
    if (sourcesData.length) {
      sourcesData.forEach((o, i) => {
        sources.push(<a href={o.sourceURL} title={`View source for  ${this.props.name}`} key={o.name}>{o.name});
        if (i < sourcesData.length - 1) {
          sources.push(', ');
        }
      });
    }

    const country = countryData || {};

    return (

Updated {updated}

<Link to={`/location/${encodeURIComponent(this.props.name)}`} title={`View ${this.props.name} page`}>{this.props.name}</Link> in {this.props.city}, {country.name}

Collection start

{started}

Measurements

{formatThousands(this.props.totalMeasurements)}

Values

{this.renderParameters()}

              {sources.length ?
Source
 : null}
              {sources.length ?
{sources}
 : null}

<a href='#' className='cfa-download' title={`Download data for ${this.props.name}`} onClick={this.onDownloadClick}>Download

<Link to={`/location/${encodeURIComponent(this.props.name)}`} className='cfa-go' title={`View ${this.props.name} page`}>View More</Link>

    );
  }
});

module.exports = LocationCard;
<commit_msg>Fix lint errors<commit_after>'use strict';
import React from 'react';
import moment from 'moment';
import { Link } from 'react-router';

import { formatThousands } from '../utils/format';

var LocationCard = React.createClass({
  displayName: 'LocationCard',

  propTypes: {
    onDownloadClick: React.PropTypes.func,
    compact: React.PropTypes.bool,
    name: React.PropTypes.string,
    city: React.PropTypes.string,
    countryData: React.PropTypes.object,
    sourcesData: React.PropTypes.array,
    totalMeasurements: React.PropTypes.number,
    parametersList: React.PropTypes.array,
    lastUpdate: React.PropTypes.string,
    collectionStart: React.PropTypes.string
  },

  onDownloadClick: function (e) {
    e.preventDefault();
    this.props.onDownloadClick();
  },

  renderParameters: function () {
    return this.props.parametersList.map(o => o.name).join(', ');
  },

  render: function () {
    const { countryData } = this.props;
    let updated = moment(this.props.lastUpdate).fromNow();
    let started = moment(this.props.collectionStart).format('YYYY/MM/DD');
    let sourcesData = this.props.sourcesData;

    let sources = [];
    if (sourcesData.length) {
      sourcesData.forEach((o, i) => {
        sources.push(<a href={o.sourceURL} title={`View source for  ${this.props.name}`} key={o.name}>{o.name});
        if (i < sourcesData.length - 1) {
          sources.push(', ');
        }
      });
    }

    const country = countryData || {};

    return (

Updated {updated}

<Link to={`/location/${encodeURIComponent(this.props.name)}`} title={`View ${this.props.name} page`}>{this.props.name}</Link> in {this.props.city}, {country.name}

Collection start

{started}

Measurements

{formatThousands(this.props.totalMeasurements)}

Values

{this.renderParameters()}

              {sources.length ?
Source
 : null}
              {sources.length ?
{sources}
 : null}

<a href='#' className='cfa-download' title={`Download data for ${this.props.name}`} onClick={this.onDownloadClick}>Download

<Link to={`/location/${encodeURIComponent(this.props.name)}`} className='cfa-go' title={`View ${this.props.name} page`}>View More</Link>

    );
  }
});

module.exports = LocationCard;
<|endoftext|>


<commit_before>/**
 * Not type checking this file because flow doesn't like attaching
 * properties to Elements.
 */

import { looseEqual, looseIndexOf } from 'shared/util'
import { warn, nextTick, isAndroid, isIE9, isIE, isEdge } from 'core/util/index'

const modelableTagRE = /^input|select|textarea|vue-component-[0-9]+(-[0-9a-zA-Z_\-]*)?$/

/* istanbul ignore if */
if (isIE9) {
  // http://www.matts411.com/post/internet-explorer-9-oninput/
  document.addEventListener('selectionchange', () => {
    const el = document.activeElement
    if (el && el.vmodel) {
      trigger(el, 'input')
    }
  })
}

export default {
  bind (el, binding, vnode) {
    if (process.env.NODE_ENV !== 'production') {
      if (!modelableTagRE.test(vnode.tag)) {
        warn(
          `v-model is not supported on element type: <${vnode.tag}>. ` +
          'If you are working with contenteditable, it\'s recommended to ' +
          'wrap a library dedicated for that purpose inside a custom component.',
          vnode.context
        )
      }
    }
    if (vnode.tag === 'select') {
      setSelected(el, binding, vnode.context)
      /* istanbul ignore if */
      if (isIE || isEdge) {
        nextTick(() => {
          setSelected(el, binding, vnode.context)
        })
      }
    } else if (vnode.tag === 'textarea' || el.type === 'text') {
      if (!isAndroid) {
        el.addEventListener('compositionstart', onCompositionStart)
        el.addEventListener('compositionend', onCompositionEnd)
      }
      /* istanbul ignore if */
      if (isIE9) {
        el.vmodel = true
      }
    }
  },
  componentUpdated (el, binding, vnode) {
    if (vnode.tag === 'select') {
      setSelected(el, binding, vnode.context)
      // in case the options rendered by v-for have changed,
      // it's possible that the value is out-of-sync with the rendered options.
      // detect such cases and filter out values that no longer has a matchig
      // option in the DOM.
      const needReset = el.multiple
        ? binding.value.some(v => hasNoMatchingOption(v, el.options))
        : hasNoMatchingOption(binding.value, el.options)
      if (needReset) {
        trigger(el, 'change')
      }
    }
  }
}

function setSelected (el, binding, vm) {
  const value = binding.value
  const isMultiple = el.multiple
  if (isMultiple && !Array.isArray(value)) {
    process.env.NODE_ENV !== 'production' && warn(
      `<select multiple v-model="${binding.expression}"> ` +
      `expects an Array value for its binding, but got ${
        Object.prototype.toString.call(value).slice(8, -1)
      }`,
      vm
    )
    return
  }
  let selected, option
  for (let i = 0, l = el.options.length; i < l; i++) {
    option = el.options[i]
    if (isMultiple) {
      selected = looseIndexOf(value, getValue(option)) > -1
      if (option.selected !== selected) {
        option.selected = selected
      }
    } else {
      if (looseEqual(getValue(option), value)) {
        if (el.selectedIndex !== i) {
          el.selectedIndex = i
        }
        return
      }
    }
  }
  if (!isMultiple) {
    el.selectedIndex = -1
  }
}

function hasNoMatchingOption (value, options) {
  for (let i = 0, l = options.length; i < l; i++) {
    if (looseEqual(getValue(options[i]), value)) {
      return false
    }
  }
  return true
}

function getValue (option) {
  return '_value' in option
    ? option._value
    : option.value
}

function onCompositionStart (e) {
  e.target.composing = true
}

function onCompositionEnd (e) {
  e.target.composing = false
  trigger(e.target, 'input')
}

function trigger (el, type) {
  const e = document.createEvent('HTMLEvents')
  e.initEvent(type, true, true)
  el.dispatchEvent(e)
}
<commit_msg>fix IE/Edge select remembering selection (#3803)<commit_after>/**
 * Not type checking this file because flow doesn't like attaching
 * properties to Elements.
 */

import { looseEqual, looseIndexOf } from 'shared/util'
import { warn, nextTick, isAndroid, isIE9, isIE, isEdge } from 'core/util/index'

const modelableTagRE = /^input|select|textarea|vue-component-[0-9]+(-[0-9a-zA-Z_\-]*)?$/

/* istanbul ignore if */
if (isIE9) {
  // http://www.matts411.com/post/internet-explorer-9-oninput/
  document.addEventListener('selectionchange', () => {
    const el = document.activeElement
    if (el && el.vmodel) {
      trigger(el, 'input')
    }
  })
}

export default {
  bind (el, binding, vnode) {
    if (process.env.NODE_ENV !== 'production') {
      if (!modelableTagRE.test(vnode.tag)) {
        warn(
          `v-model is not supported on element type: <${vnode.tag}>. ` +
          'If you are working with contenteditable, it\'s recommended to ' +
          'wrap a library dedicated for that purpose inside a custom component.',
          vnode.context
        )
      }
    }
    if (vnode.tag === 'select') {
      setSelected(el, binding, vnode.context)
      /* istanbul ignore if */
      if (isIE || isEdge) {
        const cb = () => {
          setSelected(el, binding, vnode.context)
        }
        nextTick(cb)
        setTimeout(cb, 0)
      }
    } else if (vnode.tag === 'textarea' || el.type === 'text') {
      if (!isAndroid) {
        el.addEventListener('compositionstart', onCompositionStart)
        el.addEventListener('compositionend', onCompositionEnd)
      }
      /* istanbul ignore if */
      if (isIE9) {
        el.vmodel = true
      }
    }
  },
  componentUpdated (el, binding, vnode) {
    if (vnode.tag === 'select') {
      setSelected(el, binding, vnode.context)
      // in case the options rendered by v-for have changed,
      // it's possible that the value is out-of-sync with the rendered options.
      // detect such cases and filter out values that no longer has a matchig
      // option in the DOM.
      const needReset = el.multiple
        ? binding.value.some(v => hasNoMatchingOption(v, el.options))
        : hasNoMatchingOption(binding.value, el.options)
      if (needReset) {
        trigger(el, 'change')
      }
    }
  }
}

function setSelected (el, binding, vm) {
  const value = binding.value
  const isMultiple = el.multiple
  if (isMultiple && !Array.isArray(value)) {
    process.env.NODE_ENV !== 'production' && warn(
      `<select multiple v-model="${binding.expression}"> ` +
      `expects an Array value for its binding, but got ${
        Object.prototype.toString.call(value).slice(8, -1)
      }`,
      vm
    )
    return
  }
  let selected, option
  for (let i = 0, l = el.options.length; i < l; i++) {
    option = el.options[i]
    if (isMultiple) {
      selected = looseIndexOf(value, getValue(option)) > -1
      if (option.selected !== selected) {
        option.selected = selected
      }
    } else {
      if (looseEqual(getValue(option), value)) {
        if (el.selectedIndex !== i) {
          el.selectedIndex = i
        }
        return
      }
    }
  }
  if (!isMultiple) {
    el.selectedIndex = -1
  }
}

function hasNoMatchingOption (value, options) {
  for (let i = 0, l = options.length; i < l; i++) {
    if (looseEqual(getValue(options[i]), value)) {
      return false
    }
  }
  return true
}

function getValue (option) {
  return '_value' in option
    ? option._value
    : option.value
}

function onCompositionStart (e) {
  e.target.composing = true
}

function onCompositionEnd (e) {
  e.target.composing = false
  trigger(e.target, 'input')
}

function trigger (el, type) {
  const e = document.createEvent('HTMLEvents')
  e.initEvent(type, true, true)
  el.dispatchEvent(e)
}
<|endoftext|>


<commit_before>let path = require('path')

const rootPath = path.join(__dirname,'../../')

module.exports = {
    development: {
        rootPath: rootPath,
        port: 3000,
        dbConnectionString: 'mongodb://localhost:27017/chatroom-app'
    },
    production: {
        rootPath: rootPath,
        port: PORT,
        dbConnectionString: process.env.DB_CONN_STR
    }
}<commit_msg>fixed: heroku minor changes<commit_after>let path = require('path')

const rootPath = path.join(__dirname,'../../')

module.exports = {
    development: {
        rootPath: rootPath,
        port: 3000,
        dbConnectionString: 'mongodb://localhost:27017/chatroom-app'
    },
    production: {
        rootPath: rootPath,
        port: process.env.PORT,
        dbConnectionString: process.env.DB_CONN_STR
    }
}<|endoftext|>


<commit_before>export { Base };

//!!! ...unfinished... (2022/05/30) should create objects representing this operation's output(s).
class Operation {
}

/**
 * Count there are how many operations using this operation's output tensor.
 *
 * @member {any} tensorId
 *   This tensor's identifier.
 *
 * @member {object} operationObject
 *   The object which implements the operation. It should have the following method:
 *   - setKeepInputTensor( bKeepInputTensor0, bKeepInputTensor1 )
 *   - pfnOperation( inputTensor0, inputTensor1 )
 *

!!! ...unfinished... (2022/05/30) What if the previous operation has two outputs? (e.g. ConcatShuffleSplit)

 * @member {Base} input0
 *   The TensorOpCounter.Base object which represents this operation's first input.
 *
 * @member {Base} input1
 *   The TensorOpCounter.Base object which represents this operation's second input. It could be null which means this operation
 * does not have second input tensor.
 *
 * @member {Base[]} nextOperationArray
 *   The operations which will use the output tensor of this operation.
 *
 * @member {number} nextOperationsCount
 *   This operation's output tensor is used by how many operations which is behind this operation. This is the same as
 * this.nextOperationArray.length. If zero, the output tensor is not used by other operation.
 *
 *
 *
 */
class Base {

//!!! ...unfinished... (2022/05/30) should create objects representing this operation's output(s).

  constructor( tensorId, operationObject, input0, input1 ) {
    this.tensorId = tensorId;
    this.operationObject = operationObject;
    this.inputArray = [ input0, input1 ];
    this.nextOperationArray = [];

    // Record this operation is one of the input's operations.
    for ( let i = 0; i < this.inputArray.length; ++i ) {
      let input = this.inputArray[ i ];
      if ( input )
        input.nextOperationArray.push( this );
    }

    this.bKeepInputTensorArray = new Array( this.inputArray.length );
  }

  /**
   * Call the this.operationObject.setKeepInputTensor() according to：
   *   - whether the operation is the last operation of the this.input0 / this.input1.
   *   - whether the this.input0 / this.input1 is in alwaysKeepSet.
   *
   * @param {Set<Base>} alwaysKeepSet
   *   A set object. Its every element is TensorOpCounter.Base object. They represent tensors never be disposed. The this.input0
   * and this.input1 will be compared with them.
   */
  setKeepInputTensor_IfNotLastOperation_Or_In( alwaysKeepSet ) {

    if ( !this.operationObject )
      return; // Since there is no operation, there is no need to set up its keep-input flags.

    // Every input which needs to be disposed by this operation will have an entry in this Map.
    //
    // The key is the input TensorOpCounter.Base object.
    // The value is its array index in this.inputArray[] (also in this.bKeepInputTensorArray[]).
    let input_dispose_Map = new Map;

    for ( let i = 0; i < this.inputArray.length; ++i ) {
      let input = this.inputArray[ i ];

      if ( !input )
        continue; // Since the input does not exist, there is no need to dispose it.

      if ( ( alwaysKeepSet ) && ( alwaysKeepSet.has( input ) ) )
        continue; // The input in alwaysKeepSet should always be kept (always not to be disposed).

      if ( input.nextOperationArray.length <= 0 )
        continue; // Since the input does not processed by this operation, there is no need to dispose it. (shoud not happen)

      // If this operation is the last operation of the input tensor, this operation is responsible for disposing it.
      //
      // Note: If an input appears multiple times in this.inputArray[] (i.e. multiple inputs of this operation are the same one input),
      // the map will only record the last one. So that the input will only be disposed once (rather than multiple times).
      if ( input.nextOperationArray[ input.nextOperationArray.length - 1 ] == this )
        input_dispose_Map.set( input, i );

      // Otherwise, This operation uses the input tensor. There are, however, other operations still need use the same input tensor.
      // So the input tensor should not be disposed (i.e. should be kept) by this operation.
    }

    // Find out and mark all the input tensors should be disposed (i.e. will not be kept) by this operation.
    this.bKeepInputTensorArray.fill( true ); // Default is keep every input.
    for ( let [ input, arrayIndex ] of input_dispose_Map ) {
      this.bKeepInputTensorArray[ arrayIndex ] = false;
    }

    // Configure the operation to keep or dispose its inputs.
    this.operationObject.setKeepInputTensor( this.bKeepInputTensorArray[ 0 ], this.bKeepInputTensorArray[ 1 ] );
  }

  get nextOperationsCount() {
    return this.nextOperationArray.length;
  }

}
<commit_msg>Update TensorOpCounter.js<commit_after>export { Base };

//!!! ...unfinished... (2022/05/30) should create objects representing this operation's output(s).
class OperationPlaceholder {
}

//!!! ...unfinished... (2022/05/30) Rename Base to TensorPlaceholder.

/**
 * Count there are how many operations using this operation's output tensor.
 *
 * @member {any} tensorId
 *   This tensor's identifier.
 *
 * @member {object} operationObject
 *   The object which implements the operation. It should have the following method:
 *   - setKeepInputTensor( bKeepInputTensor0, bKeepInputTensor1 )
 *   - pfnOperation( inputTensor0, inputTensor1 )
 *

!!! ...unfinished... (2022/05/30) What if the previous operation has two outputs? (e.g. ConcatShuffleSplit)

 * @member {Base} input0
 *   The TensorOpCounter.Base object which represents this operation's first input.
 *
 * @member {Base} input1
 *   The TensorOpCounter.Base object which represents this operation's second input. It could be null which means this operation
 * does not have second input tensor.
 *
 * @member {Base[]} nextOperationArray
 *   The operations which will use the output tensor of this operation.
 *
 * @member {number} nextOperationsCount
 *   This operation's output tensor is used by how many operations which is behind this operation. This is the same as
 * this.nextOperationArray.length. If zero, the output tensor is not used by other operation.
 *
 *
 *
 */
class Base {

//!!! ...unfinished... (2022/05/30) should create objects representing this operation's output(s).

  constructor( tensorId, operationObject, input0, input1 ) {
    this.tensorId = tensorId;
    this.operationObject = operationObject;
    this.inputArray = [ input0, input1 ];
    this.nextOperationArray = [];

    // Record this operation is one of the input's operations.
    for ( let i = 0; i < this.inputArray.length; ++i ) {
      let input = this.inputArray[ i ];
      if ( input )
        input.nextOperationArray.push( this );
    }

    this.bKeepInputTensorArray = new Array( this.inputArray.length );
  }

  /**
   * Call the this.operationObject.setKeepInputTensor() according to：
   *   - whether the operation is the last operation of the this.input0 / this.input1.
   *   - whether the this.input0 / this.input1 is in alwaysKeepSet.
   *
   * @param {Set<Base>} alwaysKeepSet
   *   A set object. Its every element is TensorOpCounter.Base object. They represent tensors never be disposed. The this.input0
   * and this.input1 will be compared with them.
   */
  setKeepInputTensor_IfNotLastOperation_Or_In( alwaysKeepSet ) {

    if ( !this.operationObject )
      return; // Since there is no operation, there is no need to set up its keep-input flags.

    // Every input which needs to be disposed by this operation will have an entry in this Map.
    //
    // The key is the input TensorOpCounter.Base object.
    // The value is its array index in this.inputArray[] (also in this.bKeepInputTensorArray[]).
    let input_dispose_Map = new Map;

    for ( let i = 0; i < this.inputArray.length; ++i ) {
      let input = this.inputArray[ i ];

      if ( !input )
        continue; // Since the input does not exist, there is no need to dispose it.

      if ( ( alwaysKeepSet ) && ( alwaysKeepSet.has( input ) ) )
        continue; // The input in alwaysKeepSet should always be kept (always not to be disposed).

      if ( input.nextOperationArray.length <= 0 )
        continue; // Since the input does not processed by this operation, there is no need to dispose it. (shoud not happen)

      // If this operation is the last operation of the input tensor, this operation is responsible for disposing it.
      //
      // Note: If an input appears multiple times in this.inputArray[] (i.e. multiple inputs of this operation are the same one input),
      // the map will only record the last one. So that the input will only be disposed once (rather than multiple times).
      if ( input.nextOperationArray[ input.nextOperationArray.length - 1 ] == this )
        input_dispose_Map.set( input, i );

      // Otherwise, This operation uses the input tensor. There are, however, other operations still need use the same input tensor.
      // So the input tensor should not be disposed (i.e. should be kept) by this operation.
    }

    // Find out and mark all the input tensors should be disposed (i.e. will not be kept) by this operation.
    this.bKeepInputTensorArray.fill( true ); // Default is keep every input.
    for ( let [ input, arrayIndex ] of input_dispose_Map ) {
      this.bKeepInputTensorArray[ arrayIndex ] = false;
    }

    // Configure the operation to keep or dispose its inputs.
    this.operationObject.setKeepInputTensor( this.bKeepInputTensorArray[ 0 ], this.bKeepInputTensorArray[ 1 ] );
  }

  get nextOperationsCount() {
    return this.nextOperationArray.length;
  }

}
<|endoftext|>


<commit_before>import { createAction, createThunkAction } from 'redux-tools';
import { getExtent, getLoss } from 'services/forest-data';
import sortBy from 'lodash/sortBy';
import groupBy from 'lodash/groupBy';
import sumBy from 'lodash/sumBy';
import max from 'lodash/max';
import reverse from 'lodash/reverse';
import axios from 'axios';

export const setHeaderLoading = createAction('setHeaderLoading');
export const setHeaderData = createAction('setHeaderData');

export const getHeaderData = createThunkAction(
  'getHeaderData',
  params => dispatch => {
    dispatch(setHeaderLoading({ loading: true, error: false }));
    axios
      .all([
        getExtent(params),
        getExtent({ ...params, forestType: 'plantations' }),
        getLoss(params),
        getLoss({ ...params, forestType: 'plantations' }),
        getLoss({ ...params, forestType: 'primary_forest' })
      ])
      .then(
        axios.spread(
          (
            totalExtent,
            totalPlantationsExtent,
            totalLoss,
            totalPlantationsLoss,
            totalPrimaryLoss
          ) => {
            const extent = totalExtent.data.data;
            const loss = totalLoss.data.data;
            const plantationsLoss = totalPlantationsLoss.data.data;
            const plantationsExtent = totalPlantationsExtent.data.data;

            // group over years
            const groupedLoss = plantationsLoss && groupBy(loss, 'year');
            const groupedPlantationsLoss =
              plantationsLoss && groupBy(plantationsLoss, 'year');

            const primaryLoss = totalPrimaryLoss.data.data;
            const latestYear = max(Object.keys(groupedLoss));

            const latestPlantationLoss =
              groupedPlantationsLoss[latestYear] || null;
            const latestLoss = groupedLoss[latestYear] || null;

            // sum over different bound1 within year
            const summedPlantationsLoss =
              latestPlantationLoss.length && latestPlantationLoss[0].area
                ? sumBy(latestPlantationLoss, 'area')
                : 0;
            const summedPlantationsEmissions =
              latestPlantationLoss.length && latestPlantationLoss[0].emissions
                ? sumBy(latestPlantationLoss, 'emissions')
                : 0;
            const summedLoss =
              latestLoss.length && latestLoss[0].area
                ? sumBy(latestLoss, 'area')
                : 0;
            const summedEmissions =
              latestLoss.length && latestLoss[0].emissions
                ? sumBy(latestLoss, 'emissions')
                : 0;

            const data = {
              totalArea: (extent[0] && extent[0].total_area) || 0,
              extent: (extent[0] && extent[0].value) || 0,
              plantationsExtent:
                plantationsExtent && plantationsExtent.length
                  ? plantationsExtent[0].area
                  : 0,
              totalLoss: {
                area: summedLoss || 0,
                year: latestYear || 0,
                emissions: summedEmissions || 0
              },
              plantationsLoss: {
                area: summedPlantationsLoss || 0,
                emissions: summedPlantationsEmissions || 0
              },
              primaryLoss:
                primaryLoss && primaryLoss.length
                  ? reverse(sortBy(primaryLoss, 'year'))[0]
                  : {}
            };
            dispatch(setHeaderData(data));
          }
        )
      )
      .catch(error => {
        dispatch(setHeaderLoading({ loading: false, error: true }));
        console.info(error);
      });
  }
);

export const handleLocationChange = createThunkAction(
  'handleLocationChange',
  location => (dispatch, getState) => {
    const { query, type } = getState().location;
    const newQuery = {};

    if (query) {
      Object.keys(query).forEach(key => {
        const queryObj = query[key] || {};
        if (typeof queryObj === 'object') {
          const { forestType, landCategory, page } = queryObj;
          newQuery[key] = {
            ...queryObj,
            ...(forestType && {
              forestType: ''
            }),
            ...(landCategory && {
              landCategory: ''
            }),
            ...(page && {
              page: 0
            })
          };
        } else {
          newQuery[key] = queryObj;
        }
      });
    }

    dispatch({
      type,
      payload: {
        type: location.adm0 ? 'country' : 'global',
        ...location
      },
      query: {
        ...newQuery,
        widget: undefined
      }
    });
  }
);
<commit_msg>Catch no plantations case<commit_after>import { createAction, createThunkAction } from 'redux-tools';
import { getExtent, getLoss } from 'services/forest-data';
import sortBy from 'lodash/sortBy';
import groupBy from 'lodash/groupBy';
import sumBy from 'lodash/sumBy';
import max from 'lodash/max';
import reverse from 'lodash/reverse';
import axios from 'axios';

export const setHeaderLoading = createAction('setHeaderLoading');
export const setHeaderData = createAction('setHeaderData');

export const getHeaderData = createThunkAction(
  'getHeaderData',
  params => dispatch => {
    dispatch(setHeaderLoading({ loading: true, error: false }));
    axios
      .all([
        getExtent(params),
        getExtent({ ...params, forestType: 'plantations' }),
        getLoss(params),
        getLoss({ ...params, forestType: 'plantations' }),
        getLoss({ ...params, forestType: 'primary_forest' })
      ])
      .then(
        axios.spread(
          (
            totalExtent,
            totalPlantationsExtent,
            totalLoss,
            totalPlantationsLoss,
            totalPrimaryLoss
          ) => {
            const extent = totalExtent.data.data;
            const loss = totalLoss.data.data;
            const plantationsLoss = totalPlantationsLoss.data.data;
            const plantationsExtent = totalPlantationsExtent.data.data;

            // group over years
            const groupedLoss = plantationsLoss && groupBy(loss, 'year');
            const groupedPlantationsLoss =
              plantationsLoss && groupBy(plantationsLoss, 'year');

            const primaryLoss = totalPrimaryLoss.data.data;
            const latestYear = max(Object.keys(groupedLoss));

            const latestPlantationLoss =
              groupedPlantationsLoss[latestYear] || null;
            const latestLoss = groupedLoss[latestYear] || null;

            // sum over different bound1 within year
            const summedPlantationsLoss =
              latestPlantationLoss &&
              latestPlantationLoss.length &&
              latestPlantationLoss[0].area
                ? sumBy(latestPlantationLoss, 'area')
                : 0;
            const summedPlantationsEmissions =
              latestPlantationLoss &&
              latestPlantationLoss.length &&
              latestPlantationLoss[0].emissions
                ? sumBy(latestPlantationLoss, 'emissions')
                : 0;
            const summedLoss =
              latestLoss && latestLoss.length && latestLoss[0].area
                ? sumBy(latestLoss, 'area')
                : 0;
            const summedEmissions =
              latestLoss && latestLoss.length && latestLoss[0].emissions
                ? sumBy(latestLoss, 'emissions')
                : 0;

            const data = {
              totalArea: (extent[0] && extent[0].total_area) || 0,
              extent: (extent[0] && extent[0].value) || 0,
              plantationsExtent:
                plantationsExtent && plantationsExtent.length
                  ? plantationsExtent[0].area
                  : 0,
              totalLoss: {
                area: summedLoss || 0,
                year: latestYear || 0,
                emissions: summedEmissions || 0
              },
              plantationsLoss: {
                area: summedPlantationsLoss || 0,
                emissions: summedPlantationsEmissions || 0
              },
              primaryLoss:
                primaryLoss && primaryLoss.length
                  ? reverse(sortBy(primaryLoss, 'year'))[0]
                  : {}
            };
            dispatch(setHeaderData(data));
          }
        )
      )
      .catch(error => {
        dispatch(setHeaderLoading({ loading: false, error: true }));
        console.info(error);
      });
  }
);

export const handleLocationChange = createThunkAction(
  'handleLocationChange',
  location => (dispatch, getState) => {
    const { query, type } = getState().location;
    const newQuery = {};

    if (query) {
      Object.keys(query).forEach(key => {
        const queryObj = query[key] || {};
        if (typeof queryObj === 'object') {
          const { forestType, landCategory, page } = queryObj;
          newQuery[key] = {
            ...queryObj,
            ...(forestType && {
              forestType: ''
            }),
            ...(landCategory && {
              landCategory: ''
            }),
            ...(page && {
              page: 0
            })
          };
        } else {
          newQuery[key] = queryObj;
        }
      });
    }

    dispatch({
      type,
      payload: {
        type: location.adm0 ? 'country' : 'global',
        ...location
      },
      query: {
        ...newQuery,
        widget: undefined
      }
    });
  }
);
<|endoftext|>


<commit_before>/**
 * Module four all our email sending needs!
 *
 * This compiles templates with external style sheets and varaibles into email ready (inline styles)
 * emails. It then sends them using a transport method of our choice.
 *
 * It utilizies the 'email-templates' module from NPM to render nice html emails.
 *
 *
 * @note config settings are located in config.js and can be set per environment
 * @note our temlates are stored in app/templates/email-slug
 *
 * @todo support non-html emails, for simple stuff?
 *
 */
var path = require('path')
    , templatesDir, emailTemplates = require('email-templates')
    , nodemailer = require('nodemailer')
    , fs = require('fs')
    , localConfig = {}
    , _ = require('underscore')
    , transport = null
    , preventSend = false
    , env = process.env.NODE_ENV || 'development'
    ;

module.exports = {

    /**
    * Initialize function, needs to be called in our server.js file
    * Creates a transport using environment config settings
    *
    */
    init: function (config) {

        // save the config object within this module
        localConfig = config;

        // create our nodemailer transport object
        // we only need to create this once, and we'll use it to send any emails for this request
        transport = nodemailer.createTransport(localConfig.type, localConfig.settings);

        // don't send real emails in development or testing
        if(env !== 'production') {
            preventSend = true;
        }

    },

    /**
    * Function to send an email using a specified template and set of variables
    * This function should be called anywhere in the app that we need to send an email
    *
    * It does the following:
    * - Check for a valid slug, ie: there is a template folder that exists
    * - Get the template defaults (subject, variables, etc.) which prevents errors in template rendering
    * - Then render the template
    * - validate attachments, if any, match the required format
    * - Check environment, and only log if we are in development or testing
    * - Send email
    *
    * @note that currently this function doesn't accept callbacks, so the controller won't know if it was
    *       completed successfully. This also means we don't need to wait for the email to send to move on.
    *
    */
    send: function(slug, locals) {

        // Get config path
        var configPath = path.join(localConfig.templatesDir, slug, 'config.js');

        // Get defaults from config path
        var defaults = require(configPath).defaults;
        if(!defaults) throw Error('The config.js file for this email is missing or invalid.');

        // extend local options with template defaults
        // this ensure all variables are set, and prevents template rendering error or
        // strange emails with broken syntax.
        locals = _.extend(defaults, locals);

        // attempt to send the email
        trySend(slug, locals);

    }

};

/**
* Generate an email address formatted name from a name object or a string
*
* Ideally email addresses use the pattern: "John Doe <john@doe.com>",
* which allows email clients to register a display name and email address
*
* You can pass this function a plain email, which it will return
* Or you can pass an object and it will assemlble the proper "name <email>" formatting
*
* Expected object should be this:
*
* objOrString: {
*     fullName: 'First Last',
*     email: 'johndoe@gmail.com'
* }
*
* @example buildAddress(matt@gmail.com)
*    // return "matt@gmail.com"
*
* @example buildAddress({fullName : 'Matt Miller'})
*    // throw error, email is missing!
*
* @example buildAddress({fullName : 'Matt Miller', email: 'matt@gmail.com'})
*    // return "Matt Miller <matt@gmail.com>"
*
*
* @todo convert our variable to lowercase, since thats much easier to remember
*
*/
var buildAddress = function(objOrString) {

    var formattedAddress;

    if(!objOrString) {
        throw Error('You must provide at least an email string');
    }

    if(typeof objOrString === 'string') {
        formattedAddress = objOrString;
        return formattedAddress;
    }

    if(typeof objOrString !== 'string' && !objOrString.email) {
        throw Error('You must provide a valid email');
    }

    formattedAddress = objOrString.email;

    if(objOrString.fullName) {
        formattedAddress = objOrString.fullName + " <" + formattedAddress + ">";
    }

    return formattedAddress;

};

/**
* Function to check if an object matches an object format.
* Returns false or the obj if match.
*
*/
var matchFormat = function(format, obj) {

    var match = true;
    var formatted = {};

    _.each(format, function(value, key) {
        if(!obj[key]) {
            match = false;
            //console.warn(key + ' doesn\'t exist in obj');
            return;
        } else {
            //console.log(key + ' = ' + value);
            formatted[key] = obj[key];
        }
    });

    if(!match) {

        console.warn('Pattern didn\'t match format. Please try again with:');
        console.warn(format);

        fs.exists(obj, function(exists) {
            if(exists) {
                console.log('Although not in the proper format, this is a real file. We can try to fudge it. We won\'t be doing it now though.');
            } else {
                console.log('No hope :(');
                return;
            }
        });

    } else {
        return formatted;
    }

};

/**
* Validates format for attachments. Currenly only validates File Stream
* @todo support Binary Buffer and String if needed
*
* If you only send an array of filePaths, we'll be nice enought to generate the proper
* structure for you :)
*
*/
var validateAttachments = function(attachments) {

    if(typeof attachments !== 'object' && typeof attachments !== 'array') {
        throw Error('Attachment should be an array (For multiple) or an object');
    }

    var format = {
        fileName: null,     // file name
        filePath: null,     // path to file
        cid: null           // should be as unique as possible
    };

    _.each(attachments, function(attachment) {
        attachment = matchFormat(format, attachment);
    });

    return attachments;

};

/**
* Logging function for delopment and testing mode
*
*/
var logEmail = function(options) {
    console.info('-----------------------------------');
    console.info('TESTING, TESTING, read all about it');
    console.info('-----------------------------------');
    console.info(options);
    console.info('-----------------------------------');
};

/**
* Function compiles template, and sends email to a single user
*
*
*/
var trySend = function(templateSlug, locals) {

    /**
    * Initialize the email template generator
    *
    */
    emailTemplates(localConfig.templatesDir, function (err, template) {

        if(err) {
            console.error(err);
        }

        // Send a single email
        template(templateSlug, locals.variables, function (err, html, text) {

            if(err) {
                console.error(err);
            }

            // Assemble our final options, we do this here so we can easily log or email them.
            var finalOptions = {

                // basic information
                from: buildAddress(localConfig.from),
                to: buildAddress(locals.to),
                subject: locals.subject,

                // options that creates text automatically? We set it specifically in our template
                // generateTextFromHTML: true,

                // will not always be present
                attachments: locals.attachments ? validateAttachments(locals.attachments) : null,
                headers: locals.headers ? locals.headers : null,

                // text and html are returned from our template builder
                text: text,
                html: html

            };

            /**
            * PreventSend will be flagged true if we are in development || testing mode.
            * This prevents us from sending un wanted emails to random people.
            */
            if(preventSend) {

                logEmail(finalOptions);

            } else {
               // Send email using our default transport
                transport.sendMail(finalOptions, function (err, responseStatus) {
                    if (err) {
                        console.log(err);
                    } else {
                        console.log(responseStatus.message);
                    }
                });
            }

        });

    });

};<commit_msg>Check for required params within emailer<commit_after>/**
 * Module four all our email sending needs!
 *
 * This compiles templates with external style sheets and varaibles into email ready (inline styles)
 * emails. It then sends them using a transport method of our choice.
 *
 * It utilizies the 'email-templates' module from NPM to render nice html emails.
 *
 *
 * @note config settings are located in config.js and can be set per environment
 * @note our temlates are stored in app/templates/email-slug
 *
 * @todo support non-html emails, for simple stuff?
 *
 */
var path = require('path')
    , templatesDir, emailTemplates = require('email-templates')
    , nodemailer = require('nodemailer')
    , fs = require('fs')
    , localConfig = {}
    , _ = require('underscore')
    , transport = null
    , preventSend = false
    , env = process.env.NODE_ENV || 'development'
    ;

module.exports = {

    /**
    * Initialize function, needs to be called in our server.js file
    * Creates a transport using environment config settings
    *
    */
    init: function (config) {

        // save the config object within this module
        localConfig = config;

        // create our nodemailer transport object
        // we only need to create this once, and we'll use it to send any emails for this request
        transport = nodemailer.createTransport(localConfig.type, localConfig.settings);

        // don't send real emails in development or testing
        if(env !== 'production') {
            preventSend = true;
        }

    },

    /**
    * Function to send an email using a specified template and set of variables
    * This function should be called anywhere in the app that we need to send an email
    *
    * It does the following:
    * - Check for a valid slug, ie: there is a template folder that exists
    * - Get the template defaults (subject, variables, etc.) which prevents errors in template rendering
    * - Then render the template
    * - validate attachments, if any, match the required format
    * - Check environment, and only log if we are in development or testing
    * - Send email
    *
    * @note that currently this function doesn't accept callbacks, so the controller won't know if it was
    *       completed successfully. This also means we don't need to wait for the email to send to move on.
    *
    */
    send: function(slug, locals) {

        if(!slug || !locals) throw Error('Slug and locals are required');

        // Get config path
        var configPath = path.join(localConfig.templatesDir, slug, 'config.js');

        // Get defaults from config path
        var defaults = require(configPath).defaults;
        if(!defaults) throw Error('The config.js file for this email is missing or invalid.');

        // extend local options with template defaults
        // this ensure all variables are set, and prevents template rendering error or
        // strange emails with broken syntax.
        locals = _.extend(defaults, locals);

        // attempt to send the email
        trySend(slug, locals);

    }

};

/**
* Generate an email address formatted name from a name object or a string
*
* Ideally email addresses use the pattern: "John Doe <john@doe.com>",
* which allows email clients to register a display name and email address
*
* You can pass this function a plain email, which it will return
* Or you can pass an object and it will assemlble the proper "name <email>" formatting
*
* Expected object should be this:
*
* objOrString: {
*     fullName: 'First Last',
*     email: 'johndoe@gmail.com'
* }
*
* @example buildAddress(matt@gmail.com)
*    // return "matt@gmail.com"
*
* @example buildAddress({fullName : 'Matt Miller'})
*    // throw error, email is missing!
*
* @example buildAddress({fullName : 'Matt Miller', email: 'matt@gmail.com'})
*    // return "Matt Miller <matt@gmail.com>"
*
*
* @todo convert our variable to lowercase, since thats much easier to remember
*
*/
var buildAddress = function(objOrString) {

    var formattedAddress;

    if(!objOrString) {
        throw Error('You must provide at least an email string');
    }

    if(typeof objOrString === 'string') {
        formattedAddress = objOrString;
        return formattedAddress;
    }

    if(typeof objOrString !== 'string' && !objOrString.email) {
        throw Error('You must provide a valid email');
    }

    formattedAddress = objOrString.email;

    if(objOrString.fullName) {
        formattedAddress = objOrString.fullName + " <" + formattedAddress + ">";
    }

    return formattedAddress;

};

/**
* Function to check if an object matches an object format.
* Returns false or the obj if match.
*
*/
var matchFormat = function(format, obj) {

    var match = true;
    var formatted = {};

    _.each(format, function(value, key) {
        if(!obj[key]) {
            match = false;
            //console.warn(key + ' doesn\'t exist in obj');
            return;
        } else {
            //console.log(key + ' = ' + value);
            formatted[key] = obj[key];
        }
    });

    if(!match) {

        console.warn('Pattern didn\'t match format. Please try again with:');
        console.warn(format);

        fs.exists(obj, function(exists) {
            if(exists) {
                console.log('Although not in the proper format, this is a real file. We can try to fudge it. We won\'t be doing it now though.');
            } else {
                console.log('No hope :(');
                return;
            }
        });

    } else {
        return formatted;
    }

};

/**
* Validates format for attachments. Currenly only validates File Stream
* @todo support Binary Buffer and String if needed
*
* If you only send an array of filePaths, we'll be nice enought to generate the proper
* structure for you :)
*
*/
var validateAttachments = function(attachments) {

    if(typeof attachments !== 'object' && typeof attachments !== 'array') {
        throw Error('Attachment should be an array (For multiple) or an object');
    }

    var format = {
        fileName: null,     // file name
        filePath: null,     // path to file
        cid: null           // should be as unique as possible
    };

    _.each(attachments, function(attachment) {
        attachment = matchFormat(format, attachment);
    });

    return attachments;

};

/**
* Logging function for delopment and testing mode
*
*/
var logEmail = function(options) {
    console.info('-----------------------------------');
    console.info('TESTING, TESTING, read all about it');
    console.info('-----------------------------------');
    console.info(options);
    console.info('-----------------------------------');
};

/**
* Function compiles template, and sends email to a single user
*
*
*/
var trySend = function(templateSlug, locals) {

    /**
    * Initialize the email template generator
    *
    */
    emailTemplates(localConfig.templatesDir, function (err, template) {

        if(err) {
            console.error(err);
        }

        // Send a single email
        template(templateSlug, locals.variables, function (err, html, text) {

            if(err) {
                console.error(err);
            }

            // Assemble our final options, we do this here so we can easily log or email them.
            var finalOptions = {

                // basic information
                from: buildAddress(localConfig.from),
                to: buildAddress(locals.to),
                subject: locals.subject,

                // options that creates text automatically? We set it specifically in our template
                // generateTextFromHTML: true,

                // will not always be present
                attachments: locals.attachments ? validateAttachments(locals.attachments) : null,
                headers: locals.headers ? locals.headers : null,

                // text and html are returned from our template builder
                text: text,
                html: html

            };

            /**
            * PreventSend will be flagged true if we are in development || testing mode.
            * This prevents us from sending un wanted emails to random people.
            */
            if(preventSend) {

                logEmail(finalOptions);

            } else {
               // Send email using our default transport
                transport.sendMail(finalOptions, function (err, responseStatus) {
                    if (err) {
                        console.log(err);
                    } else {
                        console.log(responseStatus.message);
                    }
                });
            }

        });

    });

};<|endoftext|>