<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import { isElloAndroid } from '../../lib/jello'
import {
  BoltIcon,
  CircleIcon,
  GridIcon,
  ListIcon,
  SearchIcon,
  SparklesIcon,
} from '../assets/Icons'
import {
  NavbarLayoutTool,
  NavbarLink,
  NavbarMark,
  NavbarMorePostsButton,
  NavbarOmniButton,
  NavbarProfile,
} from './NavbarParts'
import { CategoryTabBar } from '../tabs/CategoryTabBar'
import NotificationsContainer from '../../containers/NotificationsContainer'
import { css, media, modifier, parent, select } from '../../styles/jss'
import * as s from '../../styles/jso'

const navbarStyle = css(
  s.fixed,
  s.fullWidth,
  { height: 80, top: -80, right: 0, left: 0 },
  s.zNavbar,
  s.bgcWhite,
  modifier('isPaginatoring', select('.navbar-content', { transform: 'translate3d(0, 100%, 0)' })),
  select('.isNavbarHidden ~ &:hover .navbar-content',
    {
      transitionDelay: '350ms',
      transform: 'translate3d(0, 100%, 0)',
    }),
  select('&.hide:hover .navbar-content',
    {
      transitionDelay: '350ms',
      transform: 'translate3d(0, 100%, 0)',
    }),
  parent('.isOmnibarActive', s.overflowHidden, s.pointerNone, s.bgcTransparent, s.opacity0),
  select('.isOnboardingView ~ &', s.displayNone),
  media(s.minBreak3,
    select('.no-touch .isNavbarHidden ~ .Discover + &:hover .navbar-content', { transitionDelay: '1.5s' }),
  ),
)

const grabberStyle = css(
  s.relative,
  s.block,
  s.fullWidth,
  s.p0,
  {
    height: 0,
    margin: 0,
    overflow: 'hidden',
  },
  select(
    '&:hover',
    { cursor: 'pointer' },
  ),
  select(
    '.isNavbarHidden ~ .Navbar &',
    { height: 15, marginTop: 0 },
  ),
  select(
    '.Navbar.hide &',
    { height: 15, marginTop: 0 },
    media(s.maxBreak3,
      { display: 'none' },
    ),
  ),
)

const wrapperStyle = css(
  s.relative,
  s.p10,
  s.fullWidth,
  s.bgcWhite,
  { transition: 'transform 350ms ease', transform: 'translate3d(0, calc(100%), 0)' },
  { height: 80, margin: 0 },
  select('.isNavbarHidden ~ .Navbar &', { transform: 'translate3d(0, 0, 0)' }),
  select('.Navbar.hide &', { transform: 'translate3d(0, 0, 0)' }),
  media(s.minBreak3,
    s.p20,
    { borderBottom: '1px solid #f2f2f2' },
  ),
)

const containerStyle = css(
  s.relative,
  s.block,
  s.maxSiteWidth,
  s.fullWidth,
  { margin: '0 auto', height: 80 },
)

const mainStyle = css(
  s.maxSiteWidth,
  s.mxAuto,
  s.relative,
)

const linksStyle = css(
  s.absolute,
  { top: 0, left: 0 },
  s.nowrap,
  media(s.minBreak3,
    parent('.isLoggedIn', { top: 'calc(50% + 5px)', right: 200, left: 'auto', marginTop: -20 }),
    parent('.isLoggedOut', { top: 'calc(50% + 5px)', right: 175, left: 'auto', marginTop: -20 }),
    parent('.isOmnibarActive .Navbar >', s.absolute, { transform: 'translate3d(400px, 0, 0)' }),
  ),
)

export const NavbarLoggedOut = ({
  categoryTabs,
  areCategoriesSubscribed,
  deviceSize,
  isLightBoxActive,
  hasLoadMoreButton,
  onClickLoadMorePosts,
  onClickNavbarMark,
  pathname,
}, { onClickArtistInvites, onClickLogin, onClickSignup }) => {
  const isTruncatedHeader = deviceSize === 'mobile' || deviceSize === 'tablet'
  return (
    <nav className={`Navbar ${navbarStyle}`} >
      <div className={`navbar-content ${wrapperStyle}`}>
        <div className={`navbar-container ${containerStyle}`}>
          <div className={`NavbarMain ${mainStyle}`}>
            <NavbarMark onClick={onClickNavbarMark} />
            {hasLoadMoreButton ? <NavbarMorePostsButton onClick={onClickLoadMorePosts} /> : null}
            <div className={`NavbarLinks ${linksStyle}`}>
              {isTruncatedHeader &&
                <NavbarLink
                  className="LabelOnly"
                  label="Editorial"
                  pathname={pathname}
                  to="/"
                />
              }
              <NavbarLink
                className="LabelOnly"
                label="Artist Invites"
                onClick={onClickArtistInvites}
                pathname={pathname}
                to="/artist-invites"
              />
              <NavbarLink
                className="LabelOnly"
                icon={<SparklesIcon />}
                label="Discover"
                pathname={pathname}
                to="/discover"
              />
              <NavbarLink
                className="LabelOnly"
                label="Giveaways"
                pathname={pathname}
                to="/elloartgiveaways"
              />
              <NavbarLink
                className="IconOnly"
                icon={<SearchIcon />}
                label="Search"
                pathname={pathname}
                to="/search"
              />

            <NavbarLink
              className="LabelOnly isLogin"
              label="Login"
              onClick={onClickLogin}
              pathname={pathname}
              to="/enter"
            />
            <NavbarLink
              className="LabelOnly isSignUp"
              label="Sign Up"
              onClick={onClickSignup}
              pathname={pathname}
              to="/join"
            />

        {categoryTabs && !isLightBoxActive &&
          <CategoryTabBar
            pathname={pathname}
            tabs={categoryTabs}
            areCategoriesSubscribed={areCategoriesSubscribed}
            subscribed={false}
            deviceSize={deviceSize}
          />
        }

  )
}

NavbarLoggedOut.propTypes = {
  categoryTabs: PropTypes.array,
  areCategoriesSubscribed: PropTypes.bool,
  deviceSize: PropTypes.string.isRequired,
  isLightBoxActive: PropTypes.bool,
  hasLoadMoreButton: PropTypes.bool.isRequired,
  onClickLoadMorePosts: PropTypes.func.isRequired,
  onClickNavbarMark: PropTypes.func.isRequired,
  pathname: PropTypes.string.isRequired,
}
NavbarLoggedOut.defaultProps = {
  categoryTabs: null,
  isLightBoxActive: false,
  areCategoriesSubscribed: false,
}
NavbarLoggedOut.contextTypes = {
  onClickArtistInvites: PropTypes.func.isRequired,
  onClickLogin: PropTypes.func.isRequired,
  onClickSignup: PropTypes.func.isRequired,
}

export const NavbarLoggedIn = ({
  artistInvitesInProfileMenu,
  avatar,
  categoryTabs,
  areCategoriesSubscribed,
  deviceSize,
  hasLoadMoreButton,
  isBrand,
  isGridMode,
  isLightBoxActive,
  isLayoutToolHidden,
  isNotificationsActive,
  isNotificationsUnread,
  isProfileMenuActive,
  notificationCategory,
  onClickAvatar,
  onClickLoadMorePosts,
  onClickNavbarMark,
  onClickNotification,
  onClickOmniButton,
  onClickToggleLayoutMode,
  onDragLeaveStreamLink,
  onDragOverOmniButton,
  onDragOverStreamLink,
  onDropStreamLink,
  onLogOut,
  pathname,
  username,
  innerWidth,
}, { onClickArtistInvites }) => {
  const isTruncatedHeader = deviceSize === 'mobile' || deviceSize === 'tablet'
  return (
    <nav className={`Navbar ${navbarStyle}`}>
      <div className={`navbar-content ${wrapperStyle}`}>
        <div className={`navbar-container ${containerStyle}`}>
          <div className={`NavbarMain ${mainStyle}`}>
            <NavbarMark onClick={onClickNavbarMark} />
            {hasLoadMoreButton ? <NavbarMorePostsButton onClick={onClickLoadMorePosts} /> : null}
            <div className={`NavbarLinks ${linksStyle}`}>
              {isTruncatedHeader &&
                <NavbarLink
                  className="LabelOnly"
                  label="Editorial"
                  pathname={pathname}
                  to="/"
                />
              }
              {!artistInvitesInProfileMenu &&
                <NavbarLink
                  className="LabelOnly"
                  label="Artist Invites"
                  onClick={onClickArtistInvites}
                  pathname={pathname}
                  to="/artist-invites"
                />
              }
              <NavbarLink
                className="LabelOnly"
                icon={<SparklesIcon />}
                label="Discover"
                pathname={pathname}
                to="/discover/subscribed"
              />
              <NavbarLink
                className="LabelOnly"
                icon={<CircleIcon />}
                label="Following"
                onDragLeave={onDragLeaveStreamLink}
                onDragOver={onDragOverStreamLink}
                onDrop={onDropStreamLink}
                pathname={pathname}
                to="/following"
              />
              {innerWidth > 500
                  ? <NavbarLink
                    className="LabelOnly"
                    label="Giveaways"
                    pathname={pathname}
                    to="/elloartgiveaways"
                  /> : null }
              <NavbarLink
                className={classNames('IconOnly', { isNotificationsUnread })}
                icon={<BoltIcon />}
                label="Notifications"
                onClick={isElloAndroid() || isTruncatedHeader ? null : onClickNotification}
                pathname={pathname}
                to={`/notifications${notificationCategory}`}
              />
              <NavbarLink
                className="IconOnly"
                icon={<SearchIcon />}
                label="Search"
                pathname={pathname}
                to="/search"
              />

            <NavbarProfile
              artistInvitesInProfileMenu={artistInvitesInProfileMenu}
              avatar={avatar}
              isBrand={isBrand}
              isProfileMenuActive={isProfileMenuActive}
              onClickAvatar={onClickAvatar}
              onLogOut={onLogOut}
              username={username}
              innerWidth={innerWidth}
            />
            {isTruncatedHeader && !isLayoutToolHidden ?
              <NavbarLayoutTool
                icon={isGridMode ? <ListIcon /> : <GridIcon />}
                onClick={onClickToggleLayoutMode}
              /> : null
            }
            <NavbarOmniButton
              onClick={onClickOmniButton}
              onDragOver={onDragOverOmniButton}
            />
            {isNotificationsActive ?
              <NotificationsContainer isModal /> : null
            }

        {categoryTabs && !isLightBoxActive &&
          <CategoryTabBar
            pathname={pathname}
            tabs={categoryTabs}
            areCategoriesSubscribed={areCategoriesSubscribed}
            subscribed
            deviceSize={deviceSize}
          />
        }

      <div className={`grabber ${grabberStyle}`} />

  )
}

NavbarLoggedIn.propTypes = {
  artistInvitesInProfileMenu: PropTypes.bool.isRequired,
  avatar: PropTypes.object,
  categoryTabs: PropTypes.array,
  areCategoriesSubscribed: PropTypes.bool,
  deviceSize: PropTypes.string.isRequired,
  hasLoadMoreButton: PropTypes.bool.isRequired,
  isBrand: PropTypes.bool.isRequired,
  isGridMode: PropTypes.bool,
  isLightBoxActive: PropTypes.bool,
  isLayoutToolHidden: PropTypes.bool.isRequired,
  isNotificationsActive: PropTypes.bool.isRequired,
  isNotificationsUnread: PropTypes.bool.isRequired,
  isProfileMenuActive: PropTypes.bool.isRequired,
  notificationCategory: PropTypes.string.isRequired,
  onClickAvatar: PropTypes.func.isRequired,
  onClickLoadMorePosts: PropTypes.func.isRequired,
  onClickNavbarMark: PropTypes.func.isRequired,
  onClickNotification: PropTypes.func.isRequired,
  onClickOmniButton: PropTypes.func.isRequired,
  onClickToggleLayoutMode: PropTypes.func.isRequired,
  onDragLeaveStreamLink: PropTypes.func.isRequired,
  onDragOverOmniButton: PropTypes.func.isRequired,
  onDragOverStreamLink: PropTypes.func.isRequired,
  onDropStreamLink: PropTypes.func.isRequired,
  onLogOut: PropTypes.func.isRequired,
  pathname: PropTypes.string.isRequired,
  username: PropTypes.string,
  innerWidth: PropTypes.number,
}
NavbarLoggedIn.defaultProps = {
  avatar: null,
  categoryTabs: null,
  areCategoriesSubscribed: false,
  isGridMode: false,
  isLightBoxActive: false,
  username: null,
  innerWidth: null,
}
NavbarLoggedIn.contextTypes = {
  onClickArtistInvites: PropTypes.func.isRequired,
}

<commit_msg>navbar - notifications - restore old bool behavior<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import { isElloAndroid } from '../../lib/jello'
import {
  BoltIcon,
  CircleIcon,
  GridIcon,
  ListIcon,
  SearchIcon,
  SparklesIcon,
} from '../assets/Icons'
import {
  NavbarLayoutTool,
  NavbarLink,
  NavbarMark,
  NavbarMorePostsButton,
  NavbarOmniButton,
  NavbarProfile,
} from './NavbarParts'
import { CategoryTabBar } from '../tabs/CategoryTabBar'
import NotificationsContainer from '../../containers/NotificationsContainer'
import { css, media, modifier, parent, select } from '../../styles/jss'
import * as s from '../../styles/jso'

const navbarStyle = css(
  s.fixed,
  s.fullWidth,
  { height: 80, top: -80, right: 0, left: 0 },
  s.zNavbar,
  s.bgcWhite,
  modifier('isPaginatoring', select('.navbar-content', { transform: 'translate3d(0, 100%, 0)' })),
  select('.isNavbarHidden ~ &:hover .navbar-content',
    {
      transitionDelay: '350ms',
      transform: 'translate3d(0, 100%, 0)',
    }),
  select('&.hide:hover .navbar-content',
    {
      transitionDelay: '350ms',
      transform: 'translate3d(0, 100%, 0)',
    }),
  parent('.isOmnibarActive', s.overflowHidden, s.pointerNone, s.bgcTransparent, s.opacity0),
  select('.isOnboardingView ~ &', s.displayNone),
  media(s.minBreak3,
    select('.no-touch .isNavbarHidden ~ .Discover + &:hover .navbar-content', { transitionDelay: '1.5s' }),
  ),
)

const grabberStyle = css(
  s.relative,
  s.block,
  s.fullWidth,
  s.p0,
  {
    height: 0,
    margin: 0,
    overflow: 'hidden',
  },
  select(
    '&:hover',
    { cursor: 'pointer' },
  ),
  select(
    '.isNavbarHidden ~ .Navbar &',
    { height: 15, marginTop: 0 },
  ),
  select(
    '.Navbar.hide &',
    { height: 15, marginTop: 0 },
    media(s.maxBreak3,
      { display: 'none' },
    ),
  ),
)

const wrapperStyle = css(
  s.relative,
  s.p10,
  s.fullWidth,
  s.bgcWhite,
  { transition: 'transform 350ms ease', transform: 'translate3d(0, calc(100%), 0)' },
  { height: 80, margin: 0 },
  select('.isNavbarHidden ~ .Navbar &', { transform: 'translate3d(0, 0, 0)' }),
  select('.Navbar.hide &', { transform: 'translate3d(0, 0, 0)' }),
  media(s.minBreak3,
    s.p20,
    { borderBottom: '1px solid #f2f2f2' },
  ),
)

const containerStyle = css(
  s.relative,
  s.block,
  s.maxSiteWidth,
  s.fullWidth,
  { margin: '0 auto', height: 80 },
)

const mainStyle = css(
  s.maxSiteWidth,
  s.mxAuto,
  s.relative,
)

const linksStyle = css(
  s.absolute,
  { top: 0, left: 0 },
  s.nowrap,
  media(s.minBreak3,
    parent('.isLoggedIn', { top: 'calc(50% + 5px)', right: 200, left: 'auto', marginTop: -20 }),
    parent('.isLoggedOut', { top: 'calc(50% + 5px)', right: 175, left: 'auto', marginTop: -20 }),
    parent('.isOmnibarActive .Navbar >', s.absolute, { transform: 'translate3d(400px, 0, 0)' }),
  ),
)

export const NavbarLoggedOut = ({
  categoryTabs,
  areCategoriesSubscribed,
  deviceSize,
  isLightBoxActive,
  hasLoadMoreButton,
  onClickLoadMorePosts,
  onClickNavbarMark,
  pathname,
}, { onClickArtistInvites, onClickLogin, onClickSignup }) => {
  const isTruncatedHeader = deviceSize === 'mobile' || deviceSize === 'tablet'
  return (
    <nav className={`Navbar ${navbarStyle}`} >
      <div className={`navbar-content ${wrapperStyle}`}>
        <div className={`navbar-container ${containerStyle}`}>
          <div className={`NavbarMain ${mainStyle}`}>
            <NavbarMark onClick={onClickNavbarMark} />
            {hasLoadMoreButton ? <NavbarMorePostsButton onClick={onClickLoadMorePosts} /> : null}
            <div className={`NavbarLinks ${linksStyle}`}>
              {isTruncatedHeader &&
                <NavbarLink
                  className="LabelOnly"
                  label="Editorial"
                  pathname={pathname}
                  to="/"
                />
              }
              <NavbarLink
                className="LabelOnly"
                label="Artist Invites"
                onClick={onClickArtistInvites}
                pathname={pathname}
                to="/artist-invites"
              />
              <NavbarLink
                className="LabelOnly"
                icon={<SparklesIcon />}
                label="Discover"
                pathname={pathname}
                to="/discover"
              />
              <NavbarLink
                className="LabelOnly"
                label="Giveaways"
                pathname={pathname}
                to="/elloartgiveaways"
              />
              <NavbarLink
                className="IconOnly"
                icon={<SearchIcon />}
                label="Search"
                pathname={pathname}
                to="/search"
              />

            <NavbarLink
              className="LabelOnly isLogin"
              label="Login"
              onClick={onClickLogin}
              pathname={pathname}
              to="/enter"
            />
            <NavbarLink
              className="LabelOnly isSignUp"
              label="Sign Up"
              onClick={onClickSignup}
              pathname={pathname}
              to="/join"
            />

        {categoryTabs && !isLightBoxActive &&
          <CategoryTabBar
            pathname={pathname}
            tabs={categoryTabs}
            areCategoriesSubscribed={areCategoriesSubscribed}
            subscribed={false}
            deviceSize={deviceSize}
          />
        }

  )
}

NavbarLoggedOut.propTypes = {
  categoryTabs: PropTypes.array,
  areCategoriesSubscribed: PropTypes.bool,
  deviceSize: PropTypes.string.isRequired,
  isLightBoxActive: PropTypes.bool,
  hasLoadMoreButton: PropTypes.bool.isRequired,
  onClickLoadMorePosts: PropTypes.func.isRequired,
  onClickNavbarMark: PropTypes.func.isRequired,
  pathname: PropTypes.string.isRequired,
}
NavbarLoggedOut.defaultProps = {
  categoryTabs: null,
  isLightBoxActive: false,
  areCategoriesSubscribed: false,
}
NavbarLoggedOut.contextTypes = {
  onClickArtistInvites: PropTypes.func.isRequired,
  onClickLogin: PropTypes.func.isRequired,
  onClickSignup: PropTypes.func.isRequired,
}

export const NavbarLoggedIn = ({
  artistInvitesInProfileMenu,
  avatar,
  categoryTabs,
  areCategoriesSubscribed,
  deviceSize,
  hasLoadMoreButton,
  isBrand,
  isGridMode,
  isLightBoxActive,
  isLayoutToolHidden,
  isNotificationsActive,
  isNotificationsUnread,
  isProfileMenuActive,
  notificationCategory,
  onClickAvatar,
  onClickLoadMorePosts,
  onClickNavbarMark,
  onClickNotification,
  onClickOmniButton,
  onClickToggleLayoutMode,
  onDragLeaveStreamLink,
  onDragOverOmniButton,
  onDragOverStreamLink,
  onDropStreamLink,
  onLogOut,
  pathname,
  username,
  innerWidth,
}, { onClickArtistInvites }) => {
  const isTruncatedHeader = deviceSize === 'mobile' || deviceSize === 'tablet'
  return (
    <nav className={`Navbar ${navbarStyle}`}>
      <div className={`navbar-content ${wrapperStyle}`}>
        <div className={`navbar-container ${containerStyle}`}>
          <div className={`NavbarMain ${mainStyle}`}>
            <NavbarMark onClick={onClickNavbarMark} />
            {hasLoadMoreButton ? <NavbarMorePostsButton onClick={onClickLoadMorePosts} /> : null}
            <div className={`NavbarLinks ${linksStyle}`}>
              {isTruncatedHeader &&
                <NavbarLink
                  className="LabelOnly"
                  label="Editorial"
                  pathname={pathname}
                  to="/"
                />
              }
              {!artistInvitesInProfileMenu &&
                <NavbarLink
                  className="LabelOnly"
                  label="Artist Invites"
                  onClick={onClickArtistInvites}
                  pathname={pathname}
                  to="/artist-invites"
                />
              }
              <NavbarLink
                className="LabelOnly"
                icon={<SparklesIcon />}
                label="Discover"
                pathname={pathname}
                to="/discover/subscribed"
              />
              <NavbarLink
                className="LabelOnly"
                icon={<CircleIcon />}
                label="Following"
                onDragLeave={onDragLeaveStreamLink}
                onDragOver={onDragOverStreamLink}
                onDrop={onDropStreamLink}
                pathname={pathname}
                to="/following"
              />
              {innerWidth > 500
                  ? <NavbarLink
                    className="LabelOnly"
                    label="Giveaways"
                    pathname={pathname}
                    to="/elloartgiveaways"
                  /> : null }
              <NavbarLink
                className={classNames('IconOnly', { isNotificationsUnread })}
                icon={<BoltIcon />}
                label="Notifications"
                onClick={isElloAndroid() || isTruncatedHeader ? null : onClickNotification}
                pathname={pathname}
                to={`/notifications${notificationCategory}`}
              />
              <NavbarLink
                className="IconOnly"
                icon={<SearchIcon />}
                label="Search"
                pathname={pathname}
                to="/search"
              />

            <NavbarProfile
              artistInvitesInProfileMenu={artistInvitesInProfileMenu}
              avatar={avatar}
              isBrand={isBrand}
              isProfileMenuActive={isProfileMenuActive}
              onClickAvatar={onClickAvatar}
              onLogOut={onLogOut}
              username={username}
              innerWidth={innerWidth}
            />
            {isTruncatedHeader && !isLayoutToolHidden ?
              <NavbarLayoutTool
                icon={isGridMode ? <ListIcon /> : <GridIcon />}
                onClick={onClickToggleLayoutMode}
              /> : null
            }
            <NavbarOmniButton
              onClick={onClickOmniButton}
              onDragOver={onDragOverOmniButton}
            />
            {!isTruncatedHeader && isNotificationsActive ?
              <NotificationsContainer isModal /> : null
            }

        {categoryTabs && !isLightBoxActive &&
          <CategoryTabBar
            pathname={pathname}
            tabs={categoryTabs}
            areCategoriesSubscribed={areCategoriesSubscribed}
            subscribed
            deviceSize={deviceSize}
          />
        }

      <div className={`grabber ${grabberStyle}`} />

  )
}

NavbarLoggedIn.propTypes = {
  artistInvitesInProfileMenu: PropTypes.bool.isRequired,
  avatar: PropTypes.object,
  categoryTabs: PropTypes.array,
  areCategoriesSubscribed: PropTypes.bool,
  deviceSize: PropTypes.string.isRequired,
  hasLoadMoreButton: PropTypes.bool.isRequired,
  isBrand: PropTypes.bool.isRequired,
  isGridMode: PropTypes.bool,
  isLightBoxActive: PropTypes.bool,
  isLayoutToolHidden: PropTypes.bool.isRequired,
  isNotificationsActive: PropTypes.bool.isRequired,
  isNotificationsUnread: PropTypes.bool.isRequired,
  isProfileMenuActive: PropTypes.bool.isRequired,
  notificationCategory: PropTypes.string.isRequired,
  onClickAvatar: PropTypes.func.isRequired,
  onClickLoadMorePosts: PropTypes.func.isRequired,
  onClickNavbarMark: PropTypes.func.isRequired,
  onClickNotification: PropTypes.func.isRequired,
  onClickOmniButton: PropTypes.func.isRequired,
  onClickToggleLayoutMode: PropTypes.func.isRequired,
  onDragLeaveStreamLink: PropTypes.func.isRequired,
  onDragOverOmniButton: PropTypes.func.isRequired,
  onDragOverStreamLink: PropTypes.func.isRequired,
  onDropStreamLink: PropTypes.func.isRequired,
  onLogOut: PropTypes.func.isRequired,
  pathname: PropTypes.string.isRequired,
  username: PropTypes.string,
  innerWidth: PropTypes.number,
}
NavbarLoggedIn.defaultProps = {
  avatar: null,
  categoryTabs: null,
  areCategoriesSubscribed: false,
  isGridMode: false,
  isLightBoxActive: false,
  username: null,
  innerWidth: null,
}
NavbarLoggedIn.contextTypes = {
  onClickArtistInvites: PropTypes.func.isRequired,
}

<|endoftext|>


<commit_before>/**
 * dirPagination - AngularJS module for paginating (almost) anything.
 *
 *
 * Credits
 * =======
 *
 * Daniel Tabuenca: https://groups.google.com/d/msg/angular/an9QpzqIYiM/r8v-3W1X5vcJ
 * for the idea on how to dynamically invoke the ng-repeat directive.
 *
 * I borrowed a couple of lines and a few attribute names from the AngularUI Bootstrap project:
 * https://github.com/angular-ui/bootstrap/blob/master/src/pagination/pagination.js
 *
 * Copyright 2014 Michael Bromley <michael@michaelbromley.co.uk>
 */

(function() {

    /**
     * Config
     */
    var moduleName = 'angularUtils.directives.dirPagination';
    var templatePath = 'directives/pagination/dirPagination.tpl.html';

    /**
     * Module
     */
    var module;
    try {
        module = angular.module(moduleName);
    } catch(err) {
        // named module does not exist, so create one
        module = angular.module(moduleName, []);
    }

    module.directive('dirPaginate', ['$compile', '$parse', '$timeout', 'paginationService', function($compile, $parse, $timeout, paginationService) {
        return  {
            terminal: true,
            compile: function(element, attrs){
                attrs.$set('ngRepeat', attrs.dirPaginate); // Add ng-repeat to the dom element
                element.removeAttr(attrs.$attr.dirPaginate); // Remove the dir-paginate attribute to prevent infinite recursion of compilation

                var expression = attrs.dirPaginate;
                // regex taken directly from https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js#L211
                var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);

                var filterPattern = /\|\s*itemsPerPage\s*:[^|]*/;
                if (match[2].match(filterPattern) === null) {
                    throw 'pagination directive: the \'itemsPerPage\' filter must be set.';
                }
                var itemsPerPageFilterRemoved = match[2].replace(filterPattern, '');
                var collectionGetter = $parse(itemsPerPageFilterRemoved);

                return function(scope, element, attrs){
                    var paginationId;
                    var compiled =  $compile(element); // we manually compile the element again, as we have now swapped dir-paginate for an ng-repeat

                    paginationId = attrs.paginationId || '__default';
                    paginationService.registerInstance(paginationId);

                    var currentPageGetter;
                    if (attrs.currentPage) {
                        currentPageGetter = $parse(attrs.currentPage);
                    } else {
                        // if the current-page attribute was not set, we'll make our own
                        var defaultCurrentPage = paginationId + '__currentPage';
                        scope[defaultCurrentPage] = 1;
                        currentPageGetter = $parse(defaultCurrentPage);
                    }
                    paginationService.setCurrentPageParser(paginationId, currentPageGetter, scope);

                    if (typeof attrs.totalItems !== 'undefined') {
                        paginationService.setAsyncModeTrue(paginationId);
                        scope.$watch(function() {
                            return $parse(attrs.totalItems)(scope);
                        }, function (result) {
                            if (0 <= result) {
                                paginationService.setCollectionLength(paginationId, result);
                            }
                        });
                    } else {
                        scope.$watchCollection(function() {
                            return collectionGetter(scope);
                        }, function(collection) {
                            if (collection) {
                                paginationService.setCollectionLength(paginationId, collection.length);
                            }
                        });
                    }

                    // Delegate to the link function returned by the new compilation of the ng-repeat
                    compiled(scope);
                };
            }
        };
    }]);

    module.directive('dirPaginationControls', ['paginationService', function(paginationService) {
        var numberRegex = /^\d+$/;
        /**
         * Generate an array of page numbers (or the '...' string) which is used in an ng-repeat to generate the
         * links used in pagination
         *
         * @param currentPage
         * @param rowsPerPage
         * @param paginationRange
         * @param collectionLength
         * @returns {Array}
         */
        function generatePagesArray(currentPage, collectionLength, rowsPerPage, paginationRange) {
            var pages = [];
            var totalPages = Math.ceil(collectionLength / rowsPerPage);
            var halfWay = Math.ceil(paginationRange / 2);
            var position;

            if (currentPage <= halfWay) {
                position = 'start';
            } else if (totalPages - halfWay < currentPage) {
                position = 'end';
            } else {
                position = 'middle';
            }

            var ellipsesNeeded = paginationRange < totalPages;
            var i = 1;
            while (i <= totalPages && i <= paginationRange) {
                var pageNumber = calculatePageNumber(i, currentPage, paginationRange, totalPages);

                var openingEllipsesNeeded = (i === 2 && (position === 'middle' || position === 'end'));
                var closingEllipsesNeeded = (i === paginationRange - 1 && (position === 'middle' || position === 'start'));
                if (ellipsesNeeded && (openingEllipsesNeeded || closingEllipsesNeeded)) {
                    pages.push('...');
                } else {
                    pages.push(pageNumber);
                }
                i ++;
            }
            return pages;
        }

        /**
         * Given the position in the sequence of pagination links [i], figure out what page number corresponds to that position.
         *
         * @param i
         * @param currentPage
         * @param paginationRange
         * @param totalPages
         * @returns {*}
         */
        function calculatePageNumber(i, currentPage, paginationRange, totalPages) {
            var halfWay = Math.ceil(paginationRange/2);
            if (i === paginationRange) {
                return totalPages;
            } else if (i === 1) {
                return i;
            } else if (paginationRange < totalPages) {
                if (totalPages - halfWay < currentPage) {
                    return totalPages - paginationRange + i;
                } else if (halfWay < currentPage) {
                    return currentPage - halfWay + i;
                } else {
                    return i;
                }
            } else {
                return i;
            }
        }

        return {
            restrict: 'AE',
            templateUrl: function(elem, attrs) {
                return attrs.templateUrl || templatePath;
            },
            scope: {
                maxSize: '=?',
                onPageChange: '&?'
            },
            link: function(scope, element, attrs) {
                var paginationId;
                paginationId = attrs.paginationId || '__default';
                if (!scope.maxSize) { scope.maxSize = 9; }
                scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : true;
                scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : false;

                if (!paginationService.isRegistered(paginationId)) {
                    var idMessage = (paginationId !== '__default') ? ' (id: ' + paginationId + ') ' : ' ';
                    throw 'pagination directive: the pagination controls' + idMessage + 'cannot be used without the corresponding pagination directive.';
                }

                var paginationRange = Math.max(scope.maxSize, 5);
                scope.pages = [];
                scope.pagination = {
                    last: 1,
                    current: 1
                };

                scope.$watch(function() {
                    return (paginationService.getCollectionLength(paginationId) + 1) * paginationService.getItemsPerPage(paginationId);
                }, function(length) {
                    if (0 < length) {
                        generatePagination();
                    }
                });

                scope.$watch(function() {
                    return paginationService.getCurrentPage(paginationId);
                }, function(currentPage) {
                    goToPage(currentPage);
                });

                scope.setCurrent = function(num) {
                    if (isValidPageNumber(num)) {
                        paginationService.setCurrentPage(paginationId, num);
                    }
                };

                function goToPage(num) {
                    if (isValidPageNumber(num)) {
                        scope.pages = generatePagesArray(num, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange);
                        scope.pagination.current = num;

                        // if a callback has been set, then call it with the page number as an argument
                        if (scope.onPageChange) {
                            scope.onPageChange({ newPageNumber : num });
                        }
                    }
                }

                function generatePagination() {
                    scope.pages = generatePagesArray(1, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange);
                    scope.pagination.current = parseInt(paginationService.getCurrentPage(paginationId));
                    scope.pagination.last = scope.pages[scope.pages.length - 1];
                    if (scope.pagination.last < scope.pagination.current) {
                        scope.setCurrent(scope.pagination.last);
                    }
                }

                function isValidPageNumber(num) {
                    return (numberRegex.test(num) && (0 < num && num <= scope.pagination.last));
                }
            }
        };
    }]);

    module.filter('itemsPerPage', ['paginationService', function(paginationService) {
        return function(collection, itemsPerPage, paginationId) {
            if (typeof (paginationId) === 'undefined') {
                paginationId = '__default';
            }
            if (!paginationService.isRegistered(paginationId)) {
                throw 'pagination directive: the itemsPerPage id argument (id: ' + paginationId + ') does not match a registered pagination-id.';
            }
            var end;
            var start;
            if (collection instanceof Array) {
                itemsPerPage = parseInt(itemsPerPage) || 9999999999;
                if (paginationService.isAsyncMode(paginationId)) {
                    start = 0;
                } else {
                    start = (paginationService.getCurrentPage(paginationId) - 1) * itemsPerPage;
                }
                end = start + itemsPerPage;
                paginationService.setItemsPerPage(paginationId, itemsPerPage);

                return collection.slice(start, end);
            } else {
                return collection;
            }
        };
    }]);

    module.service('paginationService', function() {
        var instances = {};
        var lastRegisteredInstance;

        this.registerInstance = function(instanceId) {
            if (typeof instances[instanceId] === 'undefined') {
                instances[instanceId] = {
                    asyncMode: false
                };
                lastRegisteredInstance = instanceId;
            }
        };

        this.isRegistered = function(instanceId) {
            return (typeof instances[instanceId] !== 'undefined');
        };

        this.getLastInstanceId = function() {
            return lastRegisteredInstance;
        };

        this.setCurrentPageParser = function(instanceId, val, scope) {
            instances[instanceId].currentPageParser = val;
            instances[instanceId].context = scope;
        };
        this.setCurrentPage = function(instanceId, val) {
            instances[instanceId].currentPageParser.assign(instances[instanceId].context, val);
        };
        this.getCurrentPage = function(instanceId) {
            return instances[instanceId].currentPageParser(instances[instanceId].context);
        };

        this.setItemsPerPage = function(instanceId, val) {
            instances[instanceId].itemsPerPage = val;
        };
        this.getItemsPerPage = function(instanceId) {
            return instances[instanceId].itemsPerPage;
        };

        this.setCollectionLength = function(instanceId, val) {
            instances[instanceId].collectionLength = val;
        };
        this.getCollectionLength = function(instanceId) {
            return instances[instanceId].collectionLength;
        };

        this.setAsyncModeTrue = function(instanceId) {
            instances[instanceId].asyncMode = true;
        };

        this.isAsyncMode = function(instanceId) {
            return instances[instanceId].asyncMode;
        };
    });
})();
<commit_msg>Fixed two pagination bugs<commit_after>/**
 * dirPagination - AngularJS module for paginating (almost) anything.
 *
 *
 * Credits
 * =======
 *
 * Daniel Tabuenca: https://groups.google.com/d/msg/angular/an9QpzqIYiM/r8v-3W1X5vcJ
 * for the idea on how to dynamically invoke the ng-repeat directive.
 *
 * I borrowed a couple of lines and a few attribute names from the AngularUI Bootstrap project:
 * https://github.com/angular-ui/bootstrap/blob/master/src/pagination/pagination.js
 *
 * Copyright 2014 Michael Bromley <michael@michaelbromley.co.uk>
 */

(function() {

    /**
     * Config
     */
    var moduleName = 'angularUtils.directives.dirPagination';
    var templatePath = 'directives/pagination/dirPagination.tpl.html';

    /**
     * Module
     */
    var module;
    try {
        module = angular.module(moduleName);
    } catch(err) {
        // named module does not exist, so create one
        module = angular.module(moduleName, []);
    }

    module.directive('dirPaginate', ['$compile', '$parse', '$timeout', 'paginationService', function($compile, $parse, $timeout, paginationService) {
        return  {
            terminal: true,
            compile: function(element, attrs){
                attrs.$set('ngRepeat', attrs.dirPaginate); // Add ng-repeat to the dom element
                element.removeAttr(attrs.$attr.dirPaginate); // Remove the dir-paginate attribute to prevent infinite recursion of compilation

                var expression = attrs.dirPaginate;
                // regex taken directly from https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js#L211
                var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);

                var filterPattern = /\|\s*itemsPerPage\s*:[^|]*/;
                if (match[2].match(filterPattern) === null) {
                    throw 'pagination directive: the \'itemsPerPage\' filter must be set.';
                }
                var itemsPerPageFilterRemoved = match[2].replace(filterPattern, '');
                var collectionGetter = $parse(itemsPerPageFilterRemoved);

                return function(scope, element, attrs){
                    var paginationId;
                    var compiled =  $compile(element); // we manually compile the element again, as we have now swapped dir-paginate for an ng-repeat

                    paginationId = attrs.paginationId || '__default';
                    paginationService.registerInstance(paginationId);

                    var currentPageGetter;
                    if (attrs.currentPage) {
                        currentPageGetter = $parse(attrs.currentPage);
                    } else {
                        // if the current-page attribute was not set, we'll make our own
                        var defaultCurrentPage = paginationId + '__currentPage';
                        scope[defaultCurrentPage] = 1;
                        currentPageGetter = $parse(defaultCurrentPage);
                    }
                    paginationService.setCurrentPageParser(paginationId, currentPageGetter, scope);

                    if (typeof attrs.totalItems !== 'undefined') {
                        paginationService.setAsyncModeTrue(paginationId);
                        scope.$watch(function() {
                            return $parse(attrs.totalItems)(scope);
                        }, function (result) {
                            if (0 <= result) {
                                paginationService.setCollectionLength(paginationId, result);
                            }
                        });
                    } else {
                        scope.$watchCollection(function() {
                            return collectionGetter(scope);
                        }, function(collection) {
                            if (collection) {
                                paginationService.setCollectionLength(paginationId, collection.length);
                            }
                        });
                    }

                    // Delegate to the link function returned by the new compilation of the ng-repeat
                    compiled(scope);
                };
            }
        };
    }]);

    module.directive('dirPaginationControls', ['paginationService', function(paginationService) {
        var numberRegex = /^\d+$/;
        /**
         * Generate an array of page numbers (or the '...' string) which is used in an ng-repeat to generate the
         * links used in pagination
         *
         * @param currentPage
         * @param rowsPerPage
         * @param paginationRange
         * @param collectionLength
         * @returns {Array}
         */
        function generatePagesArray(currentPage, collectionLength, rowsPerPage, paginationRange) {
            var pages = [];
            var totalPages = Math.ceil(collectionLength / rowsPerPage);
            var halfWay = Math.ceil(paginationRange / 2);
            var position;

            if (currentPage <= halfWay) {
                position = 'start';
            } else if (totalPages - halfWay < currentPage) {
                position = 'end';
            } else {
                position = 'middle';
            }

            var ellipsesNeeded = paginationRange < totalPages;
            var i = 1;
            while (i <= totalPages && i <= paginationRange) {
                var pageNumber = calculatePageNumber(i, currentPage, paginationRange, totalPages);

                var openingEllipsesNeeded = (i === 2 && (position === 'middle' || position === 'end'));
                var closingEllipsesNeeded = (i === paginationRange - 1 && (position === 'middle' || position === 'start'));
                if (ellipsesNeeded && (openingEllipsesNeeded || closingEllipsesNeeded)) {
                    pages.push('...');
                } else {
                    pages.push(pageNumber);
                }
                i ++;
            }
            return pages;
        }

        /**
         * Given the position in the sequence of pagination links [i], figure out what page number corresponds to that position.
         *
         * @param i
         * @param currentPage
         * @param paginationRange
         * @param totalPages
         * @returns {*}
         */
        function calculatePageNumber(i, currentPage, paginationRange, totalPages) {
            var halfWay = Math.ceil(paginationRange/2);
            if (i === paginationRange) {
                return totalPages;
            } else if (i === 1) {
                return i;
            } else if (paginationRange < totalPages) {
                if (totalPages - halfWay < currentPage) {
                    return totalPages - paginationRange + i;
                } else if (halfWay < currentPage) {
                    return currentPage - halfWay + i;
                } else {
                    return i;
                }
            } else {
                return i;
            }
        }

        return {
            restrict: 'AE',
            templateUrl: function(elem, attrs) {
                return attrs.templateUrl || templatePath;
            },
            scope: {
                maxSize: '=?',
                onPageChange: '&?'
            },
            link: function(scope, element, attrs) {
                var paginationId;
                paginationId = attrs.paginationId || '__default';
                if (!scope.maxSize) { scope.maxSize = 9; }
                scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : true;
                scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : false;

                if (!paginationService.isRegistered(paginationId)) {
                    var idMessage = (paginationId !== '__default') ? ' (id: ' + paginationId + ') ' : ' ';
                    throw 'pagination directive: the pagination controls' + idMessage + 'cannot be used without the corresponding pagination directive.';
                }

                var paginationRange = Math.max(scope.maxSize, 5);
                scope.pages = [];
                scope.pagination = {
                    last: 1,
                    current: 1
                };

                scope.$watch(function() {
                    return (paginationService.getCollectionLength(paginationId) + 1) * paginationService.getItemsPerPage(paginationId);
                }, function(length) {
                    if (0 < length) {
                        generatePagination();
                    }
                });

                scope.$watch(function() {
                    return paginationService.getCurrentPage(paginationId);
                }, function(currentPage, previousPage) {
                    if (currentPage != previousPage) {
                        goToPage(currentPage);
                    }
                });

                scope.setCurrent = function(num) {
                    if (isValidPageNumber(num)) {
                        paginationService.setCurrentPage(paginationId, num);
                    }
                };

                function goToPage(num) {
                    if (isValidPageNumber(num)) {
                        scope.pages = generatePagesArray(num, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange);
                        scope.pagination.current = num;

                        // if a callback has been set, then call it with the page number as an argument
                        if (scope.onPageChange) {
                            scope.onPageChange({ newPageNumber : num });
                        }
                    }
                }

                function generatePagination() {
                    scope.pages = generatePagesArray(paginationService.getCurrentPage(paginationId), paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange);
                    scope.pagination.current = parseInt(paginationService.getCurrentPage(paginationId));
                    scope.pagination.last = scope.pages[scope.pages.length - 1];
                    if (scope.pagination.last < scope.pagination.current) {
                        scope.setCurrent(scope.pagination.last);
                    }
                }

                function isValidPageNumber(num) {
                    return (numberRegex.test(num) && (0 < num && num <= scope.pagination.last));
                }
            }
        };
    }]);

    module.filter('itemsPerPage', ['paginationService', function(paginationService) {
        return function(collection, itemsPerPage, paginationId) {
            if (typeof (paginationId) === 'undefined') {
                paginationId = '__default';
            }
            if (!paginationService.isRegistered(paginationId)) {
                throw 'pagination directive: the itemsPerPage id argument (id: ' + paginationId + ') does not match a registered pagination-id.';
            }
            var end;
            var start;
            if (collection instanceof Array) {
                itemsPerPage = parseInt(itemsPerPage) || 9999999999;
                if (paginationService.isAsyncMode(paginationId)) {
                    start = 0;
                } else {
                    start = (paginationService.getCurrentPage(paginationId) - 1) * itemsPerPage;
                }
                end = start + itemsPerPage;
                paginationService.setItemsPerPage(paginationId, itemsPerPage);

                return collection.slice(start, end);
            } else {
                return collection;
            }
        };
    }]);

    module.service('paginationService', function() {
        var instances = {};
        var lastRegisteredInstance;

        this.registerInstance = function(instanceId) {
            if (typeof instances[instanceId] === 'undefined') {
                instances[instanceId] = {
                    asyncMode: false
                };
                lastRegisteredInstance = instanceId;
            }
        };

        this.isRegistered = function(instanceId) {
            return (typeof instances[instanceId] !== 'undefined');
        };

        this.getLastInstanceId = function() {
            return lastRegisteredInstance;
        };

        this.setCurrentPageParser = function(instanceId, val, scope) {
            instances[instanceId].currentPageParser = val;
            instances[instanceId].context = scope;
        };
        this.setCurrentPage = function(instanceId, val) {
            instances[instanceId].currentPageParser.assign(instances[instanceId].context, val);
        };
        this.getCurrentPage = function(instanceId) {
            return instances[instanceId].currentPageParser(instances[instanceId].context);
        };

        this.setItemsPerPage = function(instanceId, val) {
            instances[instanceId].itemsPerPage = val;
        };
        this.getItemsPerPage = function(instanceId) {
            return instances[instanceId].itemsPerPage;
        };

        this.setCollectionLength = function(instanceId, val) {
            instances[instanceId].collectionLength = val;
        };
        this.getCollectionLength = function(instanceId) {
            return instances[instanceId].collectionLength;
        };

        this.setAsyncModeTrue = function(instanceId) {
            instances[instanceId].asyncMode = true;
        };

        this.isAsyncMode = function(instanceId) {
            return instances[instanceId].asyncMode;
        };
    });
})();
<|endoftext|>


<commit_before>describe('$resilient', function () {
  var $resilient, $$resilient

  beforeEach(module('ngResilient'))

  beforeEach(inject(function (_$$resilient_, _$resilient_) {
    $resilient = _$resilient_
    $$resilient = _$$resilient_
  }))

  describe('API', function () {
    it('should expose the resilient API', function () {
      expect($$resilient).to.be.an('function')
    })

    it('should expose the library version', function () {
      expect($$resilient.VERSION).to.be.a('string')
    })

    it('should expose the main constructor', function () {
      expect($resilient).to.be.an('function')
    })
  })

  describe('create client with discovery options', function () {
    var resilient = null

    it('should create a client', function () {
      resilient = $resilient({
        service: { servers: [ location.origin + '/server' ] }
      })
    })

    it('should perform a request', function () {
      resilient.get('/test')
    })

    it('should perform a POST request', function () {
      resilient.post('/test')
    })

    it('should perform a PUT request', function () {
      resilient.put('/test')
    })

    it('should perform a DELETE request', function () {
      resilient.del('/test')
    })

    it('should perform a PATCH request', function () {
      resilient.patch('/test')
    })

    it('should perform a HEAD request', function () {
      resilient.head('/test')
    })
  })

})
<commit_msg>chore(test)<commit_after>describe('$resilient', function () {
  var $resilient, $$resilient

  function proxy(options, cb) {
    cb(null, { status: 200 })
  }

  beforeEach(module('ngResilient'))

  beforeEach(inject(function (_$$resilient_, _$resilient_) {
    $resilient = _$resilient_
    $$resilient = _$$resilient_
  }))

  describe('API', function () {
    it('should expose the resilient API', function () {
      expect($$resilient).to.be.an('function')
    })

    it('should expose the library version', function () {
      expect($$resilient.VERSION).to.be.a('string')
    })

    it('should expose the main constructor', function () {
      expect($resilient).to.be.an('function')
    })
  })

  describe('create client with discovery options', function () {
    var resilient = null

    it('should create a client', function () {
      resilient = $resilient({
        service: { servers: [ location.origin + '/server' ] }
      })
      resilient.resilient.setHttpClient(proxy)
    })

    it('should perform GET request', function () {
      resilient.get('/test')
    })

    it('should perform a POST request', function () {
      resilient.post('/test')
    })

    it('should perform a PUT request', function () {
      resilient.put('/test')
    })

    it('should perform a DELETE request', function () {
      resilient.del('/test')
    })

    it('should perform a PATCH request', function () {
      resilient.patch('/test')
    })

    it('should perform a HEAD request', function () {
      resilient.head('/test')
    })
  })

})
<|endoftext|>


<commit_before>'use strict';

let http = require('http');
let express = require('express');
let test = require('tape');
let freeport = require('freeport');
let pipe = require('pipe-io');
let restafary = require('..');

let get = (path, root, fn) => {
    freeport((error, port) => {
        let app = express();
        let server = http.createServer(app);
        let ip = '127.0.0.1';

        app.use(restafary({
            root: root
        }));

        server.listen(port, ip, () => {
            http.get(`http://127.0.0.1:${port}/${path}`, (res) => {
                fn(res, () => {
                    server.close();
                });
            });
        });
    });
}

test('restafary: path traversal beyond root', (t) => {
    get('fs..%2f..%2fetc/passwd', '/tmp', (res, cb) => {
        pipe.getBody(res, (error, body) => {
            cb();
            t.notOk(error, `should not be error: ${error}`);
            t.equal(body, 'Path /etc/passwd beyond root /tmp!', 'should return beyond root message');
            t.end();
        });
    });
});

test('restafary: path traversal', (t) => {
    get('fs/bin', '/', (res, cb) => {
        pipe.getBody(res, (error, body) => {
            let fn = () => {
                JSON.parse(body);
            };
            t.notOk(error, `should not be error: ${error}`);
            t.doesNotThrow(fn, 'should not throw');
            cb();
            t.end();
        });
    });
});

test('restafary: path traversal, not default root', (t) => {
    get('fs/local', '/usr', (res, cb) => {
        pipe.getBody(res, (error, body) => {
            let fn = () => {
                JSON.parse(body);
            };
            t.notOk(error, `should not be error: ${error}`);
            t.doesNotThrow(fn, 'should not throw');
            cb();
            t.end();
        });
    });
});

<commit_msg>chore(restafary) add ";"<commit_after>'use strict';

let http = require('http');
let express = require('express');
let test = require('tape');
let freeport = require('freeport');
let pipe = require('pipe-io');
let restafary = require('..');

let get = (path, root, fn) => {
    freeport((error, port) => {
        let app = express();
        let server = http.createServer(app);
        let ip = '127.0.0.1';

        app.use(restafary({
            root: root
        }));

        server.listen(port, ip, () => {
            http.get(`http://127.0.0.1:${port}/${path}`, (res) => {
                fn(res, () => {
                    server.close();
                });
            });
        });
    });
};

test('restafary: path traversal beyond root', (t) => {
    get('fs..%2f..%2fetc/passwd', '/tmp', (res, cb) => {
        pipe.getBody(res, (error, body) => {
            cb();
            t.notOk(error, `should not be error: ${error}`);
            t.equal(body, 'Path /etc/passwd beyond root /tmp!', 'should return beyond root message');
            t.end();
        });
    });
});

test('restafary: path traversal', (t) => {
    get('fs/bin', '/', (res, cb) => {
        pipe.getBody(res, (error, body) => {
            let fn = () => {
                JSON.parse(body);
            };
            t.notOk(error, `should not be error: ${error}`);
            t.doesNotThrow(fn, 'should not throw');
            cb();
            t.end();
        });
    });
});

test('restafary: path traversal, not default root', (t) => {
    get('fs/local', '/usr', (res, cb) => {
        pipe.getBody(res, (error, body) => {
            let fn = () => {
                JSON.parse(body);
            };
            t.notOk(error, `should not be error: ${error}`);
            t.doesNotThrow(fn, 'should not throw');
            cb();
            t.end();
        });
    });
});

<|endoftext|>


<commit_before>'use strict'

const extend = require('extend')
const _ = require('lodash')
const path = require('path')
const Inert = require('inert')
const Vision = require('vision')
const HapiSwagger = require('hapi-swagger')
const Mrhorse = require('mrhorse')
const logging = require('loggin')
const logUtil = require('./utilities/log-util')
const chalk = require('chalk')
const restHelperFactory = require('./utilities/rest-helper-factory')
const handlerHelper = require('./utilities/handler-helper')
const joiHelper = require('./utilities/joi-mongoose-helper')
const testHelper = require('./utilities/test-helper')
const modelGenerator = require('./utilities/model-generator')
const apiGenerator = require('./utilities/api-generator')
const defaultConfig = require('./config')
const globals = require('./globals')

const internals = {
  modelsGenerated: false,
  globalModels: {}
}

module.exports = {
  plugin: {
    name: 'rest-hapi',
    version: '1.0.0',
    register
  },
  config: defaultConfig,
  generateModels: generateModels,
  list: handlerHelper.list,
  find: handlerHelper.find,
  create: handlerHelper.create,
  update: handlerHelper.update,
  deleteOne: handlerHelper.deleteOne,
  deleteMany: handlerHelper.deleteMany,
  addOne: handlerHelper.addOne,
  removeOne: handlerHelper.removeOne,
  addMany: handlerHelper.addMany,
  removeMany: handlerHelper.removeMany,
  getAll: handlerHelper.getAll,
  logger: {},
  getLogger: getLogger,
  logUtil: logUtil,
  joiHelper: joiHelper,
  testHelper: testHelper
}

async function register(server, options) {
  let config = defaultConfig

  // Overwrite the default config with config set by the user
  extend(true, config, options.config)
  module.exports.config = config

  let Log = getLogger('api')

  module.exports.logger = Log

  // Add the logger object to the request object for access later
  server.ext('onRequest', (request, h) => {
    request.logger = Log

    return h.continue
  })

  const mongoose = mongooseInit(options.mongoose, Log, config)

  logUtil.logActionStart(Log, 'Initializing Server')

  let models

  if (internals.modelsGenerated) {
    // Models generated previously
    models = internals.globalModels
  } else {
    try {
      models = await modelGenerator(mongoose, Log, config)
    } catch (err) {
      if (err.message.includes('no such file')) {
        Log.error(
          'The policies directory provided does not exist. ' +
            "Try setting the 'policyPath' property of the config file."
        )
      } else {
        throw err
      }
    }
  }

  registerHapiSwagger(server, Log, config)

  registerMrHorse(server, Log, config)

  return generateRoutes(server, mongoose, models, Log, config)
}

/**
 * Allows the user to pre-generate the models before the routes in case the models are needed
 * in other plugins (ex: auth plugin might require user model)
 * @param mongoose
 * @returns {*}
 */
function generateModels(mongoose) {
  internals.modelsGenerated = true

  let config = defaultConfig

  extend(true, config, module.exports.config)

  let Log = getLogger('models')

  module.exports.logger = Log

  return modelGenerator(mongoose, Log, config).then(function(models) {
    internals.globalModels = models
    return models
  })
}

/**
 * Get a new Log object with a root label.
 * @param label: The root label for the Log.
 * @returns {*}
 */
function getLogger(label) {
  let config = defaultConfig

  extend(true, config, module.exports.config)

  let rootLogger = logging.getLogger(chalk.gray(label))

  rootLogger.logLevel = config.loglevel

  return rootLogger
}

/**
 * Connect mongoose and add to globals.
 * @param mongoose
 * @param logger
 * @param config
 * @returns {*}
 */
function mongooseInit(mongoose, logger, config) {
  const Log = logger.bind('mongoose-init')

  mongoose.Promise = Promise

  logUtil.logActionStart(
    Log,
    'Connecting to Database',
    _.omit(config.mongo, ['pass'])
  )

  mongoose.connect(config.mongo.URI)

  globals.mongoose = mongoose

  Log.log('mongoose connected')

  return mongoose
}

/**
 * Register and configure the mrhorse plugin.
 * @param server
 * @param logger
 * @param config
 * @returns {Promise<void>}
 */
async function registerMrHorse(server, logger, config) {
  const Log = logger.bind('register-MrHorse')

  let policyPath = ''

  if (config.enablePolicies) {
    if (config.absolutePolicyPath === true) {
      policyPath = config.policyPath
    } else {
      policyPath = __dirname.replace(
        'node_modules/rest-hapi',
        config.policyPath
      )
    }
  } else {
    policyPath = path.join(__dirname, '/policies')
  }
  await server.register([
    {
      plugin: Mrhorse,
      options: {
        policyDirectory: policyPath
      }
    }
  ])

  if (config.enablePolicies) {
    await server.plugins.mrhorse.loadPolicies(server, {
      policyDirectory: path.join(__dirname, '/policies')
    })
  }

  Log.info('MrHorse plugin registered')
}

/**
 * Register and configure the hapi-swagger plugin.
 * @param server
 * @param logger
 * @param config
 * @returns {Promise<void>}
 */
async function registerHapiSwagger(server, logger, config) {
  const Log = logger.bind('register-hapi-swagger')

  let swaggerOptions = {
    documentationPath: '/',
    info: {
      title: config.appTitle,
      version: config.version
    },
    host: config.swaggerHost,
    expanded: config.docExpansion,
    swaggerUI: config.enableSwaggerUI,
    documentationPage: config.enableSwaggerUI,
    schemes: config.enableSwaggerHttps ? ['https'] : ['http'],
    reuseDefinitions: false
  }

  await server.register([
    Inert,
    Vision,
    { plugin: HapiSwagger, options: swaggerOptions }
  ])

  Log.info('hapi-swagger plugin registered')
}

function generateRoutes(server, mongoose, models, logger, config) {
  const Log = logger.bind()

  const restHelper = restHelperFactory(logger, mongoose, server)

  for (let modelKey in models) {
    // Generate endpoints for all of the models
    let model = models[modelKey]
    restHelper.generateRoutes(server, model, { models: models })
  }

  // Generate custom endpoints
  return apiGenerator(server, mongoose, Log, config)
}
<commit_msg>feat: Add option to disable swagger<commit_after>'use strict'

const extend = require('extend')
const _ = require('lodash')
const path = require('path')
const Inert = require('inert')
const Vision = require('vision')
const HapiSwagger = require('hapi-swagger')
const Mrhorse = require('mrhorse')
const logging = require('loggin')
const logUtil = require('./utilities/log-util')
const chalk = require('chalk')
const restHelperFactory = require('./utilities/rest-helper-factory')
const handlerHelper = require('./utilities/handler-helper')
const joiHelper = require('./utilities/joi-mongoose-helper')
const testHelper = require('./utilities/test-helper')
const modelGenerator = require('./utilities/model-generator')
const apiGenerator = require('./utilities/api-generator')
const defaultConfig = require('./config')
const globals = require('./globals')

const internals = {
  modelsGenerated: false,
  globalModels: {}
}

module.exports = {
  plugin: {
    name: 'rest-hapi',
    version: '1.0.0',
    register
  },
  config: defaultConfig,
  generateModels: generateModels,
  list: handlerHelper.list,
  find: handlerHelper.find,
  create: handlerHelper.create,
  update: handlerHelper.update,
  deleteOne: handlerHelper.deleteOne,
  deleteMany: handlerHelper.deleteMany,
  addOne: handlerHelper.addOne,
  removeOne: handlerHelper.removeOne,
  addMany: handlerHelper.addMany,
  removeMany: handlerHelper.removeMany,
  getAll: handlerHelper.getAll,
  logger: {},
  getLogger: getLogger,
  logUtil: logUtil,
  joiHelper: joiHelper,
  testHelper: testHelper
}

async function register(server, options) {
  let config = defaultConfig

  // Overwrite the default config with config set by the user
  extend(true, config, options.config)
  module.exports.config = config

  let Log = getLogger('api')

  module.exports.logger = Log

  // Add the logger object to the request object for access later
  server.ext('onRequest', (request, h) => {
    request.logger = Log

    return h.continue
  })

  const mongoose = mongooseInit(options.mongoose, Log, config)

  logUtil.logActionStart(Log, 'Initializing Server')

  let models

  if (internals.modelsGenerated) {
    // Models generated previously
    models = internals.globalModels
  } else {
    try {
      models = await modelGenerator(mongoose, Log, config)
    } catch (err) {
      if (err.message.includes('no such file')) {
        Log.error(
          'The policies directory provided does not exist. ' +
            "Try setting the 'policyPath' property of the config file."
        )
      } else {
        throw err
      }
    }
  }

  if (!config.disableSwagger) {
    await registerHapiSwagger(server, Log, config)
  }

  registerMrHorse(server, Log, config)

  return generateRoutes(server, mongoose, models, Log, config)
}

/**
 * Allows the user to pre-generate the models before the routes in case the models are needed
 * in other plugins (ex: auth plugin might require user model)
 * @param mongoose
 * @returns {*}
 */
function generateModels(mongoose) {
  internals.modelsGenerated = true

  let config = defaultConfig

  extend(true, config, module.exports.config)

  let Log = getLogger('models')

  module.exports.logger = Log

  return modelGenerator(mongoose, Log, config).then(function(models) {
    internals.globalModels = models
    return models
  })
}

/**
 * Get a new Log object with a root label.
 * @param label: The root label for the Log.
 * @returns {*}
 */
function getLogger(label) {
  let config = defaultConfig

  extend(true, config, module.exports.config)

  let rootLogger = logging.getLogger(chalk.gray(label))

  rootLogger.logLevel = config.loglevel

  return rootLogger
}

/**
 * Connect mongoose and add to globals.
 * @param mongoose
 * @param logger
 * @param config
 * @returns {*}
 */
function mongooseInit(mongoose, logger, config) {
  const Log = logger.bind('mongoose-init')

  mongoose.Promise = Promise

  logUtil.logActionStart(
    Log,
    'Connecting to Database',
    _.omit(config.mongo, ['pass'])
  )

  mongoose.connect(config.mongo.URI)

  globals.mongoose = mongoose

  Log.log('mongoose connected')

  return mongoose
}

/**
 * Register and configure the mrhorse plugin.
 * @param server
 * @param logger
 * @param config
 * @returns {Promise<void>}
 */
async function registerMrHorse(server, logger, config) {
  const Log = logger.bind('register-MrHorse')

  let policyPath = ''

  if (config.enablePolicies) {
    if (config.absolutePolicyPath === true) {
      policyPath = config.policyPath
    } else {
      policyPath = __dirname.replace(
        'node_modules/rest-hapi',
        config.policyPath
      )
    }
  } else {
    policyPath = path.join(__dirname, '/policies')
  }
  await server.register([
    {
      plugin: Mrhorse,
      options: {
        policyDirectory: policyPath
      }
    }
  ])

  if (config.enablePolicies) {
    await server.plugins.mrhorse.loadPolicies(server, {
      policyDirectory: path.join(__dirname, '/policies')
    })
  }

  Log.info('MrHorse plugin registered')
}

/**
 * Register and configure the hapi-swagger plugin.
 * @param server
 * @param logger
 * @param config
 * @returns {Promise<void>}
 */
async function registerHapiSwagger(server, logger, config) {
  const Log = logger.bind('register-hapi-swagger')

  let swaggerOptions = {
    documentationPath: '/',
    info: {
      title: config.appTitle,
      version: config.version
    },
    host: config.swaggerHost,
    expanded: config.docExpansion,
    swaggerUI: config.enableSwaggerUI,
    documentationPage: config.enableSwaggerUI,
    schemes: config.enableSwaggerHttps ? ['https'] : ['http'],
    reuseDefinitions: false
  }

  await server.register([
    Inert,
    Vision,
    { plugin: HapiSwagger, options: swaggerOptions }
  ])

  Log.info('hapi-swagger plugin registered')
}

function generateRoutes(server, mongoose, models, logger, config) {
  const Log = logger.bind()

  const restHelper = restHelperFactory(logger, mongoose, server)

  for (let modelKey in models) {
    // Generate endpoints for all of the models
    let model = models[modelKey]
    restHelper.generateRoutes(server, model, { models: models })
  }

  // Generate custom endpoints
  return apiGenerator(server, mongoose, Log, config)
}
<|endoftext|>