<commit_before>/* global __NEXT_DATA__ */

import { parse, format } from 'url'
import EventEmitter from '../EventEmitter'
import shallowEquals from '../shallow-equals'
import PQueue from '../p-queue'
import { loadGetInitialProps, getURL, warn, execOnce } from '../utils'
import { _rewriteUrlForNextExport } from './'

export default class Router {
  constructor (pathname, query, as, { pageLoader, Component, ErrorComponent, err } = {}) {
    // represents the current component key
    this.route = toRoute(pathname)

    // set up the component cache (by route keys)
    this.components = {}
    // We should not keep the cache, if there's an error
    // Otherwise, this cause issues when when going back and
    // come again to the errored page.
    if (Component !== ErrorComponent) {
      this.components[this.route] = { Component, err }
    }

    // Handling Router Events
    this.events = new EventEmitter()

    this.pageLoader = pageLoader
    this.prefetchQueue = new PQueue({ concurrency: 2 })
    this.ErrorComponent = ErrorComponent
    this.pathname = pathname
    this.query = query
    this.asPath = as
    this.subscriptions = new Set()
    this.componentLoadCancel = null
    this.onPopState = this.onPopState.bind(this)

    if (typeof window !== 'undefined') {
      // in order for `e.state` to work on the `onpopstate` event
      // we have to register the initial route upon initialization
      this.changeState('replaceState', format({ pathname, query }), getURL())

      window.addEventListener('popstate', this.onPopState)
    }
  }

  async onPopState (e) {
    if (!e.state) {
      // We get state as undefined for two reasons.
      //  1. With older safari (< 8) and older chrome (< 34)
      //  2. When the URL changed with #
      //
      // In the both cases, we don't need to proceed and change the route.
      // (as it's already changed)
      // But we can simply replace the state with the new changes.
      // Actually, for (1) we don't need to nothing. But it's hard to detect that event.
      // So, doing the following for (1) does no harm.
      const { pathname, query } = this
      this.changeState('replaceState', format({ pathname, query }), getURL())
      return
    }

    const { url, as, options } = e.state
    this.replace(url, as, options)
  }

  update (route, Component) {
    const data = this.components[route]
    if (!data) {
      throw new Error(`Cannot update unavailable route: ${route}`)
    }

    const newData = { ...data, Component }
    this.components[route] = newData

    if (route === this.route) {
      this.notify(newData)
    }
  }

  async reload (route) {
    delete this.components[route]
    this.pageLoader.clearCache(route)

    if (route !== this.route) return

    const { pathname, query } = this
    const url = window.location.href

    this.events.emit('routeChangeStart', url)
    const routeInfo = await this.getRouteInfo(route, pathname, query, url)
    const { error } = routeInfo

    if (error && error.cancelled) {
      return
    }

    this.notify(routeInfo)

    if (error) {
      this.events.emit('routeChangeError', error, url)
      throw error
    }

    this.events.emit('routeChangeComplete', url)
  }

  back () {
    window.history.back()
  }

  push (url, as = url, options = {}) {
    return this.change('pushState', url, as, options)
  }

  replace (url, as = url, options = {}) {
    return this.change('replaceState', url, as, options)
  }

  async change (method, _url, _as, options) {
    // If url and as provided as an object representation,
    // we'll format them into the string version here.
    const url = typeof _url === 'object' ? format(_url) : _url
    let as = typeof _as === 'object' ? format(_as) : _as

    // Add the ending slash to the paths. So, we can serve the
    // "<page>/index.html" directly for the SSR page.
    if (__NEXT_DATA__.nextExport) {
      as = _rewriteUrlForNextExport(as)
    }

    this.abortComponentLoad(as)

    // If the url change is only related to a hash change
    // We should not proceed. We should only change the state.
    if (this.onlyAHashChange(as)) {
      this.changeState(method, url, as)
      this.scrollToHash(as)
      return
    }

    const { pathname, query } = parse(url, true)

    // If asked to change the current URL we should reload the current page
    // (not location.reload() but reload getInitalProps and other Next.js stuffs)
    // We also need to set the method = replaceState always
    // as this should not go into the history (That's how browsers work)
    if (!this.urlIsNew(pathname, query)) {
      method = 'replaceState'
    }

    const route = toRoute(pathname)
    const { shallow = false } = options
    let routeInfo = null

    this.events.emit('routeChangeStart', as)

    // If shallow === false and other conditions met, we reuse the
    // existing routeInfo for this route.
    // Because of this, getInitialProps would not run.
    if (shallow && this.isShallowRoutingPossible(route)) {
      routeInfo = this.components[route]
    } else {
      routeInfo = await this.getRouteInfo(route, pathname, query, as)
    }

    const { error } = routeInfo

    if (error && error.cancelled) {
      return false
    }

    this.events.emit('beforeHistoryChange', as)
    this.changeState(method, url, as, options)
    const hash = window.location.hash.substring(1)

    this.set(route, pathname, query, as, { ...routeInfo, hash })

    if (error) {
      this.events.emit('routeChangeError', error, as)
      throw error
    }

    this.events.emit('routeChangeComplete', as)
    return true
  }

  changeState (method, url, as, options = {}) {
    if (window.frameElement) {
      execOnce(warn)(`Warning: You're using Next.js inside an iFrame. Browser history is disabled.`)
    } else if (method !== 'pushState' || getURL() !== as) {
      window.history[method]({ url, as, options }, null, as)
    }
  }

  async getRouteInfo (route, pathname, query, as) {
    let routeInfo = null

    try {
      routeInfo = this.components[route]
      if (!routeInfo) {
        routeInfo = { Component: await this.fetchComponent(route, as) }
      }

      const { Component } = routeInfo

      if (typeof Component !== 'function') {
        throw new Error(`The default export is not a React Component in page: "${pathname}"`)
      }

      const ctx = { pathname, query, asPath: as }
      routeInfo.props = await this.getInitialProps(Component, ctx)

      this.components[route] = routeInfo
    } catch (err) {
      if (err.code === 'PAGE_LOAD_ERROR') {
        // If we can't load the page it could be one of following reasons
        //  1. Page doesn't exists
        //  2. Page does exist in a different zone
        //  3. Internal error while loading the page

        // So, doing a hard reload is the proper way to deal with this.
        window.location.href = as

        // Changing the URL doesn't block executing the current code path.
        // So, we need to mark it as a cancelled error and stop the routing logic.
        err.cancelled = true
        return { error: err }
      }

      if (err.cancelled) {
        return { error: err }
      }

      const Component = this.ErrorComponent
      routeInfo = { Component, err }
      const ctx = { err, pathname, query }
      routeInfo.props = await this.getInitialProps(Component, ctx)

      routeInfo.error = err
    }

    return routeInfo
  }

  set (route, pathname, query, as, data) {
    this.route = route
    this.pathname = pathname
    this.query = query
    this.asPath = as
    this.notify(data)
  }

  onlyAHashChange (as) {
    if (!this.asPath) return false
    const [ oldUrlNoHash, oldHash ] = this.asPath.split('#')
    const [ newUrlNoHash, newHash ] = as.split('#')

    // If the urls are change, there's more than a hash change
    if (oldUrlNoHash !== newUrlNoHash) {
      return false
    }

    // If the hash has changed, then it's a hash only change.
    // This check is necessary to handle both the enter and
    // leave hash === '' cases. The identity case falls through
    // and is treated as a next reload.
    return oldHash !== newHash
  }

  scrollToHash (as) {
    const [ , hash ] = as.split('#')
    const el = document.getElementById(hash)
    if (el) {
      el.scrollIntoView()
    }
  }

  urlIsNew (pathname, query) {
    return this.pathname !== pathname || !shallowEquals(query, this.query)
  }

  isShallowRoutingPossible (route) {
    return (
      // If there's cached routeInfo for the route.
      Boolean(this.components[route]) &&
      // If the route is already rendered on the screen.
      this.route === route
    )
  }

  async prefetch (url) {
    // We don't add support for prefetch in the development mode.
    // If we do that, our on-demand-entries optimization won't performs better
    if (process.env.NODE_ENV === 'development') return

    const { pathname } = parse(url)
    const route = toRoute(pathname)
    return this.prefetchQueue.add(() => this.fetchRoute(route))
  }

  async fetchComponent (route, as) {
    let cancelled = false
    const cancel = this.componentLoadCancel = function () {
      cancelled = true
    }

    const Component = await this.fetchRoute(route)

    if (cancelled) {
      const error = new Error(`Abort fetching component for route: "${route}"`)
      error.cancelled = true
      throw error
    }

    if (cancel === this.componentLoadCancel) {
      this.componentLoadCancel = null
    }

    return Component
  }

  async getInitialProps (Component, ctx) {
    let cancelled = false
    const cancel = () => { cancelled = true }
    this.componentLoadCancel = cancel

    const props = await loadGetInitialProps(Component, ctx)

    if (cancel === this.componentLoadCancel) {
      this.componentLoadCancel = null
    }

    if (cancelled) {
      const err = new Error('Loading initial props cancelled')
      err.cancelled = true
      throw err
    }

    return props
  }

  async fetchRoute (route) {
    return await this.pageLoader.loadPage(route)
  }

  abortComponentLoad (as) {
    if (this.componentLoadCancel) {
      this.events.emit('routeChangeError', new Error('Route Cancelled'), as)
      this.componentLoadCancel()
      this.componentLoadCancel = null
    }
  }

  notify (data) {
    this.subscriptions.forEach((fn) => fn(data))
  }

  subscribe (fn) {
    this.subscriptions.add(fn)
    return () => this.subscriptions.delete(fn)
  }
}

function toRoute (path) {
  return path.replace(/\/$/, '') || '/'
}
<commit_msg>Return true instead of void (#3869)<commit_after>/* global __NEXT_DATA__ */

import { parse, format } from 'url'
import EventEmitter from '../EventEmitter'
import shallowEquals from '../shallow-equals'
import PQueue from '../p-queue'
import { loadGetInitialProps, getURL, warn, execOnce } from '../utils'
import { _rewriteUrlForNextExport } from './'

export default class Router {
  constructor (pathname, query, as, { pageLoader, Component, ErrorComponent, err } = {}) {
    // represents the current component key
    this.route = toRoute(pathname)

    // set up the component cache (by route keys)
    this.components = {}
    // We should not keep the cache, if there's an error
    // Otherwise, this cause issues when when going back and
    // come again to the errored page.
    if (Component !== ErrorComponent) {
      this.components[this.route] = { Component, err }
    }

    // Handling Router Events
    this.events = new EventEmitter()

    this.pageLoader = pageLoader
    this.prefetchQueue = new PQueue({ concurrency: 2 })
    this.ErrorComponent = ErrorComponent
    this.pathname = pathname
    this.query = query
    this.asPath = as
    this.subscriptions = new Set()
    this.componentLoadCancel = null
    this.onPopState = this.onPopState.bind(this)

    if (typeof window !== 'undefined') {
      // in order for `e.state` to work on the `onpopstate` event
      // we have to register the initial route upon initialization
      this.changeState('replaceState', format({ pathname, query }), getURL())

      window.addEventListener('popstate', this.onPopState)
    }
  }

  async onPopState (e) {
    if (!e.state) {
      // We get state as undefined for two reasons.
      //  1. With older safari (< 8) and older chrome (< 34)
      //  2. When the URL changed with #
      //
      // In the both cases, we don't need to proceed and change the route.
      // (as it's already changed)
      // But we can simply replace the state with the new changes.
      // Actually, for (1) we don't need to nothing. But it's hard to detect that event.
      // So, doing the following for (1) does no harm.
      const { pathname, query } = this
      this.changeState('replaceState', format({ pathname, query }), getURL())
      return
    }

    const { url, as, options } = e.state
    this.replace(url, as, options)
  }

  update (route, Component) {
    const data = this.components[route]
    if (!data) {
      throw new Error(`Cannot update unavailable route: ${route}`)
    }

    const newData = { ...data, Component }
    this.components[route] = newData

    if (route === this.route) {
      this.notify(newData)
    }
  }

  async reload (route) {
    delete this.components[route]
    this.pageLoader.clearCache(route)

    if (route !== this.route) return

    const { pathname, query } = this
    const url = window.location.href

    this.events.emit('routeChangeStart', url)
    const routeInfo = await this.getRouteInfo(route, pathname, query, url)
    const { error } = routeInfo

    if (error && error.cancelled) {
      return
    }

    this.notify(routeInfo)

    if (error) {
      this.events.emit('routeChangeError', error, url)
      throw error
    }

    this.events.emit('routeChangeComplete', url)
  }

  back () {
    window.history.back()
  }

  push (url, as = url, options = {}) {
    return this.change('pushState', url, as, options)
  }

  replace (url, as = url, options = {}) {
    return this.change('replaceState', url, as, options)
  }

  async change (method, _url, _as, options) {
    // If url and as provided as an object representation,
    // we'll format them into the string version here.
    const url = typeof _url === 'object' ? format(_url) : _url
    let as = typeof _as === 'object' ? format(_as) : _as

    // Add the ending slash to the paths. So, we can serve the
    // "<page>/index.html" directly for the SSR page.
    if (__NEXT_DATA__.nextExport) {
      as = _rewriteUrlForNextExport(as)
    }

    this.abortComponentLoad(as)

    // If the url change is only related to a hash change
    // We should not proceed. We should only change the state.
    if (this.onlyAHashChange(as)) {
      this.changeState(method, url, as)
      this.scrollToHash(as)
      return true
    }

    const { pathname, query } = parse(url, true)

    // If asked to change the current URL we should reload the current page
    // (not location.reload() but reload getInitalProps and other Next.js stuffs)
    // We also need to set the method = replaceState always
    // as this should not go into the history (That's how browsers work)
    if (!this.urlIsNew(pathname, query)) {
      method = 'replaceState'
    }

    const route = toRoute(pathname)
    const { shallow = false } = options
    let routeInfo = null

    this.events.emit('routeChangeStart', as)

    // If shallow === false and other conditions met, we reuse the
    // existing routeInfo for this route.
    // Because of this, getInitialProps would not run.
    if (shallow && this.isShallowRoutingPossible(route)) {
      routeInfo = this.components[route]
    } else {
      routeInfo = await this.getRouteInfo(route, pathname, query, as)
    }

    const { error } = routeInfo

    if (error && error.cancelled) {
      return false
    }

    this.events.emit('beforeHistoryChange', as)
    this.changeState(method, url, as, options)
    const hash = window.location.hash.substring(1)

    this.set(route, pathname, query, as, { ...routeInfo, hash })

    if (error) {
      this.events.emit('routeChangeError', error, as)
      throw error
    }

    this.events.emit('routeChangeComplete', as)
    return true
  }

  changeState (method, url, as, options = {}) {
    if (window.frameElement) {
      execOnce(warn)(`Warning: You're using Next.js inside an iFrame. Browser history is disabled.`)
    } else if (method !== 'pushState' || getURL() !== as) {
      window.history[method]({ url, as, options }, null, as)
    }
  }

  async getRouteInfo (route, pathname, query, as) {
    let routeInfo = null

    try {
      routeInfo = this.components[route]
      if (!routeInfo) {
        routeInfo = { Component: await this.fetchComponent(route, as) }
      }

      const { Component } = routeInfo

      if (typeof Component !== 'function') {
        throw new Error(`The default export is not a React Component in page: "${pathname}"`)
      }

      const ctx = { pathname, query, asPath: as }
      routeInfo.props = await this.getInitialProps(Component, ctx)

      this.components[route] = routeInfo
    } catch (err) {
      if (err.code === 'PAGE_LOAD_ERROR') {
        // If we can't load the page it could be one of following reasons
        //  1. Page doesn't exists
        //  2. Page does exist in a different zone
        //  3. Internal error while loading the page

        // So, doing a hard reload is the proper way to deal with this.
        window.location.href = as

        // Changing the URL doesn't block executing the current code path.
        // So, we need to mark it as a cancelled error and stop the routing logic.
        err.cancelled = true
        return { error: err }
      }

      if (err.cancelled) {
        return { error: err }
      }

      const Component = this.ErrorComponent
      routeInfo = { Component, err }
      const ctx = { err, pathname, query }
      routeInfo.props = await this.getInitialProps(Component, ctx)

      routeInfo.error = err
    }

    return routeInfo
  }

  set (route, pathname, query, as, data) {
    this.route = route
    this.pathname = pathname
    this.query = query
    this.asPath = as
    this.notify(data)
  }

  onlyAHashChange (as) {
    if (!this.asPath) return false
    const [ oldUrlNoHash, oldHash ] = this.asPath.split('#')
    const [ newUrlNoHash, newHash ] = as.split('#')

    // If the urls are change, there's more than a hash change
    if (oldUrlNoHash !== newUrlNoHash) {
      return false
    }

    // If the hash has changed, then it's a hash only change.
    // This check is necessary to handle both the enter and
    // leave hash === '' cases. The identity case falls through
    // and is treated as a next reload.
    return oldHash !== newHash
  }

  scrollToHash (as) {
    const [ , hash ] = as.split('#')
    const el = document.getElementById(hash)
    if (el) {
      el.scrollIntoView()
    }
  }

  urlIsNew (pathname, query) {
    return this.pathname !== pathname || !shallowEquals(query, this.query)
  }

  isShallowRoutingPossible (route) {
    return (
      // If there's cached routeInfo for the route.
      Boolean(this.components[route]) &&
      // If the route is already rendered on the screen.
      this.route === route
    )
  }

  async prefetch (url) {
    // We don't add support for prefetch in the development mode.
    // If we do that, our on-demand-entries optimization won't performs better
    if (process.env.NODE_ENV === 'development') return

    const { pathname } = parse(url)
    const route = toRoute(pathname)
    return this.prefetchQueue.add(() => this.fetchRoute(route))
  }

  async fetchComponent (route, as) {
    let cancelled = false
    const cancel = this.componentLoadCancel = function () {
      cancelled = true
    }

    const Component = await this.fetchRoute(route)

    if (cancelled) {
      const error = new Error(`Abort fetching component for route: "${route}"`)
      error.cancelled = true
      throw error
    }

    if (cancel === this.componentLoadCancel) {
      this.componentLoadCancel = null
    }

    return Component
  }

  async getInitialProps (Component, ctx) {
    let cancelled = false
    const cancel = () => { cancelled = true }
    this.componentLoadCancel = cancel

    const props = await loadGetInitialProps(Component, ctx)

    if (cancel === this.componentLoadCancel) {
      this.componentLoadCancel = null
    }

    if (cancelled) {
      const err = new Error('Loading initial props cancelled')
      err.cancelled = true
      throw err
    }

    return props
  }

  async fetchRoute (route) {
    return await this.pageLoader.loadPage(route)
  }

  abortComponentLoad (as) {
    if (this.componentLoadCancel) {
      this.events.emit('routeChangeError', new Error('Route Cancelled'), as)
      this.componentLoadCancel()
      this.componentLoadCancel = null
    }
  }

  notify (data) {
    this.subscriptions.forEach((fn) => fn(data))
  }

  subscribe (fn) {
    this.subscriptions.add(fn)
    return () => this.subscriptions.delete(fn)
  }
}

function toRoute (path) {
  return path.replace(/\/$/, '') || '/'
}
<|endoftext|>


<commit_before>var AdminPages = [
                    'mediaManager',
                    'albumManager',
                    'albumEdit',
                    'profile'
                 ];

var routerFilters = {
    isLoggedIn: function (pause) {
      if (Meteor.loggingIn()) {
        this.render(this.loadingTemplate);
      } else {
        AccountsTemplates.ensureSignedIn.call(this, pause);
      }
    }
};

// Controllers

SettingsController = RouteController.extend({
    metadata: function () {
        return { title: 'Settings', description: null};
    },
    data: function () {
        return {
          hasSettings: !!Settings.find().count(),
          settings: Settings.findOne()
        }
    }
});

AlbumController = RouteController.extend({
    template: 'album',
    increment: getSetting('mediaPerPage', 30),
    limit: function() {
      return parseInt(this.params.limit) || this.increment;
    },
    findOptions: function() {
      return {limit: this.limit()};
    },
    metadata: function() {
      var album = Albums.findOne({slug: this.params.slug});
      if (!! album)
        return { title       : capitalize(album.title) + ' Album',
                 description : capitalize(album.title) };
    },
    album: function () {
      return Albums.findOne({slug: this.params.slug});
    },
    items: function () {
      return Media.find({}, this.findOptions());
    },
    subscriptions: function() {
      this.itemsSub = Meteor.subscribe('albumMedia', this.params.slug, this.findOptions());
    },
    waitOn: function () {
     // return Meteor.subscribe('albumBySlug', this.params.slug);
      return Meteor.subscribe('albumLinks');
    },
    onAfterAction: function () {
      if (!Albums.findOne({slug: this.params.slug}))
        this.render('notFound');
      else this.render();
    },
    data: function() {
        var hasMore = this.items().count() === this.limit();
        var nextPath = this.route.path({slug: this.params.slug, limit: this.limit() + this.increment});

        return {
            album: this.album(),
            items: this.items(),
            ready: this.itemsSub.ready(),
            nextPath: hasMore ? nextPath : null
        };
    }
  });

MediaListController = RouteController.extend({
	  template: 'mediaManager',
	  increment: getSetting('mediaPerPage', 30),
	  limit: function() {
	    return parseInt(this.params.limit) || this.increment;
	  },
	  findOptions: function() {
	    return { sort: {'uploadedAt': -1}, limit: this.limit() };
	  },
    metadata: function() {
      return { title: 'Media Library', description: null}
    },
    items: function () {
        var sortBy = SortAction.getSortBy(),
            sortAndLimit;

        if (sortBy)
          sortAndLimit = {sort: sortBy, limit: this.limit()};
        else
          sortAndLimit = {limit: this.limit()};

        var keyword = GetSearch(),
            query = new RegExp( keyword, 'i' );

        var results = Media.find({ $or: [
                                      {'metadata.title': query},
                                      {'metadata.credit': query},
                                      {'metadata.caption': query},
                                      {'original.name': query},
                                      {'metadata.tags.name': query},
                                     ]}, sortAndLimit );

        return results;

    },
    subscriptions: function() {
      this.itemsSub = Meteor.subscribe('media', this.findOptions());
    },
    waitOn: function () {
      return Meteor.subscribe('tags');
    },
	  data: function() {
	     	var hasMore = this.items().count() === this.limit();
        var nextPath = this.route.path({limit: this.limit() + this.increment});

	     	return {
            //tags:  this.tags(),
            items: this.items(),
		        ready: this.itemsSub.ready(),
		        nextPath: hasMore ? nextPath : null
	     	};
	  }

	});

AlbumEditController = RouteController.extend({
    template: 'albumEdit',
    findOptions: function() {
      return { sort: { 'uploadedAt': -1 } };
    },
    album: function () {
      return Albums.findOne(this.params._id);
    },
    metadata: function() {
      return { title: 'Edit Album', description: null}
    },
    mediaItems: function() {
      var keyword = GetSearch(),
          query = new RegExp( keyword, 'i' ),
          results;

      results = Media.find({ $or: [
                                  {'metadata.title': query},
                                  {'metadata.credit': query},
                                  {'metadata.caption': query},
                                  {'metadata.tags.name': query},
                                  {'original.name': query},
                                 ]} );

      return results;

    },
    waitOn: function () {
      return [Meteor.subscribe('media', this.findOptions()), Meteor.subscribe('album', this.params._id)];
    },
    data: function() {
        return {
            mediaItems: this.mediaItems(),
            album: this.album()
        };
    }

  });

AlbumListController = RouteController.extend({
    template: 'albumManager',
    increment: getSetting('mediaPerPage', 30),
    limit: function() {
      return parseInt(this.params.limit) || this.increment;
    },
    findOptions: function() {
      return { limit: this.limit() };
    },
    metadata: function() {
      return { title: 'Albums', description: null}
    },
    items: function () {
        var keyword = GetSearch(),
            query = new RegExp( keyword, 'i' ),
            results;

        results = Albums.find({ $or: [
                                       {'title': query},
                                       {'description': query},
                                     ]
                              });

        return results;

    },
    onBeforeAction: function () {
      // if(isAdmin())
      //   Meteor.call('removeUnusedAlbums'); // clear out any unsaved (abandoned) albums
      this.next();
    },
    subscriptions: function() {
      this.itemsSub = Meteor.subscribe('albums', this.findOptions());
    },
    data: function() {
        var hasMore = this.items().count() === this.limit();
        var nextPath = this.route.path({limit: this.limit() + this.increment});
        return {
            items: this.items(),
            ready: this.itemsSub.ready(),
            nextPath: hasMore ? nextPath : null
        };
    }

  });

TagListController = RouteController.extend({
    template: 'tagManager',
    metadata: function() {
      return { title: 'Tags', description: null };
    },
    tags: function () {
      var sortBy = SortAction.getSortBy(),
          sortObj;

      if (sortBy)
        sortObj = { sort: sortBy };

      var query = new RegExp( GetSearch(), 'i' ),
          results;

      results = Tags.find({'name': query}, sortObj );

      return results;

    },
    waitOn: function () {
      return [ Meteor.subscribe('tags'), Meteor.subscribe('mediaTags') ];
    },
    data: function() {
        return {
          tags: this.tags()
        }
    }

  });

  Router.configure({
      layoutTemplate: 'layout',
      loadingTemplate: 'loading',
      notFoundTemplate: 'notFound',
      yieldTemplates: {
        'header': { to: 'header' },
        // 'footer': { to: 'footer' },
      },
      waitOn: function () {
        return Meteor.subscribe('settings');
        // return _.map(preloadSubscriptions, function(sub){
        //   // can either pass strings or objects with subName and subArguments properties
        //   if (typeof sub === 'object'){
        //     Meteor.subscribe(sub.subName, sub.subArguments);
        //   }else{
        //     Meteor.subscribe(sub);
        //   }
        // });
      },
      onAfterAction: function() {
        // set metadata
        var meta = this.metadata(),
            title = getSetting('title'),
            description = getSetting('siteDescription');

        title += !! meta.title ? ' :: ' + meta.title : '';
        description = !! meta.description ? meta.description : description;

        $('title').text(title);
        $('meta[name=description]').attr('content', description);
      }
    });

    Router.plugin('dataNotFound', {notFoundTemplate: 'notFound'});

    //Router.onBeforeAction(IR_BeforeHooks.setLayout);

    // Router.onBeforeAction(function () {
    //   IR_BeforeHooks.setLayout(this);
    //   this.next();
    // });

    Router.onBeforeAction(routerFilters.isLoggedIn, {only: AdminPages})
    Router.onBeforeAction(function() { pageChanged(false); this.next() });
    Router.onBeforeAction('dataNotFound');

    Router.onAfterAction(function() {
        // set metadata
        if (typeof this.metadata === 'function') {
          var meta = this.metadata(),
              title = getSetting('title'),
              description = getSetting('siteDescription');

          title += !! meta.title ? ' :: ' + meta.title : '';
          description = !! meta.description ? meta.description : description;

          $('title').text(title);
          $('meta[name=description]').attr('content', description);
        }
    });
    // Router.onAfterAction(loadGoogleAnalytics);
    // Router.onAfterAction(googleAnalyticsPageView(this.path));

// Routes

	/**
    * Homepage
    * Path: /
    * Redirects user on login.
    */
    Router.route('/', {
      name: 'home',
      path: '/',
      template: 'home',
    });

  /**
    * Album Page
    * Path: /album/:_id/:limit
    * Page for viewing album images
    */
    Router.route('/album/:slug/:limit?', {
      name          : 'album',
      path          : '/album/:slug/:limit?',
      controller    : AlbumController
    });

  /**
    * Album Edit Page
    * Path: /admin/media-manager/album-edit/:_id
    * Default page for editing a album and the media within it
    */
    Router.route('/admin/album/:_id/edit', {
      name          : 'albumEdit',
      path          : '/admin/album/:_id/edit',
      controller    : AlbumEditController
    });

  /**
    * Media Library
    * Path: /admin/media-manager/:limit
    * Default page for viewing, adding and editing images
    */
    Router.route('/admin/media-manager/', {
      name          : 'mediaManager',
      path          : '/admin/media-manager/:limit?',
      controller    : MediaListController
    });

  /**
    * Album Library
    * Path: /admin/album-manager/:limit
    * Default page for viewing, adding albums
    */
    Router.route('/admin/album-manager/', {
      name          : 'albumManager',
      template      : 'albumManager',
      path          : '/admin/album-manager/:limit?',
      controller    : AlbumListController
    });

  /**
    * Tags Library
    * Path: /admin/tag-manager/
    * Default page for viewing, adding/removing tags
    */
    Router.route('/admin/tag-manager/', {
      name          : 'tagManager',
      template      : 'tagManager',
      path          : '/admin/tag-manager/',
      controller    : TagListController
    });

  /**
    * Site Settings
    * Path: /admin/settings
    * View / Edit settings form
    */
    Router.route('/admin/settings', {
      name          : 'settings',
      template      : 'settingsManager',
      path          : '/admin/settings',
      controller    : SettingsController
    });

    Router.route('/sign-out', {
      name: 'signOut',
      template: 'home',
      onBeforeAction: function() {
        Meteor.logout(function() {});
        this.next();
      }
    });

  /**
    * Albums
    * Path: /albums/:limit
    * Default page for albums
    */
    Router.route('/albums/:limit?', {
      name          : 'albums',
      template      : 'albums',
      path          : '/albums/:limit?',
      controller    : AlbumListController
    });

  /**
    * Not Found
    * Path: *
    * Send any undefined routes here
    */
    Router.route('*', {
      path            : '*',
      template        : 'notFound',
      name            : 'notFound'
    });

    <commit_msg>Added ability to search media by album title<commit_after>var AdminPages = [
                    'mediaManager',
                    'albumManager',
                    'albumEdit',
                    'profile'
                 ];

var routerFilters = {
    isLoggedIn: function (pause) {
      if (Meteor.loggingIn()) {
        this.render(this.loadingTemplate);
      } else {
        AccountsTemplates.ensureSignedIn.call(this, pause);
      }
    }
};

// Controllers

SettingsController = RouteController.extend({
    metadata: function () {
        return { title: 'Settings', description: null};
    },
    data: function () {
        return {
          hasSettings: !!Settings.find().count(),
          settings: Settings.findOne()
        }
    }
});

AlbumController = RouteController.extend({
    template: 'album',
    increment: getSetting('mediaPerPage', 30),
    limit: function() {
      return parseInt(this.params.limit) || this.increment;
    },
    findOptions: function() {
      return {limit: this.limit()};
    },
    metadata: function() {
      var album = Albums.findOne({slug: this.params.slug});
      if (!! album)
        return { title       : capitalize(album.title) + ' Album',
                 description : capitalize(album.title) };
    },
    album: function () {
      return Albums.findOne({slug: this.params.slug});
    },
    items: function () {
      return Media.find({}, this.findOptions());
    },
    subscriptions: function() {
      this.itemsSub = Meteor.subscribe('albumMedia', this.params.slug, this.findOptions());
    },
    waitOn: function () {
     // return Meteor.subscribe('albumBySlug', this.params.slug);
      return Meteor.subscribe('albumLinks');
    },
    onAfterAction: function () {
      if (!Albums.findOne({slug: this.params.slug}))
        this.render('notFound');
      else this.render();
    },
    data: function() {
        var hasMore = this.items().count() === this.limit();
        var nextPath = this.route.path({slug: this.params.slug, limit: this.limit() + this.increment});

        return {
            album: this.album(),
            items: this.items(),
            ready: this.itemsSub.ready(),
            nextPath: hasMore ? nextPath : null
        };
    }
  });

MediaListController = RouteController.extend({
	  template: 'mediaManager',
	  increment: getSetting('mediaPerPage', 30),
	  limit: function() {
	    return parseInt(this.params.limit) || this.increment;
	  },
	  findOptions: function() {
	    return { sort: {'uploadedAt': -1}, limit: this.limit() };
	  },
    metadata: function() {
      return { title: 'Media Library', description: null}
    },
    items: function () {
        var sortBy = SortAction.getSortBy(),
            sortAndLimit;

        if (sortBy)
          sortAndLimit = {sort: sortBy, limit: this.limit()};
        else
          sortAndLimit = {limit: this.limit()};

        var keyword = GetSearch(),
            query = new RegExp( keyword, 'i' );

        var results = Media.find({ $or: [
                                      {'metadata.title': query},
                                      {'metadata.credit': query},
                                      {'metadata.caption': query},
                                      {'original.name': query},
                                      {'metadata.tags.name': query},
                                      {'metadata.albums.title': query}
                                     ]}, sortAndLimit );

        return results;

    },
    subscriptions: function() {
      this.itemsSub = Meteor.subscribe('media', this.findOptions());
    },
    waitOn: function () {
      return Meteor.subscribe('tags');
    },
	  data: function() {
	     	var hasMore = this.items().count() === this.limit();
        var nextPath = this.route.path({limit: this.limit() + this.increment});

	     	return {
            //tags:  this.tags(),
            items: this.items(),
		        ready: this.itemsSub.ready(),
		        nextPath: hasMore ? nextPath : null
	     	};
	  }

	});

AlbumEditController = RouteController.extend({
    template: 'albumEdit',
    findOptions: function() {
      return { sort: { 'uploadedAt': -1 } };
    },
    album: function () {
      return Albums.findOne(this.params._id);
    },
    metadata: function() {
      return { title: 'Edit Album', description: null}
    },
    mediaItems: function() {
      var keyword = GetSearch(),
          query = new RegExp( keyword, 'i' ),
          results;

      results = Media.find({ $or: [
                                  {'metadata.title': query},
                                  {'metadata.credit': query},
                                  {'metadata.caption': query},
                                  {'metadata.tags.name': query},
                                  {'original.name': query},
                                 ]} );

      return results;

    },
    waitOn: function () {
      return [Meteor.subscribe('media', this.findOptions()), Meteor.subscribe('album', this.params._id)];
    },
    data: function() {
        return {
            mediaItems: this.mediaItems(),
            album: this.album()
        };
    }

  });

AlbumListController = RouteController.extend({
    template: 'albumManager',
    increment: getSetting('mediaPerPage', 30),
    limit: function() {
      return parseInt(this.params.limit) || this.increment;
    },
    findOptions: function() {
      return { limit: this.limit() };
    },
    metadata: function() {
      return { title: 'Albums', description: null}
    },
    items: function () {
        var keyword = GetSearch(),
            query = new RegExp( keyword, 'i' ),
            results;

        results = Albums.find({ $or: [
                                       {'title': query},
                                       {'description': query},
                                     ]
                              });

        return results;

    },
    onBeforeAction: function () {
      // if(isAdmin())
      //   Meteor.call('removeUnusedAlbums'); // clear out any unsaved (abandoned) albums
      this.next();
    },
    subscriptions: function() {
      this.itemsSub = Meteor.subscribe('albums', this.findOptions());
    },
    data: function() {
        var hasMore = this.items().count() === this.limit();
        var nextPath = this.route.path({limit: this.limit() + this.increment});
        return {
            items: this.items(),
            ready: this.itemsSub.ready(),
            nextPath: hasMore ? nextPath : null
        };
    }

  });

TagListController = RouteController.extend({
    template: 'tagManager',
    metadata: function() {
      return { title: 'Tags', description: null };
    },
    tags: function () {
      var sortBy = SortAction.getSortBy(),
          sortObj;

      if (sortBy)
        sortObj = { sort: sortBy };

      var query = new RegExp( GetSearch(), 'i' ),
          results;

      results = Tags.find({'name': query}, sortObj );

      return results;

    },
    waitOn: function () {
      return [ Meteor.subscribe('tags'), Meteor.subscribe('mediaTags') ];
    },
    data: function() {
        return {
          tags: this.tags()
        }
    }

  });

  Router.configure({
      layoutTemplate: 'layout',
      loadingTemplate: 'loading',
      notFoundTemplate: 'notFound',
      yieldTemplates: {
        'header': { to: 'header' },
        // 'footer': { to: 'footer' },
      },
      waitOn: function () {
        return Meteor.subscribe('settings');
        // return _.map(preloadSubscriptions, function(sub){
        //   // can either pass strings or objects with subName and subArguments properties
        //   if (typeof sub === 'object'){
        //     Meteor.subscribe(sub.subName, sub.subArguments);
        //   }else{
        //     Meteor.subscribe(sub);
        //   }
        // });
      },
      onAfterAction: function() {
        // set metadata
        var meta = this.metadata(),
            title = getSetting('title'),
            description = getSetting('siteDescription');

        title += !! meta.title ? ' :: ' + meta.title : '';
        description = !! meta.description ? meta.description : description;

        $('title').text(title);
        $('meta[name=description]').attr('content', description);
      }
    });

    Router.plugin('dataNotFound', {notFoundTemplate: 'notFound'});

    //Router.onBeforeAction(IR_BeforeHooks.setLayout);

    // Router.onBeforeAction(function () {
    //   IR_BeforeHooks.setLayout(this);
    //   this.next();
    // });

    Router.onBeforeAction(routerFilters.isLoggedIn, {only: AdminPages})
    Router.onBeforeAction(function() { pageChanged(false); this.next() });
    Router.onBeforeAction('dataNotFound');

    Router.onAfterAction(function() {
        // set metadata
        if (typeof this.metadata === 'function') {
          var meta = this.metadata(),
              title = getSetting('title'),
              description = getSetting('siteDescription');

          title += !! meta.title ? ' :: ' + meta.title : '';
          description = !! meta.description ? meta.description : description;

          $('title').text(title);
          $('meta[name=description]').attr('content', description);
        }
    });
    // Router.onAfterAction(loadGoogleAnalytics);
    // Router.onAfterAction(googleAnalyticsPageView(this.path));

// Routes

	/**
    * Homepage
    * Path: /
    * Redirects user on login.
    */
    Router.route('/', {
      name: 'home',
      path: '/',
      template: 'home',
    });

  /**
    * Album Page
    * Path: /album/:_id/:limit
    * Page for viewing album images
    */
    Router.route('/album/:slug/:limit?', {
      name          : 'album',
      path          : '/album/:slug/:limit?',
      controller    : AlbumController
    });

  /**
    * Album Edit Page
    * Path: /admin/media-manager/album-edit/:_id
    * Default page for editing a album and the media within it
    */
    Router.route('/admin/album/:_id/edit', {
      name          : 'albumEdit',
      path          : '/admin/album/:_id/edit',
      controller    : AlbumEditController
    });

  /**
    * Media Library
    * Path: /admin/media-manager/:limit
    * Default page for viewing, adding and editing images
    */
    Router.route('/admin/media-manager/', {
      name          : 'mediaManager',
      path          : '/admin/media-manager/:limit?',
      controller    : MediaListController
    });

  /**
    * Album Library
    * Path: /admin/album-manager/:limit
    * Default page for viewing, adding albums
    */
    Router.route('/admin/album-manager/', {
      name          : 'albumManager',
      template      : 'albumManager',
      path          : '/admin/album-manager/:limit?',
      controller    : AlbumListController
    });

  /**
    * Tags Library
    * Path: /admin/tag-manager/
    * Default page for viewing, adding/removing tags
    */
    Router.route('/admin/tag-manager/', {
      name          : 'tagManager',
      template      : 'tagManager',
      path          : '/admin/tag-manager/',
      controller    : TagListController
    });

  /**
    * Site Settings
    * Path: /admin/settings
    * View / Edit settings form
    */
    Router.route('/admin/settings', {
      name          : 'settings',
      template      : 'settingsManager',
      path          : '/admin/settings',
      controller    : SettingsController
    });

    Router.route('/sign-out', {
      name: 'signOut',
      template: 'home',
      onBeforeAction: function() {
        Meteor.logout(function() {});
        this.next();
      }
    });

  /**
    * Albums
    * Path: /albums/:limit
    * Default page for albums
    */
    Router.route('/albums/:limit?', {
      name          : 'albums',
      template      : 'albums',
      path          : '/albums/:limit?',
      controller    : AlbumListController
    });

  /**
    * Not Found
    * Path: *
    * Send any undefined routes here
    */
    Router.route('*', {
      path            : '*',
      template        : 'notFound',
      name            : 'notFound'
    });

    <|endoftext|>


<commit_before>var MiddlewareStack = Iron.MiddlewareStack;
var Url = Iron.Url;
var Layout = Iron.Layout;
var assert = Iron.utils.assert;
var DEFAULT_NOT_FOUND_TEMPLATE = '__IronRouterNotFound__';

/**
 * Client specific initialization.
 */
Router.prototype.init = function (options) {
  var self = this;

  // the current RouteController from a dispatch
  self._currentController = null;

  // the current() dep
  self._currentDep = new Deps.Dependency;

  // the location computation
  self._locationComputation = null;

  // the ui layout for the router
  self._layout = new Layout({template: self.options.layoutTemplate});

  Meteor.startup(function () {
    setTimeout(function maybeAutoInsertRouter () {
      if (self.options.autoRender !== false)
        self.insert({el: document.body});
    });
  });
};

/**
 * Programmatically insert the router into document.body or a particular
 * element with {el: 'selector'}
 */
Router.prototype.insert = function (options) {
  this._layout.insert(options);
  return this;
};

/**
 * Returns a layout view that can be used in a UI helper to render the router
 * to a particular place.
 */
Router.prototype.createView = function () {
  return this._layout.create();
};

Router.prototype.lookupNotFoundTemplate = function () {
  return this.options.notFoundTemplate || DEFAULT_NOT_FOUND_TEMPLATE;
};

Router.prototype.dispatch = function (url, context, done) {
  assert(typeof url === 'string', "expected url string in router dispatch");

  var self = this;

  // no options to controller here
  var controller = this.createController(url, context);

  if (this._currentController)
    this._currentController.stop();

  this._currentController = controller;

  var result = controller.dispatch(self._stack, url, function (err) {
    if (err)
      throw err;
    else {
      if (!controller.isHandled() && controller.willBeHandledOnServer()) {
        window.location = controller.url;
        return;
      } else if (!controller.isHandled() && !controller.willBeHandledOnServer()) {
        // looks like there's no handlers so let's give a default
        // not found message!
        this.render(self.lookupNotFoundTemplate(), {data: {url: this.url}});
        return;
      } else {
        return done && done(err);
      }
    }
  });

  if (this._currentController == controller)
    this._currentDep.changed();

  return result;
};

/**
 * The current controller object.
 */
Router.prototype.current = function () {
  this._currentDep.depend();
  return this._currentController;
};

/*
 * Scroll to a specific location on the page.
 * Overridable by applications that want to customize this behavior.
 */
Router.prototype._scrollToHash = function (hashValue) {
  console.log('scrolling!' + hashValue);
  var $target = $(hashValue);
  $('html, body').scrollTop($target.offset().top);
};

/**
 * Start reacting to location changes.
 */
Router.prototype.start = function () {
  var self = this;
  self._locationComputation = Deps.autorun(function locationComputation (c) {
    var controller;
    var loc = Iron.Location.get();
    var hash, pathname, search;
    var current = self._currentController;

    // see if only the hash part has changed
    if (current && current.location && current.location.path == loc.path && current.location.hash != loc.hash) {
      self._scrollToHash(loc.hash);
    } else {
      controller = self.dispatch(loc.href);

      // if we're going to the server cancel the url change
      if (controller.willBeHandledOnServer())
        loc.cancelUrlChange();
    }
  });
};

/**
 * Stop all computations and put us in a not started state.
 */
Router.prototype.stop = function () {
  if (!this._isStarted)
    return;

  if (this._locationComputation)
    this._locationComputation.stop();

  if (this._currentController)
    this._currentController.stop();

  this._isStarted = false;
};

/**
 * Go to a given path or route name, optinally pass parameters and options.
 *
 * Example:
 * router.go('itemsShowRoute', {_id: 5}, {hash: 'frag', query: 'string});
 */
Router.prototype.go = function (routeNameOrPath, params, options) {
  var self = this;
  var isPath = /^\/|http/;
  var path;

  if (isPath.test(routeNameOrPath)) {
    // it's a path!
    path = routeNameOrPath;
  } else {
    // it's a route name!
    var route = self.routes[routeNameOrPath];
    assert(route, "No route found named " + JSON.stringify(routeNameOrPath));
    path = route.path(params, options);
  }

  // let Iron Location handle it and we'll pick up the change in
  // Iron.Location.get() computation.
  Iron.Location.go(path, options);
};
<commit_msg>remove erroneous console log on scrollToHash<commit_after>var MiddlewareStack = Iron.MiddlewareStack;
var Url = Iron.Url;
var Layout = Iron.Layout;
var assert = Iron.utils.assert;
var DEFAULT_NOT_FOUND_TEMPLATE = '__IronRouterNotFound__';

/**
 * Client specific initialization.
 */
Router.prototype.init = function (options) {
  var self = this;

  // the current RouteController from a dispatch
  self._currentController = null;

  // the current() dep
  self._currentDep = new Deps.Dependency;

  // the location computation
  self._locationComputation = null;

  // the ui layout for the router
  self._layout = new Layout({template: self.options.layoutTemplate});

  Meteor.startup(function () {
    setTimeout(function maybeAutoInsertRouter () {
      if (self.options.autoRender !== false)
        self.insert({el: document.body});
    });
  });
};

/**
 * Programmatically insert the router into document.body or a particular
 * element with {el: 'selector'}
 */
Router.prototype.insert = function (options) {
  this._layout.insert(options);
  return this;
};

/**
 * Returns a layout view that can be used in a UI helper to render the router
 * to a particular place.
 */
Router.prototype.createView = function () {
  return this._layout.create();
};

Router.prototype.lookupNotFoundTemplate = function () {
  return this.options.notFoundTemplate || DEFAULT_NOT_FOUND_TEMPLATE;
};

Router.prototype.dispatch = function (url, context, done) {
  assert(typeof url === 'string', "expected url string in router dispatch");

  var self = this;

  // no options to controller here
  var controller = this.createController(url, context);

  if (this._currentController)
    this._currentController.stop();

  this._currentController = controller;

  var result = controller.dispatch(self._stack, url, function (err) {
    if (err)
      throw err;
    else {
      if (!controller.isHandled() && controller.willBeHandledOnServer()) {
        window.location = controller.url;
        return;
      } else if (!controller.isHandled() && !controller.willBeHandledOnServer()) {
        // looks like there's no handlers so let's give a default
        // not found message!
        this.render(self.lookupNotFoundTemplate(), {data: {url: this.url}});
        return;
      } else {
        return done && done(err);
      }
    }
  });

  if (this._currentController == controller)
    this._currentDep.changed();

  return result;
};

/**
 * The current controller object.
 */
Router.prototype.current = function () {
  this._currentDep.depend();
  return this._currentController;
};

/*
 * Scroll to a specific location on the page.
 * Overridable by applications that want to customize this behavior.
 */
Router.prototype._scrollToHash = function (hashValue) {
  var $target = $(hashValue);
  $('html, body').scrollTop($target.offset().top);
};

/**
 * Start reacting to location changes.
 */
Router.prototype.start = function () {
  var self = this;
  self._locationComputation = Deps.autorun(function locationComputation (c) {
    var controller;
    var loc = Iron.Location.get();
    var hash, pathname, search;
    var current = self._currentController;

    // see if only the hash part has changed
    if (current && current.location && current.location.path == loc.path && current.location.hash != loc.hash) {
      self._scrollToHash(loc.hash);
    } else {
      controller = self.dispatch(loc.href);

      // if we're going to the server cancel the url change
      if (controller.willBeHandledOnServer())
        loc.cancelUrlChange();
    }
  });
};

/**
 * Stop all computations and put us in a not started state.
 */
Router.prototype.stop = function () {
  if (!this._isStarted)
    return;

  if (this._locationComputation)
    this._locationComputation.stop();

  if (this._currentController)
    this._currentController.stop();

  this._isStarted = false;
};

/**
 * Go to a given path or route name, optinally pass parameters and options.
 *
 * Example:
 * router.go('itemsShowRoute', {_id: 5}, {hash: 'frag', query: 'string});
 */
Router.prototype.go = function (routeNameOrPath, params, options) {
  var self = this;
  var isPath = /^\/|http/;
  var path;

  if (isPath.test(routeNameOrPath)) {
    // it's a path!
    path = routeNameOrPath;
  } else {
    // it's a route name!
    var route = self.routes[routeNameOrPath];
    assert(route, "No route found named " + JSON.stringify(routeNameOrPath));
    path = route.path(params, options);
  }

  // let Iron Location handle it and we'll pick up the change in
  // Iron.Location.get() computation.
  Iron.Location.go(path, options);
};
<|endoftext|>


<commit_before>"use strict";
var express = require('express');
var errors = require('../errors');
var directives = require('../directives');
var schemas = require('../schemas');
var uuid = require('node-uuid');
var fs = require('fs-extra');
var path = require('path');
var R = require('ramda');

module.exports = function (logger, config, knexClient) {
  var router = express.Router();
  var tokenService = directives.token(logger, knexClient);

  /**
   * Admin Create, Read All
   */
  router.route('')
    .all(tokenService.verify('access_token'), function(req, res, next) {
      if (req.user.id !== req.circlr.user_id) {
        return next(new errors.AuthenticationRequiredError());
      }

      next();
    })
    .post(directives.validation.schema(schemas.photos.create), function(req, res, next) {
      var imageUuid = uuid.v4();
      var folder = req.files.image && req.files.image.name && req.files.image.name.substr(0, req.files.image.name.indexOf('.'));
      var uploadPath = path.join(config.upload.path, folder);

      fs.ensureDir(uploadPath, function(err) {
        if (err) {
          return next(new errors.InternalError());
        }

        fs.move(req.files.image.path, path.join(uploadPath, imageUuid + '.' + req.files.image.extension), function(err) {
          if (err) {
            return next(new errors.InternalError());
          }

          var photo = {
            uuid: imageUuid,
            path: path.join(folder, imageUuid + '.' + req.files.image.extension),
            circlr_id: req.circlr.id,
            description: req.body.description
          };
          knexClient('photos').insert(photo).returning('id')
            .then(function(row) {
              return res.status(200).json(R.merge(photo, { id: row[0] }));
            });
        });
      });
    })
    .get(function(req, res, next) {
      knexClient.select('id', 'uuid', 'path', 'description', 'created_at').from('photos').where({ circlr_id: req.circlr.id })
        .then(function(rows) {
          return res.status(200).json(rows);
        });
    });

  /**
   * Admin Update, Delete Routes
   */
  router.route('/:id')
    .all(tokenService.verify('access_token'), function(req, res, next) {
      if (req.user.id !== req.circlr.user_id) {
        return next(new errors.AuthenticationRequiredError());
      }

      next();
    })
    .put(directives.validation.schema(schemas.photos.update), function(req, res, next) {
      knexClient('photos').where({ id: req.params.id, circlr_id: req.circlr.id })
        .update({ description: req.body.description })
        .then(function(result) {
          return res.status(200).send({updated: true});
        });
    })
    .delete(function(req, res) {
      knexClient('photos').where({id: req.params.id, circlr_id: req.circlr.id }).returning('id').del()
        .then(function (result) {
          var photoId = result[0];
          return knexClient('circles_photos').where({
            circlr_id: req.circlr.id,
            photo_id: photoId
          }).del();
        }).then(function () {
          return res.status(200).send({deleted: true});
        });
    });

  /**
   * Public Read photo
   */
  router.get('/:uuid', function(req, res, next) {
    knexClient.select('uuid', 'path', 'description').from('photos').where({ uuid: req.params.uuid, circlr_id: req.circlr.id }).limit(1)
      .then(function(row) {
        if (!row || row.length === 0) {
          return next(new errors.EntityNotFoundError('photos'));
        }

        return res.status(200).json(row[0]);
      });
  });

  return router;
};<commit_msg>format file<commit_after>"use strict";
var express = require('express');
var errors = require('../errors');
var directives = require('../directives');
var schemas = require('../schemas');
var uuid = require('node-uuid');
var fs = require('fs-extra');
var path = require('path');
var R = require('ramda');

module.exports = function (logger, config, knexClient) {
  var router = express.Router();
  var tokenService = directives.token(logger, knexClient);

  /**
   * Admin Create, Read All
   */
  router.route('')
    .all(tokenService.verify('access_token'), function(req, res, next) {
      if (req.user.id !== req.circlr.user_id) {
        return next(new errors.AuthenticationRequiredError());
      }

      next();
    })
    .post(directives.validation.schema(schemas.photos.create), function(req, res, next) {
      var imageUuid = uuid.v4();
      var folder = req.files.image && req.files.image.name && req.files.image.name.substr(0, req.files.image.name.indexOf('.'));
      var uploadPath = path.join(config.upload.path, folder);

      fs.ensureDir(uploadPath, function(err) {
        if (err) {
          return next(new errors.InternalError());
        }

        fs.move(req.files.image.path, path.join(uploadPath, imageUuid + '.' + req.files.image.extension), function(err) {
          if (err) {
            return next(new errors.InternalError());
          }

          var photo = {
            uuid: imageUuid,
            path: path.join(folder, imageUuid + '.' + req.files.image.extension),
            circlr_id: req.circlr.id,
            description: req.body.description
          };
          knexClient('photos').insert(photo).returning('id')
            .then(function(row) {
              return res.status(200).json(R.merge(photo, { id: row[0] }));
            });
        });
      });
    })
    .get(function(req, res, next) {
      knexClient.select('id', 'uuid', 'path', 'description', 'created_at').from('photos').where({ circlr_id: req.circlr.id })
        .then(function(rows) {
          return res.status(200).json(rows);
        });
    });

  /**
   * Admin Update, Delete Routes
   */
  router.route('/:id')
    .all(tokenService.verify('access_token'), function(req, res, next) {
      if (req.user.id !== req.circlr.user_id) {
        return next(new errors.AuthenticationRequiredError());
      }

      next();
    })
    .put(directives.validation.schema(schemas.photos.update), function(req, res, next) {
      knexClient('photos').where({ id: req.params.id, circlr_id: req.circlr.id })
        .update({ description: req.body.description })
        .then(function(result) {
          return res.status(200).send({updated: true});
        });
    })
    .delete(function(req, res) {
      knexClient('photos').where({id: req.params.id, circlr_id: req.circlr.id }).returning('id').del()
        .then(function (result) {
          var photoId = result[0];
          return knexClient('circles_photos').where({
            circlr_id: req.circlr.id,
            photo_id: photoId
          }).del();
        }).then(function () {
          return res.status(200).send({deleted: true});
        });
    });

  /**
   * Public Read photo
   */
  router.get('/:uuid', function(req, res, next) {
    knexClient.select('uuid', 'path', 'description').from('photos').where({ uuid: req.params.uuid, circlr_id: req.circlr.id }).limit(1)
      .then(function(row) {
        if (!row || row.length === 0) {
          return next(new errors.EntityNotFoundError('photos'));
        }

        return res.status(200).json(row[0]);
      });
  });

  return router;
};<|endoftext|>


<commit_before>const router = require('./router')();
const elasticsearch = require('../plugins/elasticsearch');
const _ = require('lodash');
const logger = require('../utilities/logger');
const i18n = require('../plugins/i18n');

const allowedTypes = ['event', 'user', 'crew'];

router.use('/:type?', ({ params, query }, res) => {
  const esClient = elasticsearch.getClient();
  const luceneEscapePattern = /(\+|\-|\&|\||\!|\(|\)|\{|\}|\[|\]|\^|\"|\~|\?|\:|\\)/g;
  const saneQuery = query.q.replace(luceneEscapePattern, '\\$1');

  const q = {
    index: elasticsearch.INDEX,
    body: {
      query: {
        query_string: {
          query: saneQuery
        }
      }
    }
  };
  if (params.type !== null && _.includes(allowedTypes, params.type)) {
    q.type = params.type;
  }

  logger.debug('Search with the following query', JSON.stringify(q));
  esClient.search(q, (err, results) => {
    if (err) {
      logger.debug('Search returned this error:', err.message);
      res.render('search', {
        title: '😱 – Oh noo!',
        subtitle: i18n.__('Search is currently unavailable…'),
        searchAvailable: false
      });
    } else {
      logger.debug('Results', results.hits.hits);
      res.render('search', {
        title: i18n.__('Results'),
        data: results.hits.hits,
        resultCount: results.hits.total,
        searchAvailable: true,
        q: query.q
      });
    }
  });
});

module.exports = router;
<commit_msg>performance type for search<commit_after>const router = require('./router')();
const elasticsearch = require('../plugins/elasticsearch');
const _ = require('lodash');
const logger = require('../utilities/logger');
const i18n = require('../plugins/i18n');

const allowedTypes = ['event', 'user', 'crew', 'performance'];

router.use('/:type?', ({ params, query }, res) => {
  const esClient = elasticsearch.getClient();
  const luceneEscapePattern = /(\+|\-|\&|\||\!|\(|\)|\{|\}|\[|\]|\^|\"|\~|\?|\:|\\)/g;
  const saneQuery = query.q.replace(luceneEscapePattern, '\\$1');

  const q = {
    index: elasticsearch.INDEX,
    body: {
      query: {
        query_string: {
          query: saneQuery
        }
      }
    }
  };
  if (params.type !== null && _.includes(allowedTypes, params.type)) {
    q.type = params.type;
  }

  logger.debug('Search with the following query', JSON.stringify(q));
  esClient.search(q, (err, results) => {
    if (err) {
      logger.debug('Search returned this error:', err.message);
      res.render('search', {
        title: '😱 – Oh noo!',
        subtitle: i18n.__('Search is currently unavailable…'),
        searchAvailable: false
      });
    } else {
      logger.debug('Results', results.hits.hits);
      res.render('search', {
        title: i18n.__('Results'),
        data: results.hits.hits,
        resultCount: results.hits.total,
        searchAvailable: true,
        q: query.q
      });
    }
  });
});

module.exports = router;
<|endoftext|>


<commit_before>"use strict";

// This module should be compatible with PhantomJS v1, just like the other files
// in reify/lib/runtime. Node 4+ features like const/let and arrow functions are
// not acceptable here, and importing any npm packages should be contemplated
// with extreme skepticism.

var utils = require("./utils.js");
var Entry = require("./entry.js");

// The exports.enable method can be used to enable the Reify runtime for
// specific module objects, or for Module.prototype (where implemented),
// to make the runtime available throughout the entire module system.
exports.enable = function (mod) {
  if (typeof mod.resolve !== "function") {
    throw new Error("module.resolve not implemented");
  }

  if (mod.link !== moduleLink) {
    mod.link = moduleLink;
    mod["export"] = moduleExport;
    mod.exportDefault = moduleExportDefault;
    mod.exportAs = moduleExportAs;
    mod.runSetters = runSetters;

    // Legacy shorthand for mod.exportAs("*").
    mod.makeNsSetter = moduleMakeNsSetter;

    return true;
  }

  return false;
};

// Calling module.link(id, setters) resolves the given ID using
// module.resolve(id), which should return a canonical absolute module
// identifier string (like require.resolve); then creates an Entry object
// for the child module and evaluates its code (if this is the first time
// it has been imported) by calling module.require(id). Finally, the
// provided setter functions will be called with values exported by the
// module, possibly multiple times when/if those exported values change.
// The module.link name is intended to evoke the "liveness" of the
// exported bindings, since we are subscribing to all future exports of
// the child module, not just taking a snapshot of its current exports.
function moduleLink(id, setters, key) {
  utils.setESModule(this.exports);
  Entry.getOrCreate(this.id, this);

  var absChildId = this.resolve(id);
  var childEntry = Entry.getOrCreate(absChildId);

  if (utils.isObject(setters)) {
    childEntry.addSetters(this, setters, key);
  }

  var exports = this.require(absChildId);

  if (childEntry.module === null) {
    childEntry.module = {
      id: absChildId,
      exports: exports
    };
  }

  childEntry.runSetters();
}

// Register getter functions for local variables in the scope of an export
// statement. Pass true as the second argument to indicate that the getter
// functions always return the same values.
function moduleExport(getters, constant) {
  utils.setESModule(this.exports);
  var entry = Entry.getOrCreate(this.id, this);
  entry.addGetters(getters, constant);
  if (this.loaded) {
    // If the module has already been evaluated, then we need to trigger
    // another round of entry.runSetters calls, which begins by calling
    // entry.runModuleGetters(module).
    entry.runSetters();
  }
}

// Register a getter function that always returns the given value.
function moduleExportDefault(value) {
  return this["export"]({
    "default": function () {
      return value;
    }
  }, true);
}

// Returns a function suitable for passing as a setter callback to
// module.link. If name is an identifier, calling the function will set
// the export of that name to the given value. If the name is "*", all
// properties of the value object will be exported by name, except for
// "default" (use "*+" instead of "*" to include it). Why the "default"
// property is skipped: https://github.com/tc39/ecma262/issues/948
function moduleExportAs(name) {
  var entry = this;
  var includeDefault = name === "*+";
  return function (value) {
    if (name === "*" || name === "*+") {
      Object.keys(value).forEach(function (key) {
        if (includeDefault || key !== "default") {
          utils.copyKey(key, entry.exports, value);
        }
      });
    } else {
      entry.exports[name] = value;
    }
  };
}

// Platform-specific code should find a way to call this method whenever
// the module system is about to return module.exports from require. This
// might happen more than once per module, in case of dependency cycles,
// so we want Module.prototype.runSetters to run each time.
function runSetters(valueToPassThrough) {
  Entry.getOrCreate(this.id, this).runSetters();

  // Assignments to exported local variables get wrapped with calls to
  // module.runSetters, so module.runSetters returns the
  // valueToPassThrough parameter to allow the value of the original
  // expression to pass through. For example,
  //
  //   export var a = 1;
  //   console.log(a += 3);
  //
  // becomes
  //
  //   module.export("a", () => a);
  //   var a = 1;
  //   console.log(module.runSetters(a += 3));
  //
  // This ensures module.runSetters runs immediately after the assignment,
  // and does not interfere with the larger computation.
  return valueToPassThrough;
}

// Legacy helper that returns a function that takes a namespace object and
// copies the properties of the namespace to module.exports, excluding any
// "default" property (unless includeDefault is true), which is useful for
// implementing `export * from "module"`.
//
// Instead of using this helper like so:
//
//   module.link(id, { "*": module.makeNsSetter() });
//
// non-legacy code should simply use a string-valued setter:
//
//   module.link(id, { "*": "*" });
//
// or, to include the "default" property:
//
//   module.link(id, { "*": "*+" });
//
// This helper may be removed in a future version of Reify.
function moduleMakeNsSetter(includeDefault) {
  return this.exportAs(includeDefault ? "*+" : "*");
}
<commit_msg>Don't complain about missing module.resolve until necessary.<commit_after>"use strict";

// This module should be compatible with PhantomJS v1, just like the other files
// in reify/lib/runtime. Node 4+ features like const/let and arrow functions are
// not acceptable here, and importing any npm packages should be contemplated
// with extreme skepticism.

var utils = require("./utils.js");
var Entry = require("./entry.js");

// The exports.enable method can be used to enable the Reify runtime for
// specific module objects, or for Module.prototype (where implemented),
// to make the runtime available throughout the entire module system.
exports.enable = function (mod) {
  if (mod.link !== moduleLink) {
    mod.link = moduleLink;
    mod["export"] = moduleExport;
    mod.exportDefault = moduleExportDefault;
    mod.exportAs = moduleExportAs;
    mod.runSetters = runSetters;

    // Legacy shorthand for mod.exportAs("*").
    mod.makeNsSetter = moduleMakeNsSetter;

    return true;
  }

  return false;
};

// Calling module.link(id, setters) resolves the given ID using
// module.resolve(id), which should return a canonical absolute module
// identifier string (like require.resolve); then creates an Entry object
// for the child module and evaluates its code (if this is the first time
// it has been imported) by calling module.require(id). Finally, the
// provided setter functions will be called with values exported by the
// module, possibly multiple times when/if those exported values change.
// The module.link name is intended to evoke the "liveness" of the
// exported bindings, since we are subscribing to all future exports of
// the child module, not just taking a snapshot of its current exports.
function moduleLink(id, setters, key) {
  utils.setESModule(this.exports);
  Entry.getOrCreate(this.id, this);

  var absChildId = this.resolve(id);
  var childEntry = Entry.getOrCreate(absChildId);

  if (utils.isObject(setters)) {
    childEntry.addSetters(this, setters, key);
  }

  var exports = this.require(absChildId);

  if (childEntry.module === null) {
    childEntry.module = {
      id: absChildId,
      exports: exports
    };
  }

  childEntry.runSetters();
}

// Register getter functions for local variables in the scope of an export
// statement. Pass true as the second argument to indicate that the getter
// functions always return the same values.
function moduleExport(getters, constant) {
  utils.setESModule(this.exports);
  var entry = Entry.getOrCreate(this.id, this);
  entry.addGetters(getters, constant);
  if (this.loaded) {
    // If the module has already been evaluated, then we need to trigger
    // another round of entry.runSetters calls, which begins by calling
    // entry.runModuleGetters(module).
    entry.runSetters();
  }
}

// Register a getter function that always returns the given value.
function moduleExportDefault(value) {
  return this["export"]({
    "default": function () {
      return value;
    }
  }, true);
}

// Returns a function suitable for passing as a setter callback to
// module.link. If name is an identifier, calling the function will set
// the export of that name to the given value. If the name is "*", all
// properties of the value object will be exported by name, except for
// "default" (use "*+" instead of "*" to include it). Why the "default"
// property is skipped: https://github.com/tc39/ecma262/issues/948
function moduleExportAs(name) {
  var entry = this;
  var includeDefault = name === "*+";
  return function (value) {
    if (name === "*" || name === "*+") {
      Object.keys(value).forEach(function (key) {
        if (includeDefault || key !== "default") {
          utils.copyKey(key, entry.exports, value);
        }
      });
    } else {
      entry.exports[name] = value;
    }
  };
}

// Platform-specific code should find a way to call this method whenever
// the module system is about to return module.exports from require. This
// might happen more than once per module, in case of dependency cycles,
// so we want Module.prototype.runSetters to run each time.
function runSetters(valueToPassThrough) {
  Entry.getOrCreate(this.id, this).runSetters();

  // Assignments to exported local variables get wrapped with calls to
  // module.runSetters, so module.runSetters returns the
  // valueToPassThrough parameter to allow the value of the original
  // expression to pass through. For example,
  //
  //   export var a = 1;
  //   console.log(a += 3);
  //
  // becomes
  //
  //   module.export("a", () => a);
  //   var a = 1;
  //   console.log(module.runSetters(a += 3));
  //
  // This ensures module.runSetters runs immediately after the assignment,
  // and does not interfere with the larger computation.
  return valueToPassThrough;
}

// Legacy helper that returns a function that takes a namespace object and
// copies the properties of the namespace to module.exports, excluding any
// "default" property (unless includeDefault is true), which is useful for
// implementing `export * from "module"`.
//
// Instead of using this helper like so:
//
//   module.link(id, { "*": module.makeNsSetter() });
//
// non-legacy code should simply use a string-valued setter:
//
//   module.link(id, { "*": "*" });
//
// or, to include the "default" property:
//
//   module.link(id, { "*": "*+" });
//
// This helper may be removed in a future version of Reify.
function moduleMakeNsSetter(includeDefault) {
  return this.exportAs(includeDefault ? "*+" : "*");
}
<|endoftext|>


<commit_before>/*
 * Implement some helpers methods for interacting with the DOM,
 * be it Fastboot's SimpleDOM or the browser's version.
 *
 * Credit to https://github.com/yapplabs/ember-wormhole, from where this has been shamelessly stolen.
 */

import { getOwner } from '@ember/application';
import { DEBUG } from '@glimmer/env';
import requirejs from 'require';
import { warn } from '@ember/debug';

function childNodesOfElement(element) {
  let children = [];
  let child = element.firstChild;
  while (child) {
    children.push(child);
    child = child.nextSibling;
  }
  return children;
}

export function findElementById(doc, id) {
  if (doc.getElementById) {
    return doc.getElementById(id);
  }

  let nodes = childNodesOfElement(doc);
  let node;

  while (nodes.length) {
    node = nodes.shift();

    if (node.getAttribute && node.getAttribute('id') === id) {
      return node;
    }
    nodes = childNodesOfElement(node).concat(nodes);
  }
}

// Private Ember API usage. Get the dom implementation used by the current
// renderer, be it native browser DOM or Fastboot SimpleDOM
export function getDOM(context) {
  let { renderer } = context;
  if (!renderer._dom) {
    // pre glimmer2
    let container = getOwner ? getOwner(context) : context.container;
    let documentService = container.lookup('service:-document');

    if (documentService) {
      return documentService;
    }

    renderer = container.lookup('renderer:-dom');
  }

  if (renderer._dom && renderer._dom.document) {
    return renderer._dom.document;
  } else {
    throw new Error('Could not get DOM');
  }
}

export function getDestinationElement(context) {
  let dom = getDOM(context);
  let destinationElement = findElementById(dom, 'ember-bootstrap-wormhole');

  if (DEBUG && !destinationElement) {
    let config = getOwner(context).resolveRegistration('config:environment');
    if (config.environment === 'test' && typeof FastBoot === 'undefined') {
      let id;
      if (requirejs.has('@ember/test-helpers/dom/get-root-element')) {
        try {
          id = requirejs('@ember/test-helpers/dom/get-root-element').default().id;
        } catch (ex) {
          // no op
        }
      }
      if (!id) {
        return document.querySelector('#ember-testing > .ember-view');
      }
      return document.getElementById(id);
    }

    warn(
      `No wormhole destination element found for component ${context}. If you have set \`insertEmberWormholeElementToDom\` to false, you should insert a \`div#ember-bootstrap-wormhole\` manually!`,
      false,
      { id: 'ember-bootstrap.no-destination-element' }
    );
  }

  return destinationElement;
}
<commit_msg>Fix getDestinationElement<commit_after>/*
 * Implement some helpers methods for interacting with the DOM,
 * be it Fastboot's SimpleDOM or the browser's version.
 *
 * Credit to https://github.com/yapplabs/ember-wormhole, from where this has been shamelessly stolen.
 */

import { getOwner } from '@ember/application';
import { DEBUG } from '@glimmer/env';
import requirejs from 'require';
import { warn } from '@ember/debug';

function childNodesOfElement(element) {
  let children = [];
  let child = element.firstChild;
  while (child) {
    children.push(child);
    child = child.nextSibling;
  }
  return children;
}

export function findElementById(doc, id) {
  if (doc.getElementById) {
    return doc.getElementById(id);
  }

  let nodes = childNodesOfElement(doc);
  let node;

  while (nodes.length) {
    node = nodes.shift();

    if (node.getAttribute && node.getAttribute('id') === id) {
      return node;
    }
    nodes = childNodesOfElement(node).concat(nodes);
  }
}

// Private Ember API usage. Get the dom implementation used by the current
// renderer, be it native browser DOM or Fastboot SimpleDOM
export function getDOM(context) {
  let { renderer } = context;
  if (!renderer._dom) {
    // pre glimmer2
    let container = getOwner ? getOwner(context) : context.container;
    let documentService = container.lookup('service:-document');

    if (documentService) {
      return documentService;
    }

    renderer = container.lookup('renderer:-dom');
  }

  if (renderer._dom && renderer._dom.document) {
    return renderer._dom.document;
  } else {
    throw new Error('Could not get DOM');
  }
}

export function getDestinationElement(context) {
  let dom = getDOM(context);
  let destinationElement = findElementById(dom, 'ember-bootstrap-wormhole');

  if (DEBUG && !destinationElement) {
    let config = getOwner(context).resolveRegistration('config:environment');
    if (config.environment === 'test' && typeof FastBoot === 'undefined') {
      let id;
      if (requirejs.has('@ember/test-helpers/dom/get-root-element')) {
        try {
          id = requirejs('@ember/test-helpers/dom/get-root-element').default().id;
        } catch (ex) {
          // no op
        }
      }
      if (!id) {
        return document.querySelector('#ember-testing');
      }
      return document.getElementById(id);
    }

    warn(
      `No wormhole destination element found for component ${context}. If you have set \`insertEmberWormholeElementToDom\` to false, you should insert a \`div#ember-bootstrap-wormhole\` manually!`,
      false,
      { id: 'ember-bootstrap.no-destination-element' }
    );
  }

  return destinationElement;
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

const { describe, it, after, before } = require('mocha');
const Repository = require('../src/repository');
const assert = require('assert');
const fetch = require('node-fetch');

describe('Repository', () => {
  let repo;
  before(async () => {
    repo = new Repository();
    await repo.create('firstRepo', {
      repositoryid: 'this is the first repo'
    });
    await repo.create('secondRepo', {
      repositoryid: 'this is the second repo'
    });
  });

  describe('createRepository', async () => {
    it('creates a new repository', async () => {
      await repo.create('my-first-repository', {
        description: 'this is my first test repository'
      });
      // TODO: use a unique name for this document, using the uuid library,
      // so that two folks can run tests at the same time without colliding.
      const repository = await repo.get('my-first-repository');
      assert.strictEqual(repository.description, 'this is my first test repository');
    });
  });
  describe('allRepositories', async () => {
    it('returns the repository JSON', async () => {
      let result = await repo.getCollection('repositories');
      //TODO: this test relies on a specific document in a specific repo. Fix this.
      var shouldMatchSolution = [{'repositoryid':'this is the first repo'}, {'repositoryid':'this is the second repo'}];
      assert.strictEqual(result, shouldMatchSolution);
    })
  })
  after(async () => {
    await repo.delete('my-first-repository');
    await repo.delete('firstRepo');
    await repo.delete('secondRepo');
  });
});
<commit_msg>fix: make test only check the correct documents<commit_after>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

const { describe, it, after, before } = require('mocha');
const Repository = require('../src/repository');
const assert = require('assert');
const fetch = require('node-fetch');

describe('Repository', () => {
  let repo;
  before(async () => {
    repo = new Repository();
    await repo.create('test-repos/firstRepo', {
      repositoryid: 'this is the first repo'
    });
    await repo.create('test-repos/secondRepo', {
      repositoryid: 'this is the second repo'
    });
  });

  describe('createRepository', async () => {
    it('creates a new repository', async () => {
      await repo.create('my-first-repository', {
        description: 'this is my first test repository'
      });
      // TODO: use a unique name for this document, using the uuid library,
      // so that two folks can run tests at the same time without colliding.
      const repository = await repo.get('my-first-repository');
      assert.strictEqual(repository.description, 'this is my first test repository');
    });
  });
  describe('allRepositories', async () => {
    it('returns the repository JSON', async () => {
      let result = await repo.getCollection('repositories/test-repos');
      //TODO: this test relies on a specific document in a specific repo. Fix this.
      var shouldMatchSolution = [{'repositoryid':'this is the first repo'}, {'repositoryid':'this is the second repo'}];
      assert.strictEqual(result, shouldMatchSolution);
    })
  })
  after(async () => {
    await repo.delete('my-first-repository');
    await repo.delete('test-repos/firstRepo');
    await repo.delete('test-repos/secondRepo');
  });
});
<|endoftext|>


<commit_before>var crypto = require('crypto');
var scmp = require('scmp');
var utils = require('keystone-utils');

exports.TOKEN_KEY = '_csrf';
exports.LOCAL_KEY = 'csrf_token_key';
exports.LOCAL_VALUE = 'csrf_token_value';
exports.SECRET_KEY = exports.TOKEN_KEY + '_secret';
exports.SECRET_LENGTH = 10;
exports.CSRF_HEADER_KEY = 'x-csrf-token';
exports.XSRF_HEADER_KEY = 'x-xsrf-token';
exports.XSRF_COOKIE_KEY = 'XSRF-TOKEN';

function tokenize(salt, secret) {
	return salt + crypto.createHash('sha1').update(salt + secret).digest('base64');
}

exports.createSecret = function() {
	return crypto.pseudoRandomBytes(exports.SECRET_LENGTH).toString('base64');
};

exports.getSecret = function(req) {
	return req.session[exports.SECRET_KEY] || (req.session[exports.SECRET_KEY] = exports.createSecret());
};

exports.createToken = function(req) {
	return tokenize(utils.randomString(exports.SECRET_LENGTH), exports.getSecret(req));
};

exports.getToken = function(req, res) {
	res.locals[exports.LOCAL_VALUE] = res.locals[exports.LOCAL_VALUE] || exports.createToken(req);
	res.cookie(exports.XSRF_COOKIE_KEY, res.locals[exports.LOCAL_VALUE]);
	return res.locals[exports.LOCAL_VALUE];
};

exports.requestToken = function(req) {
	if (req.body && req.body[exports.TOKEN_KEY]) {
		return req.body[exports.TOKEN_KEY];
	} else if (req.query && req.query[exports.TOKEN_KEY]) {
		return req.query[exports.TOKEN_KEY];
	} else if (req.headers && req.headers[exports.XSRF_HEADER_KEY]) {
		return req.headers[exports.XSRF_HEADER_KEY];
	} else if (req.headers && req.headers[exports.CSRF_HEADER_KEY]) {
		return req.headers[exports.CSRF_HEADER_KEY];
	}
	return '';
};

exports.validate = function(req, token) {
	if (arguments.length === 1) {
		token = exports.requestToken(req);
	}
	console.log(token);
	if (typeof token !== 'string') {
		return false;
	}
	return scmp(token, tokenize(token.slice(0, exports.SECRET_LENGTH), req.session[exports.SECRET_KEY]));
};

exports.middleware = {

	init: function(req, res, next) {
		res.locals[exports.LOCAL_KEY] = exports.LOCAL_VALUE;
		exports.getToken(req, res);
		next();
	},

	validate: function(req, res, next) {

		// Bail on safe methods
		if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') {
			return next();
		}

		// Validate token
		if (exports.validate(req)) {
			next();
		} else {
			res.statusCode = 403;
			next(new Error('CSRF token mismatch'));
		}

	}

};
<commit_msg>Removing csrf token log<commit_after>var crypto = require('crypto');
var scmp = require('scmp');
var utils = require('keystone-utils');

exports.TOKEN_KEY = '_csrf';
exports.LOCAL_KEY = 'csrf_token_key';
exports.LOCAL_VALUE = 'csrf_token_value';
exports.SECRET_KEY = exports.TOKEN_KEY + '_secret';
exports.SECRET_LENGTH = 10;
exports.CSRF_HEADER_KEY = 'x-csrf-token';
exports.XSRF_HEADER_KEY = 'x-xsrf-token';
exports.XSRF_COOKIE_KEY = 'XSRF-TOKEN';

function tokenize(salt, secret) {
	return salt + crypto.createHash('sha1').update(salt + secret).digest('base64');
}

exports.createSecret = function() {
	return crypto.pseudoRandomBytes(exports.SECRET_LENGTH).toString('base64');
};

exports.getSecret = function(req) {
	return req.session[exports.SECRET_KEY] || (req.session[exports.SECRET_KEY] = exports.createSecret());
};

exports.createToken = function(req) {
	return tokenize(utils.randomString(exports.SECRET_LENGTH), exports.getSecret(req));
};

exports.getToken = function(req, res) {
	res.locals[exports.LOCAL_VALUE] = res.locals[exports.LOCAL_VALUE] || exports.createToken(req);
	res.cookie(exports.XSRF_COOKIE_KEY, res.locals[exports.LOCAL_VALUE]);
	return res.locals[exports.LOCAL_VALUE];
};

exports.requestToken = function(req) {
	if (req.body && req.body[exports.TOKEN_KEY]) {
		return req.body[exports.TOKEN_KEY];
	} else if (req.query && req.query[exports.TOKEN_KEY]) {
		return req.query[exports.TOKEN_KEY];
	} else if (req.headers && req.headers[exports.XSRF_HEADER_KEY]) {
		return req.headers[exports.XSRF_HEADER_KEY];
	} else if (req.headers && req.headers[exports.CSRF_HEADER_KEY]) {
		return req.headers[exports.CSRF_HEADER_KEY];
	}
	return '';
};

exports.validate = function(req, token) {
	if (arguments.length === 1) {
		token = exports.requestToken(req);
	}
	if (typeof token !== 'string') {
		return false;
	}
	return scmp(token, tokenize(token.slice(0, exports.SECRET_LENGTH), req.session[exports.SECRET_KEY]));
};

exports.middleware = {

	init: function(req, res, next) {
		res.locals[exports.LOCAL_KEY] = exports.LOCAL_VALUE;
		exports.getToken(req, res);
		next();
	},

	validate: function(req, res, next) {

		// Bail on safe methods
		if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') {
			return next();
		}

		// Validate token
		if (exports.validate(req)) {
			next();
		} else {
			res.statusCode = 403;
			next(new Error('CSRF token mismatch'));
		}

	}

};
<|endoftext|>