<commit_before>/**
 * 设置中心::默认设置
 */

const LANG = antSword['language']['settings']['adefault'];
const LANG_T = antSword['language']['toastr'];

class ADefault {

  constructor(sidebar) {
    var self = this;
    sidebar.addItem({
      id: 'adefault',
      text: `<i class="fa fa-sliders"></i> ${LANG['title']}`
    });
    const cell = sidebar.cells('adefault');
    const default_config = {
      filemanager: {
        openfileintab: false,
        bookmarks: {},
      },
    };
    // 读取配置
    const filemanager_settings = JSON.parse(antSword['storage']("adefault_filemanager", false, JSON.stringify(default_config.filemanager)));
    this.filemanager_settings = filemanager_settings;
    if(!this.filemanager_settings.bookmarks){
      this.filemanager_settings.bookmarks = default_config.filemanager.bookmarks;
    }

    const toolbar = cell.attachToolbar();
    toolbar.loadStruct([
      { id: 'save', type: 'button', text: LANG['toolbar']['save'], icon: 'save' }
    ]);
    // 表单
    const form = cell.attachForm([{
      type: 'block', name: 'filemanager', list: [
        // {type: "label", label: LANG['filemanager']['title']},
        {
          type: "fieldset", label: LANG['filemanager']['title'], list:[
            { type: "block", list: [
              {type: "label", label: LANG['filemanager']['openfileintab']['title']},
              {type: 'newcolumn', offset:20},
              {
              type: "radio", label: LANG['filemanager']['openfileintab']['window'], name: 'openfileintab', checked: filemanager_settings.openfileintab == false , position: "label-right", value: false,
              },
              {type: 'newcolumn', offset:20},
              {
                type: "radio", label: LANG['filemanager']['openfileintab']['tab'], name: 'openfileintab', checked: filemanager_settings.openfileintab == true , position: "label-right", value: true,
              }
            ]},
            // 后续文件管理其它设置
            { type: 'block', list: [
              {type: 'label', label: LANG['filemanager']['bookmark']['title']},
              {type: 'container', name: 'filemanager_bookmarks', inputWidth: 600, inputHeight: 200},
            ]},
          ]
        }
      ]},
      // 后续其它模块
    ], true);
    form.enableLiveValidation(true);
    let bookmark_grid = new dhtmlXGridObject(form.getContainer('filemanager_bookmarks'));
    bookmark_grid.setHeader(`
     ,
    ${LANG['filemanager']['bookmark']['grid']['name']},
    ${LANG['filemanager']['bookmark']['grid']['path']}
    `);
    bookmark_grid.setColTypes("ro,edtxt,edtxt");
    bookmark_grid.setColSorting('str,str,str');
    bookmark_grid.setInitWidths("40,*,200");
    bookmark_grid.setColAlign("center,left,left");
    bookmark_grid.enableMultiselect(true);
    // grid右键
    // 空白数据右键fix
    $('.objbox').on('contextmenu', (e) => {
      (e.target.nodeName === 'DIV' && bookmark_grid.callEvent instanceof Function && antSword['tabbar'].getActiveTab() === "tab_about" && sidebar.getActiveItem() === "adefault") ? bookmark_grid.callEvent('onRightClick', [-1, -1, e]) : null;
    });
    $('.objbox').on('click', (e) => {
      bmenu.hide();
    });
    bookmark_grid.attachEvent('onRightClick', (id, lid, event)=>{
      let _ids = (bookmark_grid.getSelectedId()|| '').split(',');
      if (id === -1) {
        _ids = [];
      } else if (_ids.length === 1) {
        // 如果没有选中？则选中右键对应选项
        bookmark_grid.selectRowById(id);
        _ids = [id];
      };
      let ids = [];
      _ids.map((_) => {
        ids.push(bookmark_grid.getRowAttribute(_, 'bname'));
      });
      id = ids[0] || '';

      let menu = [
        { text: LANG['filemanager']['bookmark']['bmenu']['add'], icon: 'fa fa-plus-circle', action: self.addBookMarks.bind(self)},
        { text: LANG['filemanager']['bookmark']['bmenu']['del'], icon: 'fa fa-trash-o', action: () => {
          self.delBookMarks(ids);
        }},
      ];
      bmenu(menu, event);
      return true;
    });

    bookmark_grid.attachEvent("onEditCell", function(stage,rId,cInd,nValue,oValue){
      // 2 编辑完成
      if(stage === 2) {
        if(nValue === oValue){return;}
        var obname = bookmark_grid.getRowAttribute(rId, "bname");
        var obpath = bookmark_grid.getRowAttribute(rId, "bpath");
        switch(cInd){ // 具体是哪一列被编辑了
          case 1: // name
            // if(!nValue.match(/^[a-zA-Z0-9_/]+$/)){
            //   toastr.error(LANG["filemanager"]['bookmark']['edit']["name_invalid"], LANG_T['error']);
            //   return
            // }
            if(self.filemanager_settings.bookmarks.hasOwnProperty(obname)){
              delete self.filemanager_settings.bookmarks[obname];
              self.filemanager_settings.bookmarks[nValue] = obpath;
            }
            toastr.success(LANG["filemanager"]['bookmark']['edit']["success"],LANG_T["success"]);
            break;
          case 2: // path
            nValue = nValue.replace(/\\/g,'/');
            if(!nValue.endsWith('/')){
              nValue += '/';
            }
            if(self.filemanager_settings.bookmarks.hasOwnProperty(obname)){
              self.filemanager_settings.bookmarks[obname] = nValue;
            }
          break;
        }
        antSword['storage']('adefault_filemanager', self.filemanager_settings);
        self.reloadFMBookmarks();
      }
    });

    bookmark_grid.init();
    this.bookmark_grid = bookmark_grid;

    // 保存
    toolbar.attachEvent('onClick', (id) => {
      switch(id){
        case 'save':
          if(form.validate()){
            var _formvals = form.getValues();
            let config = default_config;
            config.filemanager.openfileintab = _formvals['openfileintab'];

            // save
            // save 文件管理设置
            antSword['storage']('adefault_filemanager', config.filemanager);
            toastr.success(LANG['success'], LANG_T['success']);
            // 重启应用
            layer.confirm(LANG['confirm']['content'], {
              icon: 2, shift: 6,
              title: LANG['confirm']['title']
            }, (_) => {
              location.reload();
            });
          }else{
            toastr.error(LANG['error'], LANG_T['error']);
          }
        break;
      }
    });

    this.reloadFMBookmarks();
  }
  // 重载 bookmarks grid
  reloadFMBookmarks(){
    let self = this;
    let data = [];
    let _id = 1;
    Object.keys(self.filemanager_settings.bookmarks).map((t)=>{
      data.push({
        id: _id,
        bname: t,
        bpath: self.filemanager_settings.bookmarks[t],
        data: [
          `<i class="fa fa-bookmark-o"></i>`,
          antSword.noxss(t),
          antSword.noxss(self.filemanager_settings.bookmarks[t])
        ]
      });
      _id++;
    });
    if(data.length == 0){
      data.push({
        id: _id,
        bname: '',
        bpath: '',
        data: [
          `<i class="fa fa-bookmark-o"></i>`,
          LANG['filemanager']['bookmark']['nodata'],
          ' '
        ]
      });
    }
    self.bookmark_grid.clearAll();
    self.bookmark_grid.parse({
      'rows': data
    }, 'json');
  }

  addBookMarks() {
    let self = this;
    let hash = +new Date();
    let index = layer.prompt({
      title: `<i class="fa fa-bookmark"></i> ${LANG['filemanager']['bookmark']['add']['title']}`,
      content: '<input type="text" style="width:300px;" class="layui-layer-input" id="bname_' + hash + '" value="" placeholder="bookmark name">
<input style="width:300px;" type="text" id="bpath_' + hash + '" class="layui-layer-input" value="" placeholder="bookmark path">',
      btn: [LANG['filemanager']['bookmark']['add']['addbtn']],
      yes: (i) => {
        let _bname = $(`#bname_${hash}`);
        let _bpath = $(`#bpath_${hash}`);
        let bname = _bname.val();
        let bpath = _bpath.val();
        let gbm = self.filemanager_settings.bookmarks;
        if(gbm.hasOwnProperty(bname)) {
          _bname.focus();
          return toastr.warning(LANG['filemanager']['bookmark']['add']['namedup'], LANG_T['warning']);
        }
        bpath = bpath.replace(/\\/g,'/');
        if(!bpath.endsWith('/')) {
          bpath += '/';
        }
        gbm[bname] = bpath;
        self.filemanager_settings.bookmarks = gbm;
        antSword['storage']('adefault_filemanager', self.filemanager_settings);
        self.reloadFMBookmarks();
        toastr.success(LANG['filemanager']['bookmark']['add']['success'], LANG_T['success']);
        layer.close(i);
      }
    });
  }

  delBookMarks(ids) {
    let self = this;
    if(ids.length === 1 && !ids[0]) {
      return
    }
    layer.confirm(
      LANG['filemanager']['bookmark']['del']['confirm'](ids.length > 1 ? ids.length:ids[0]),
      {
        icon: 2,
        shift: 6,
        title: `<i class="fa fa-trash"></i> ${LANG['filemanager']['bookmark']['del']['title']}`,
      },
      (_) => {
        layer.close(_);
        ids.map((p)=>{
          if(self.filemanager_settings.bookmarks.hasOwnProperty(p)) {
            delete self.filemanager_settings.bookmarks[p];
          }
        });
        antSword['storage']('adefault_filemanager', self.filemanager_settings);
        self.reloadFMBookmarks();
        toastr.success(LANG['filemanager']['bookmark']['del']['success'], LANG_T['success']);
      }
    )
  }

}

module.exports = ADefault;
<commit_msg>(Fix:Setting) 修复默认设置保存时导致 bookmarks 清空的问题<commit_after>/**
 * 设置中心::默认设置
 */

const LANG = antSword['language']['settings']['adefault'];
const LANG_T = antSword['language']['toastr'];

class ADefault {

  constructor(sidebar) {
    var self = this;
    sidebar.addItem({
      id: 'adefault',
      text: `<i class="fa fa-sliders"></i> ${LANG['title']}`
    });
    const cell = sidebar.cells('adefault');
    const default_config = {
      filemanager: {
        openfileintab: false,
        bookmarks: {},
      },
    };
    // 读取配置
    const filemanager_settings = JSON.parse(antSword['storage']("adefault_filemanager", false, JSON.stringify(default_config.filemanager)));
    this.filemanager_settings = filemanager_settings;
    if(!this.filemanager_settings.bookmarks){
      this.filemanager_settings.bookmarks = default_config.filemanager.bookmarks;
    }

    const toolbar = cell.attachToolbar();
    toolbar.loadStruct([
      { id: 'save', type: 'button', text: LANG['toolbar']['save'], icon: 'save' }
    ]);
    // 表单
    const form = cell.attachForm([{
      type: 'block', name: 'filemanager', list: [
        // {type: "label", label: LANG['filemanager']['title']},
        {
          type: "fieldset", label: LANG['filemanager']['title'], list:[
            { type: "block", list: [
              {type: "label", label: LANG['filemanager']['openfileintab']['title']},
              {type: 'newcolumn', offset:20},
              {
              type: "radio", label: LANG['filemanager']['openfileintab']['window'], name: 'openfileintab', checked: filemanager_settings.openfileintab == false , position: "label-right", value: false,
              },
              {type: 'newcolumn', offset:20},
              {
                type: "radio", label: LANG['filemanager']['openfileintab']['tab'], name: 'openfileintab', checked: filemanager_settings.openfileintab == true , position: "label-right", value: true,
              }
            ]},
            // 后续文件管理其它设置
            { type: 'block', list: [
              {type: 'label', label: LANG['filemanager']['bookmark']['title']},
              {type: 'container', name: 'filemanager_bookmarks', inputWidth: 600, inputHeight: 200},
            ]},
          ]
        }
      ]},
      // 后续其它模块
    ], true);
    form.enableLiveValidation(true);
    let bookmark_grid = new dhtmlXGridObject(form.getContainer('filemanager_bookmarks'));
    bookmark_grid.setHeader(`
     ,
    ${LANG['filemanager']['bookmark']['grid']['name']},
    ${LANG['filemanager']['bookmark']['grid']['path']}
    `);
    bookmark_grid.setColTypes("ro,edtxt,edtxt");
    bookmark_grid.setColSorting('str,str,str');
    bookmark_grid.setInitWidths("40,*,200");
    bookmark_grid.setColAlign("center,left,left");
    bookmark_grid.enableMultiselect(true);
    // grid右键
    // 空白数据右键fix
    $('.objbox').on('contextmenu', (e) => {
      (e.target.nodeName === 'DIV' && bookmark_grid.callEvent instanceof Function && antSword['tabbar'].getActiveTab() === "tab_about" && sidebar.getActiveItem() === "adefault") ? bookmark_grid.callEvent('onRightClick', [-1, -1, e]) : null;
    });
    $('.objbox').on('click', (e) => {
      bmenu.hide();
    });
    bookmark_grid.attachEvent('onRightClick', (id, lid, event)=>{
      let _ids = (bookmark_grid.getSelectedId()|| '').split(',');
      if (id === -1) {
        _ids = [];
      } else if (_ids.length === 1) {
        // 如果没有选中？则选中右键对应选项
        bookmark_grid.selectRowById(id);
        _ids = [id];
      };
      let ids = [];
      _ids.map((_) => {
        ids.push(bookmark_grid.getRowAttribute(_, 'bname'));
      });
      id = ids[0] || '';

      let menu = [
        { text: LANG['filemanager']['bookmark']['bmenu']['add'], icon: 'fa fa-plus-circle', action: self.addBookMarks.bind(self)},
        { text: LANG['filemanager']['bookmark']['bmenu']['del'], icon: 'fa fa-trash-o', action: () => {
          self.delBookMarks(ids);
        }},
      ];
      bmenu(menu, event);
      return true;
    });

    bookmark_grid.attachEvent("onEditCell", function(stage,rId,cInd,nValue,oValue){
      // 2 编辑完成
      if(stage === 2) {
        if(nValue === oValue){return;}
        var obname = bookmark_grid.getRowAttribute(rId, "bname");
        var obpath = bookmark_grid.getRowAttribute(rId, "bpath");
        switch(cInd){ // 具体是哪一列被编辑了
          case 1: // name
            // if(!nValue.match(/^[a-zA-Z0-9_/]+$/)){
            //   toastr.error(LANG["filemanager"]['bookmark']['edit']["name_invalid"], LANG_T['error']);
            //   return
            // }
            if(self.filemanager_settings.bookmarks.hasOwnProperty(obname)){
              delete self.filemanager_settings.bookmarks[obname];
              self.filemanager_settings.bookmarks[nValue] = obpath;
            }
            toastr.success(LANG["filemanager"]['bookmark']['edit']["success"],LANG_T["success"]);
            break;
          case 2: // path
            nValue = nValue.replace(/\\/g,'/');
            if(!nValue.endsWith('/')){
              nValue += '/';
            }
            if(self.filemanager_settings.bookmarks.hasOwnProperty(obname)){
              self.filemanager_settings.bookmarks[obname] = nValue;
            }
          break;
        }
        antSword['storage']('adefault_filemanager', self.filemanager_settings);
        self.reloadFMBookmarks();
      }
    });

    bookmark_grid.init();
    this.bookmark_grid = bookmark_grid;

    // 保存
    toolbar.attachEvent('onClick', (id) => {
      switch(id){
        case 'save':
          if(form.validate()){
            var _formvals = form.getValues();
            let config = default_config;
            config.filemanager.openfileintab = _formvals['openfileintab'];
            config.filemanager.bookmarks = self.filemanager_settings.bookmarks;
            // save
            // save 文件管理设置
            antSword['storage']('adefault_filemanager', config.filemanager);
            toastr.success(LANG['success'], LANG_T['success']);
            // 重启应用
            layer.confirm(LANG['confirm']['content'], {
              icon: 2, shift: 6,
              title: LANG['confirm']['title']
            }, (_) => {
              location.reload();
            });
          }else{
            toastr.error(LANG['error'], LANG_T['error']);
          }
        break;
      }
    });

    this.reloadFMBookmarks();
  }
  // 重载 bookmarks grid
  reloadFMBookmarks(){
    let self = this;
    let data = [];
    let _id = 1;
    Object.keys(self.filemanager_settings.bookmarks).map((t)=>{
      data.push({
        id: _id,
        bname: t,
        bpath: self.filemanager_settings.bookmarks[t],
        data: [
          `<i class="fa fa-bookmark-o"></i>`,
          antSword.noxss(t),
          antSword.noxss(self.filemanager_settings.bookmarks[t])
        ]
      });
      _id++;
    });
    if(data.length == 0){
      data.push({
        id: _id,
        bname: '',
        bpath: '',
        data: [
          `<i class="fa fa-bookmark-o"></i>`,
          LANG['filemanager']['bookmark']['nodata'],
          ' '
        ]
      });
    }
    self.bookmark_grid.clearAll();
    self.bookmark_grid.parse({
      'rows': data
    }, 'json');
  }

  addBookMarks() {
    let self = this;
    let hash = +new Date();
    let index = layer.prompt({
      title: `<i class="fa fa-bookmark"></i> ${LANG['filemanager']['bookmark']['add']['title']}`,
      content: '<input type="text" style="width:300px;" class="layui-layer-input" id="bname_' + hash + '" value="" placeholder="bookmark name">
<input style="width:300px;" type="text" id="bpath_' + hash + '" class="layui-layer-input" value="" placeholder="bookmark path">',
      btn: [LANG['filemanager']['bookmark']['add']['addbtn']],
      yes: (i) => {
        let _bname = $(`#bname_${hash}`);
        let _bpath = $(`#bpath_${hash}`);
        let bname = _bname.val();
        let bpath = _bpath.val();
        let gbm = self.filemanager_settings.bookmarks;
        if(gbm.hasOwnProperty(bname)) {
          _bname.focus();
          return toastr.warning(LANG['filemanager']['bookmark']['add']['namedup'], LANG_T['warning']);
        }
        bpath = bpath.replace(/\\/g,'/');
        if(!bpath.endsWith('/')) {
          bpath += '/';
        }
        gbm[bname] = bpath;
        self.filemanager_settings.bookmarks = gbm;
        antSword['storage']('adefault_filemanager', self.filemanager_settings);
        self.reloadFMBookmarks();
        toastr.success(LANG['filemanager']['bookmark']['add']['success'], LANG_T['success']);
        layer.close(i);
      }
    });
  }

  delBookMarks(ids) {
    let self = this;
    if(ids.length === 1 && !ids[0]) {
      return
    }
    layer.confirm(
      LANG['filemanager']['bookmark']['del']['confirm'](ids.length > 1 ? ids.length:ids[0]),
      {
        icon: 2,
        shift: 6,
        title: `<i class="fa fa-trash"></i> ${LANG['filemanager']['bookmark']['del']['title']}`,
      },
      (_) => {
        layer.close(_);
        ids.map((p)=>{
          if(self.filemanager_settings.bookmarks.hasOwnProperty(p)) {
            delete self.filemanager_settings.bookmarks[p];
          }
        });
        antSword['storage']('adefault_filemanager', self.filemanager_settings);
        self.reloadFMBookmarks();
        toastr.success(LANG['filemanager']['bookmark']['del']['success'], LANG_T['success']);
      }
    )
  }

}

module.exports = ADefault;
<|endoftext|>


<commit_before>// @flow

import {} from '../../lib/storage'

import {
	trackChannelSubscribe,
	trackChannelUnsubscribe,
	trackNotificationsDisable,
	trackNotificationsEnable,
} from '@frogpond/analytics'
import OneSignal, {
	type SubscriptionState,
	type TagsObject,
} from 'react-native-onesignal'
import pify from 'pify'

const getOneSignalTags: () => Promise<TagsObject> = pify(OneSignal.getTags, {
	errorFirst: false,
})
const getOneSignalPermissions: () => Promise<SubscriptionState> = pify(
	OneSignal.getPermissionSubscriptionState,
	{errorFirst: false},
)
const promptOneSignalPushPermission: () => Promise<boolean> = pify(
	OneSignal.promptForPushNotificationsWithUserResponse,
	{errorFirst: false},
)

import {type ReduxState} from '../index'

type Dispatch<A: Action> = (action: A | Promise<A> | ThunkAction<A>) => any
type GetState = () => ReduxState
type ThunkAction<A: Action> = (dispatch: Dispatch<A>, getState: GetState) => any

export type NotificationChannelName =
	| 'channel:campus/emergency'
	| 'channel:cage/late-night-specials'
	| 'channel:pause/monthly-special'
	| 'channel:multimedia/weekly-movie'
	| 'channel:post-office/packages'
	| 'channel:sga/announcements'

const CHANNEL_SUBSCRIBE = 'notifications/CHANNEL_SUBSCRIBE'
const CHANNEL_UNSUBSCRIBE = 'notifications/CHANNEL_UNSUBSCRIBE'
const CHANNELS_HYDRATE = 'notifications/CHANNELS_HYDRATE'
const DISABLE = 'notifications/DISABLE'
const ENABLE = 'notifications/ENABLE'

type ChannelSubscribeAction = {|
	type: 'notifications/CHANNEL_SUBSCRIBE',
	payload: NotificationChannelName,
|}
export function subscribe(
	channelName: NotificationChannelName,
): ChannelSubscribeAction {
	OneSignal.sendTag(channelName, 'active')
	trackChannelSubscribe(channelName)
	return {type: CHANNEL_SUBSCRIBE, payload: channelName}
}

type ChannelUnsubscribeAction = {|
	type: 'notifications/CHANNEL_UNSUBSCRIBE',
	payload: NotificationChannelName,
|}
export function unsubscribe(
	channelName: NotificationChannelName,
): ChannelUnsubscribeAction {
	OneSignal.deleteTag(channelName)
	trackChannelUnsubscribe(channelName)
	return {type: CHANNEL_UNSUBSCRIBE, payload: channelName}
}

type ToggleChannelSubscriptionAction =
	| ChannelSubscribeAction
	| ChannelUnsubscribeAction
export function toggleSubscription(
	channelName: NotificationChannelName,
): ThunkAction<ToggleChannelSubscriptionAction> {
	return (dispatch, getState) => {
		let {notifications: {channels = new Set()} = {}} = getState()

		if (channels.has(channelName)) {
			dispatch(unsubscribe(channelName))
		} else {
			dispatch(subscribe(channelName))
		}
	}
}

type HydrateChannelsAction = {|
	type: 'notifications/CHANNELS_HYDRATE',
	payload: {
		channels: Set<NotificationChannelName>,
		permissions: SubscriptionState,
	},
|}
export function hydrate(): ThunkAction<HydrateChannelsAction> {
	return async dispatch => {
		let [tags, permissions] = await Promise.all([
			getOneSignalTags(),
			getOneSignalPermissions(),
		])
		let channels = new Set(Object.keys(tags))
		dispatch({type: CHANNELS_HYDRATE, payload: {channels, permissions}})
	}
}

type DisableNotificationsAction = {|
	type: 'notifications/DISABLE',
|}
function disable(): DisableNotificationsAction {
	OneSignal.setSubscription(false)
	trackNotificationsDisable()
	return {type: DISABLE}
}

type EnableNotificationsAction = {|
	type: 'notifications/ENABLE',
|}
function enable(): ThunkAction<EnableNotificationsAction> {
	OneSignal.setSubscription(true)
	trackNotificationsEnable()
	return {type: ENABLE}
}

export function prompt() {
	return async dispatch => {
		const permissionResult = await promptOneSignalPushPermission()
		dispatch(permissionResult ? enable() : disable())
	}
}

type Action =
	| ChannelSubscribeAction
	| ChannelUnsubscribeAction
	| HydrateChannelsAction
	| EnableNotificationsAction
	| DisableNotificationsAction

export type State = {|
	+enabled: boolean,
	+channels: Set<NotificationChannelName>,
|}

const initialState = {
	enabled: false,
	channels: new Set(),
}

export function notifications(state: State = initialState, action: Action) {
	switch (action.type) {
		case CHANNEL_SUBSCRIBE: {
			let channels: Set<NotificationChannelName> = new Set(state.channels)
			channels.add(action.payload)
			return {...state, channels}
		}

		case CHANNEL_UNSUBSCRIBE: {
			let channels: Set<NotificationChannelName> = new Set(state.channels)
			channels.delete(action.payload)
			return {...state, channels}
		}

		case CHANNELS_HYDRATE:
			return {
				...state,
				channels: action.payload.channels,
				enabled: action.payload.permissions.notificationsEnabled,
			}

		case ENABLE:
			return {...state, enabled: true}

		case DISABLE:
			return {...state, enabled: false}

		default:
			return state
	}
}
<commit_msg>expose hasPrompted status<commit_after>// @flow

import {} from '../../lib/storage'

import {
	trackChannelSubscribe,
	trackChannelUnsubscribe,
	trackNotificationsDisable,
	trackNotificationsEnable,
} from '@frogpond/analytics'
import OneSignal, {
	type SubscriptionState,
	type TagsObject,
} from 'react-native-onesignal'
import pify from 'pify'

const getOneSignalTags: () => Promise<TagsObject> = pify(OneSignal.getTags, {
	errorFirst: false,
})
const getOneSignalPermissions: () => Promise<SubscriptionState> = pify(
	OneSignal.getPermissionSubscriptionState,
	{errorFirst: false},
)
const promptOneSignalPushPermission: () => Promise<boolean> = pify(
	OneSignal.promptForPushNotificationsWithUserResponse,
	{errorFirst: false},
)

import {type ReduxState} from '../index'

type Dispatch<A: Action> = (action: A | Promise<A> | ThunkAction<A>) => any
type GetState = () => ReduxState
type ThunkAction<A: Action> = (dispatch: Dispatch<A>, getState: GetState) => any

export type NotificationChannelName =
	| 'channel:campus/emergency'
	| 'channel:cage/late-night-specials'
	| 'channel:pause/monthly-special'
	| 'channel:multimedia/weekly-movie'
	| 'channel:post-office/packages'
	| 'channel:sga/announcements'

const CHANNEL_SUBSCRIBE = 'notifications/CHANNEL_SUBSCRIBE'
const CHANNEL_UNSUBSCRIBE = 'notifications/CHANNEL_UNSUBSCRIBE'
const CHANNELS_HYDRATE = 'notifications/CHANNELS_HYDRATE'
const DISABLE = 'notifications/DISABLE'
const ENABLE = 'notifications/ENABLE'

type ChannelSubscribeAction = {|
	type: 'notifications/CHANNEL_SUBSCRIBE',
	payload: NotificationChannelName,
|}
export function subscribe(
	channelName: NotificationChannelName,
): ChannelSubscribeAction {
	OneSignal.sendTag(channelName, 'active')
	trackChannelSubscribe(channelName)
	return {type: CHANNEL_SUBSCRIBE, payload: channelName}
}

type ChannelUnsubscribeAction = {|
	type: 'notifications/CHANNEL_UNSUBSCRIBE',
	payload: NotificationChannelName,
|}
export function unsubscribe(
	channelName: NotificationChannelName,
): ChannelUnsubscribeAction {
	OneSignal.deleteTag(channelName)
	trackChannelUnsubscribe(channelName)
	return {type: CHANNEL_UNSUBSCRIBE, payload: channelName}
}

type ToggleChannelSubscriptionAction =
	| ChannelSubscribeAction
	| ChannelUnsubscribeAction
export function toggleSubscription(
	channelName: NotificationChannelName,
): ThunkAction<ToggleChannelSubscriptionAction> {
	return (dispatch, getState) => {
		let {notifications: {channels = new Set()} = {}} = getState()

		if (channels.has(channelName)) {
			dispatch(unsubscribe(channelName))
		} else {
			dispatch(subscribe(channelName))
		}
	}
}

type HydrateChannelsAction = {|
	type: 'notifications/CHANNELS_HYDRATE',
	payload: {
		channels: Set<NotificationChannelName>,
		permissions: SubscriptionState,
	},
|}
export function hydrate(): ThunkAction<HydrateChannelsAction> {
	return async dispatch => {
		let [tags, permissions] = await Promise.all([
			getOneSignalTags(),
			getOneSignalPermissions(),
		])
		let channels = new Set(Object.keys(tags))
		dispatch({type: CHANNELS_HYDRATE, payload: {channels, permissions}})
	}
}

type DisableNotificationsAction = {|
	type: 'notifications/DISABLE',
|}
function disable(): DisableNotificationsAction {
	OneSignal.setSubscription(false)
	trackNotificationsDisable()
	return {type: DISABLE}
}

type EnableNotificationsAction = {|
	type: 'notifications/ENABLE',
|}
function enable(): ThunkAction<EnableNotificationsAction> {
	OneSignal.setSubscription(true)
	trackNotificationsEnable()
	return {type: ENABLE}
}

export function prompt() {
	return async dispatch => {
		const permissionResult = await promptOneSignalPushPermission()
		dispatch(permissionResult ? enable() : disable())
	}
}

type Action =
	| ChannelSubscribeAction
	| ChannelUnsubscribeAction
	| HydrateChannelsAction
	| EnableNotificationsAction
	| DisableNotificationsAction

export type State = {|
	+enabled: boolean,
	+channels: Set<NotificationChannelName>,
	+hasPrompted: boolean,
|}

const initialState = {
	enabled: false,
	channels: new Set(),
	hasPrompted: false,
}

export function notifications(state: State = initialState, action: Action) {
	switch (action.type) {
		case CHANNEL_SUBSCRIBE: {
			let channels: Set<NotificationChannelName> = new Set(state.channels)
			channels.add(action.payload)
			return {...state, channels}
		}

		case CHANNEL_UNSUBSCRIBE: {
			let channels: Set<NotificationChannelName> = new Set(state.channels)
			channels.delete(action.payload)
			return {...state, channels}
		}

		case CHANNELS_HYDRATE:
			return {
				...state,
				channels: action.payload.channels,
				enabled: action.payload.permissions.notificationsEnabled,
				hasPrompted: action.payload.permissions.hasPrompted,
			}

		case ENABLE:
			return {...state, enabled: true, hasPrompted: true}

		case DISABLE:
			return {...state, enabled: false, hasPrompted: true}

		default:
			return state
	}
}
<|endoftext|>


<commit_before>'use strict'

var applyUpdate = require('../../../common/apply_update')
var map = require('../../../common/array/map')

var constants = require('../../../common/constants')
var internalKey = constants.internal

var common = require('../common')
var applyOptions = common.applyOptions

var helpers = require('./helpers')
var inputRecord = helpers.inputRecord
var outputRecord = helpers.outputRecord

/**
 * Memory adapter.
 */
module.exports = function (Adapter) {
  function MemoryAdapter (properties) {
    Adapter.call(this, properties)
    if (!this.options) this.options = {}
    if (!('recordsPerType' in this.options))
      this.options.recordsPerType = 1000
  }

  Object.defineProperty(MemoryAdapter, internalKey, { value: true })

  MemoryAdapter.prototype = Object.create(Adapter.prototype)

  MemoryAdapter.prototype.connect = function () {
    var Promise = this.Promise
    var recordTypes = this.recordTypes
    var type

    this.db = {}

    for (type in recordTypes)
      this.db[type] = {}

    return Promise.resolve()
  }

  MemoryAdapter.prototype.disconnect = function () {
    var Promise = this.Promise
    delete this.db
    return Promise.resolve()
  }

  MemoryAdapter.prototype.find = function (type, ids, options, meta) {
    var self = this
    var Promise = self.Promise
    var db = self.db
    var recordTypes = self.recordTypes
    var fields = recordTypes[type]
    var collection = db[type]
    var records = []
    var i, j, id, record

    if (ids && !ids.length) return Adapter.prototype.find.call(self)

    if (ids) for (i = 0, j = ids.length; i < j; i++) {
      id = ids[i]
      if (id in collection) {
        record = collection[id]

        // LRU update.
        delete collection[id]
        collection[id] = record

        records.push(outputRecord.call(self, type, record))
      }
    }

    else for (id in collection)
      records.push(outputRecord.call(self, type, collection[id]))

    return Promise.resolve(applyOptions(fields, records, options, meta))
  }

  MemoryAdapter.prototype.create = function (type, records, meta) {
    var self = this
    var message = self.message
    var Promise = self.Promise
    var db = self.db
    var recordsPerType = self.options.recordsPerType
    var primaryKey = self.keys.primary
    var ConflictError = self.errors.ConflictError
    var collection = db[type]
    var i, j, record, id, ids, language

    if (!meta) meta = {}
    language = meta.language

    records = map(records, function (record) {
      return inputRecord.call(self, type, record)
    })

    // First check for collisions.
    for (i = 0, j = records.length; i < j; i++) {
      record = records[i]
      id = record[primaryKey]

      if (collection[id])
        return Promise.reject(new ConflictError(
          message('RecordExists', language, { id: id })))
    }

    // Then save it to memory.
    for (i = 0, j = records.length; i < j; i++) {
      record = records[i]
      collection[record[primaryKey]] = record
    }

    // Clear least recently used records.
    if (recordsPerType) {
      ids = Object.keys(collection)

      if (ids.length > recordsPerType) {
        ids = ids.slice(0, ids.length - recordsPerType)

        for (i = 0, j = ids.length; i < j; i++)
          delete collection[ids[i]]
      }
    }

    return map(records, function (record) {
      return outputRecord.call(self, type, record)
    })
  }

  MemoryAdapter.prototype.update = function (type, updates) {
    var self = this
    var Promise = self.Promise
    var db = self.db
    var primaryKey = self.keys.primary
    var collection = db[type]
    var count = 0
    var i, j, update, id, record

    if (!updates.length) return Adapter.prototype.update.call(self)

    for (i = 0, j = updates.length; i < j; i++) {
      update = updates[i]
      id = update[primaryKey]
      record = collection[id]

      if (!record) continue

      count++
      record = outputRecord.call(self, type, record)

      applyUpdate(record, update)

      // LRU update.
      delete collection[id]

      collection[id] = inputRecord.call(self, type, record)
    }

    return Promise.resolve(count)
  }

  MemoryAdapter.prototype.delete = function (type, ids) {
    var Promise = this.Promise
    var db = this.db
    var collection = db[type]
    var count = 0
    var i, j, id

    if (ids && !ids.length) return Adapter.prototype.delete.call(this)

    if (ids) for (i = 0, j = ids.length; i < j; i++) {
      id = ids[i]
      if (collection[id]) {
        delete collection[id]
        count++
      }
    }

    else for (id in collection) {
      delete collection[id]
      count++
    }

    return Promise.resolve(count)
  }

  return MemoryAdapter
}
<commit_msg>remove whitespace<commit_after>'use strict'

var applyUpdate = require('../../../common/apply_update')
var map = require('../../../common/array/map')

var constants = require('../../../common/constants')
var internalKey = constants.internal

var common = require('../common')
var applyOptions = common.applyOptions

var helpers = require('./helpers')
var inputRecord = helpers.inputRecord
var outputRecord = helpers.outputRecord

/**
 * Memory adapter.
 */
module.exports = function (Adapter) {
  function MemoryAdapter (properties) {
    Adapter.call(this, properties)
    if (!this.options) this.options = {}
    if (!('recordsPerType' in this.options))
      this.options.recordsPerType = 1000
  }

  Object.defineProperty(MemoryAdapter, internalKey, { value: true })

  MemoryAdapter.prototype = Object.create(Adapter.prototype)

  MemoryAdapter.prototype.connect = function () {
    var Promise = this.Promise
    var recordTypes = this.recordTypes
    var type

    this.db = {}

    for (type in recordTypes)
      this.db[type] = {}

    return Promise.resolve()
  }

  MemoryAdapter.prototype.disconnect = function () {
    var Promise = this.Promise
    delete this.db
    return Promise.resolve()
  }

  MemoryAdapter.prototype.find = function (type, ids, options, meta) {
    var self = this
    var Promise = self.Promise
    var db = self.db
    var recordTypes = self.recordTypes
    var fields = recordTypes[type]
    var collection = db[type]
    var records = []
    var i, j, id, record

    if (ids && !ids.length) return Adapter.prototype.find.call(self)

    if (ids) for (i = 0, j = ids.length; i < j; i++) {
      id = ids[i]
      if (id in collection) {
        record = collection[id]

        // LRU update.
        delete collection[id]
        collection[id] = record

        records.push(outputRecord.call(self, type, record))
      }
    }

    else for (id in collection)
      records.push(outputRecord.call(self, type, collection[id]))

    return Promise.resolve(applyOptions(fields, records, options, meta))
  }

  MemoryAdapter.prototype.create = function (type, records, meta) {
    var self = this
    var message = self.message
    var Promise = self.Promise
    var db = self.db
    var recordsPerType = self.options.recordsPerType
    var primaryKey = self.keys.primary
    var ConflictError = self.errors.ConflictError
    var collection = db[type]
    var i, j, record, id, ids, language

    if (!meta) meta = {}
    language = meta.language

    records = map(records, function (record) {
      return inputRecord.call(self, type, record)
    })

    // First check for collisions.
    for (i = 0, j = records.length; i < j; i++) {
      record = records[i]
      id = record[primaryKey]

      if (collection[id])
        return Promise.reject(new ConflictError(
          message('RecordExists', language, { id: id })))
    }

    // Then save it to memory.
    for (i = 0, j = records.length; i < j; i++) {
      record = records[i]
      collection[record[primaryKey]] = record
    }

    // Clear least recently used records.
    if (recordsPerType) {
      ids = Object.keys(collection)

      if (ids.length > recordsPerType) {
        ids = ids.slice(0, ids.length - recordsPerType)

        for (i = 0, j = ids.length; i < j; i++)
          delete collection[ids[i]]
      }
    }

    return map(records, function (record) {
      return outputRecord.call(self, type, record)
    })
  }

  MemoryAdapter.prototype.update = function (type, updates) {
    var self = this
    var Promise = self.Promise
    var db = self.db
    var primaryKey = self.keys.primary
    var collection = db[type]
    var count = 0
    var i, j, update, id, record

    if (!updates.length) return Adapter.prototype.update.call(self)

    for (i = 0, j = updates.length; i < j; i++) {
      update = updates[i]
      id = update[primaryKey]
      record = collection[id]

      if (!record) continue

      count++
      record = outputRecord.call(self, type, record)

      applyUpdate(record, update)

      // LRU update.
      delete collection[id]

      collection[id] = inputRecord.call(self, type, record)
    }

    return Promise.resolve(count)
  }

  MemoryAdapter.prototype.delete = function (type, ids) {
    var Promise = this.Promise
    var db = this.db
    var collection = db[type]
    var count = 0
    var i, j, id

    if (ids && !ids.length) return Adapter.prototype.delete.call(this)

    if (ids) for (i = 0, j = ids.length; i < j; i++) {
      id = ids[i]
      if (collection[id]) {
        delete collection[id]
        count++
      }
    }

    else for (id in collection) {
      delete collection[id]
      count++
    }

    return Promise.resolve(count)
  }

  return MemoryAdapter
}
<|endoftext|>


<commit_before>'use strict';

var AbstractClientResource = require('./../abstract-client-resource');

/**
 * Variable Resource
 * @class
 * @memberof CamSDK.client.resource
 * @augments CamSDK.client.AbstractClientResource
 */
var Variable = AbstractClientResource.extend();

/**
 * Path used by the resource to perform HTTP queries
 * @type {String}
 */
Variable.path = 'variable-instance';

/**
 * Get variable instances
 *
 * @param  {Object}           params
 *
 * @param  {String}           [params.variableName]         Filter by variable instance name.
 *
 * @param  {String}           [params.variableNameLike]     Filter by the variable instance name.
 *                                                          The parameter can include the wildcard %
 *                                                          to express like-strategy such as:
 *                                                          - starts with (%name)
 *                                                          - ends with (name%)
 *                                                          - contains (%name%).
 *
 * @param  {String[]}         [params.processInstanceIdIn]  Only include variable instances which
 *                                                          belong to one of the passed and
 *                                                          comma-separated process instance ids.
 *
 * @param  {String[]}         [params.executionIdIn]        Only include variable instances which
 *                                                          belong to one of the passed and
 *                                                          comma-separated execution ids.
 *
 * @param  {String[]}         [params.caseInstanceIdIn]     Only include variable instances which
 *                                                          belong to one of the passed
 *                                                          case instance ids.
 *
 * @param  {String[]}         [params.caseExecutionIdIn]    Only include variable instances which
 *                                                          belong to one of the passed
 *                                                          case execution ids.
 *
 * @param  {String[]}         [params.taskIdIn]             Only include variable instances which
 *                                                          belong to one of the passed and
 *                                                          comma-separated task ids.
 *
 * @param  {String[]}         [params.activityInstanceIdIn] Only include variable instances which
 *                                                          belong to one of the passed and
 *                                                          comma-separated activity instance ids.
 *
 * @param  {String}           [params.variableValues]       Only include variable instances that
 *                                                          have the certain values. Value filtering
 *                                                          expressions are comma-separated and are
 *                                                          structured as follows:
 *                                                          A valid parameter value has the form
 *                                                          key_operator_value.
 *                                                          key is the variable name,
 *                                                          operator is the comparison operator to
 *                                                          be used and value the variable value.
 *                                                          *Note*: Values are always treated as
 *                                                          String objects on server side.
 *                                                          Valid operator values are:
 *                                                          - eq - equal to
 *                                                          - neq - not equal to
 *                                                          - gt - greater than
 *                                                          - gteq - greater than or equal to
 *                                                          - lt - lower than
 *                                                          - lteq - lower than or equal to
 *                                                          key and value may not contain underscore
 *                                                          or comma characters.
 *
 * @param  {String}           [params.sortBy]               Sort the results lexicographically by a
 *                                                          given criterion. Valid values are
 *                                                          variableName, variableType and
 *                                                          activityInstanceId.
 *                                                          Must be used in conjunction with the
 *                                                          sortOrder parameter.
 *
 * @param  {String}           [params.sortOrder]            Sort the results in a given order.
 *                                                          Values may be asc for ascending order or
 *                                                          desc for descending order.
 *                                                          Must be used in conjunction with the
 *                                                          sortBy parameter.
 *
 * @param  {String}           [params.firstResult]          Pagination of results. Specifies the
 *                                                          index of the first result to return.
 *
 * @param  {String}           [params.maxResults]           Pagination of results. Specifies the
 *                                                          maximum number of results to return.
 *                                                          Will return less results if there are no
 *                                                          more results left.
 *
 * @param  {String}           [params.deserializeValues]    Determines whether serializable variable
 *                                                          values (typically variables that store
 *                                                          custom Java objects) should be
 *                                                          deserialized on server side
 *                                                          (default true).
 *                                                          If set to true, a serializable variable
 *                                                          will be deserialized on server side and
 *                                                          transformed to JSON using
 *                                                          Jackson's POJO/bean property
 *                                                          introspection feature.
 *                                                          Note that this requires the Java classes
 *                                                          of the variable value to be on the
 *                                                          REST API's classpath.
 *                                                          If set to false, a serializable variable
 *                                                          will be returned in its serialized
 *                                                          format.
 *                                                          For example, a variable that is
 *                                                          serialized as XML will be returned as a
 *                                                          JSON string containing XML.
 *                                                          Note:While true is the default value for
 *                                                          reasons of backward compatibility, we
 *                                                          recommend setting this parameter to
 *                                                          false when developing web applications
 *                                                          that are independent of the Java process
 *                                                          applications deployed to the engine.
 *
 * @param  {RequestCallback}  done
 */
Variable.instances = function(params, done) {

  var body = {};
  var query = {};
  var queryParams = ['firstResult', 'maxResults', 'deserializeValues'];

  for (var p in params) {
    if (queryParams.indexOf(p) > -1) {
      query[p] = params[p];
    }
    else {
      body[p] = params[p];
    }
  }

  return this.http.post(this.path, {
    data: body,
    query: query,
    done: done
  });
};

module.exports = Variable;

<commit_msg>feat(api): add variable count<commit_after>'use strict';

var AbstractClientResource = require('./../abstract-client-resource');

/**
 * Variable Resource
 * @class
 * @memberof CamSDK.client.resource
 * @augments CamSDK.client.AbstractClientResource
 */
var Variable = AbstractClientResource.extend();

/**
 * Path used by the resource to perform HTTP queries
 * @type {String}
 */
Variable.path = 'variable-instance';

/**
 * Get variable instances
 *
 * @param  {Object}           params
 *
 * @param  {String}           [params.variableName]         Filter by variable instance name.
 *
 * @param  {String}           [params.variableNameLike]     Filter by the variable instance name.
 *                                                          The parameter can include the wildcard %
 *                                                          to express like-strategy such as:
 *                                                          - starts with (%name)
 *                                                          - ends with (name%)
 *                                                          - contains (%name%).
 *
 * @param  {String[]}         [params.processInstanceIdIn]  Only include variable instances which
 *                                                          belong to one of the passed and
 *                                                          comma-separated process instance ids.
 *
 * @param  {String[]}         [params.executionIdIn]        Only include variable instances which
 *                                                          belong to one of the passed and
 *                                                          comma-separated execution ids.
 *
 * @param  {String[]}         [params.caseInstanceIdIn]     Only include variable instances which
 *                                                          belong to one of the passed
 *                                                          case instance ids.
 *
 * @param  {String[]}         [params.caseExecutionIdIn]    Only include variable instances which
 *                                                          belong to one of the passed
 *                                                          case execution ids.
 *
 * @param  {String[]}         [params.taskIdIn]             Only include variable instances which
 *                                                          belong to one of the passed and
 *                                                          comma-separated task ids.
 *
 * @param  {String[]}         [params.activityInstanceIdIn] Only include variable instances which
 *                                                          belong to one of the passed and
 *                                                          comma-separated activity instance ids.
 *
 * @param  {String}           [params.variableValues]       Only include variable instances that
 *                                                          have the certain values. Value filtering
 *                                                          expressions are comma-separated and are
 *                                                          structured as follows:
 *                                                          A valid parameter value has the form
 *                                                          key_operator_value.
 *                                                          key is the variable name,
 *                                                          operator is the comparison operator to
 *                                                          be used and value the variable value.
 *                                                          *Note*: Values are always treated as
 *                                                          String objects on server side.
 *                                                          Valid operator values are:
 *                                                          - eq - equal to
 *                                                          - neq - not equal to
 *                                                          - gt - greater than
 *                                                          - gteq - greater than or equal to
 *                                                          - lt - lower than
 *                                                          - lteq - lower than or equal to
 *                                                          key and value may not contain underscore
 *                                                          or comma characters.
 *
 * @param  {String}           [params.sortBy]               Sort the results lexicographically by a
 *                                                          given criterion. Valid values are
 *                                                          variableName, variableType and
 *                                                          activityInstanceId.
 *                                                          Must be used in conjunction with the
 *                                                          sortOrder parameter.
 *
 * @param  {String}           [params.sortOrder]            Sort the results in a given order.
 *                                                          Values may be asc for ascending order or
 *                                                          desc for descending order.
 *                                                          Must be used in conjunction with the
 *                                                          sortBy parameter.
 *
 * @param  {String}           [params.firstResult]          Pagination of results. Specifies the
 *                                                          index of the first result to return.
 *
 * @param  {String}           [params.maxResults]           Pagination of results. Specifies the
 *                                                          maximum number of results to return.
 *                                                          Will return less results if there are no
 *                                                          more results left.
 *
 * @param  {String}           [params.deserializeValues]    Determines whether serializable variable
 *                                                          values (typically variables that store
 *                                                          custom Java objects) should be
 *                                                          deserialized on server side
 *                                                          (default true).
 *                                                          If set to true, a serializable variable
 *                                                          will be deserialized on server side and
 *                                                          transformed to JSON using
 *                                                          Jackson's POJO/bean property
 *                                                          introspection feature.
 *                                                          Note that this requires the Java classes
 *                                                          of the variable value to be on the
 *                                                          REST API's classpath.
 *                                                          If set to false, a serializable variable
 *                                                          will be returned in its serialized
 *                                                          format.
 *                                                          For example, a variable that is
 *                                                          serialized as XML will be returned as a
 *                                                          JSON string containing XML.
 *                                                          Note:While true is the default value for
 *                                                          reasons of backward compatibility, we
 *                                                          recommend setting this parameter to
 *                                                          false when developing web applications
 *                                                          that are independent of the Java process
 *                                                          applications deployed to the engine.
 *
 * @param  {RequestCallback}  done
 */
Variable.instances = function(params, done) {

  var body = {};
  var query = {};
  var queryParams = ['firstResult', 'maxResults', 'deserializeValues'];

  for (var p in params) {
    if (queryParams.indexOf(p) > -1) {
      query[p] = params[p];
    }
    else {
      body[p] = params[p];
    }
  }

  return this.http.post(this.path, {
    data: body,
    query: query,
    done: done
  });
};

Variable.count = function(params, done) {
  var path = this.path + '/count';

  return this.http.post(path, {
      data: params,
      done: done
  });
};

module.exports = Variable;

<|endoftext|>


<commit_before>const connectionString = `postgres://${process.env.USER}@localhost:5432/bookstoredb`
const pgp = require('pg-promise')();

const db = pgp(connectionString);

// make querys here

const getAllBooks = function(){
  return db.any("select * from books");
}

const getBookById = function(bookId) {
  return db.one('select books.* from books where id=$1', [bookId]);
}

const getBookWithGenresAndAuthorsById = function(bookId){
  return Promise.all([
    getBookById(bookId),
    getGenresByBookIds([bookId]),
    getAuthorsByBookIds([bookId]),
  ]).then(function(data){
    const book = data[0]
    book.genres = data[1]
    book.authors = data[2]
    return book;
  })
}

const getGenresByBookIds = function(bookId){
  const sql = `
    SELECT
      genres.*,
      book_genres.book_id
    FROM
      genres
    JOIN
      book_genres
    ON
      genres.id = book_genres.genre_id
    WHERE
      book_genres.book_id IN ($1:csv)
  `;
  return db.many(sql, [bookId]);
}

const getAuthorsByBookIds = function(bookId){
  const sql = `
    SELECT
      authors.*,
      author_books.book_id
    FROM
      authors
    JOIN
      author_books
    ON
      authors.id = author_books.author_id
    WHERE
      author_books.book_id IN ($1:csv)
  `;
  return db.many(sql, [bookId]);
}

const getAllBooksWithAuthorsAndGenres = function(){
  return getAllBooks().then(function(books){
    const bookIds = books.map(book => book.id)

    return Promise.all([
      getGenresByBookIds(bookIds),
      getAuthorsByBookIds(bookIds),
    ]).then(function(data){
      console.log('data', data)

      const genres = data[0]
      const authors = data[1]
      books.forEach(function(book){
        book.authors = authors.filter(author => author.book_id === book.id)
        book.genres = genres.filter(genre => genre.book_id === book.id)
      })

      console.log('Books 2nd', books)
      return books
    })
  })
}

const getAllAuthors = function(){
  return db.any('select * from authors');
}

const getAuthorById = function(id){
  return db.one('select authors.* from authors where id=$1', [id]);
}

const getAllGenres = function(){
  return db.any('select * from genres');
}

const getBooksByGenreId = function(genre_id){
  const sql = `
    SELECT
      books.*
    FROM
      books
    JOIN
      book_genres
    ON
      books.id = book_genres.book_id
    WHERE
      book_genres.genre_id=$1
  `;
  console.log(sql)
  return db.any(sql, [genre_id]);
}

const getBooksByAuthorId = function(author_id) {
  const sql = `
    SELECT
      books.*
    FROM
      books
    JOIN
      author_books
    ON
      books.id = author_books.book_id
    WHERE
      author_books.author_id=$1
  `;
  console.log(sql)
  return db.any(sql, [author_id]);
}

const associateAuthorsWithBook = function(authorIds, bookId){
  authorIds = Array.isArray(authorIds) ? authorIds : [authorIds]
  let queries = authorIds.map(authorId => {
    const sql = `
      INSERT INTO
        author_books(book_id, author_id)
      VALUES
        ($1, $2)
    `
    return db.none(sql, [bookId, authorId])
  })
  return Promise.all(queries)
}

const associateGenresWithBook = function(genreIds, bookId){
  genreIds = Array.isArray(genreIds) ? genreIds : [genreIds]

  console.log('IDs', genreIds, bookId);

  let queries = genreIds.map(genreId => {
    let sql = `
      INSERT INTO
        book_genres(book_id, genre_id)
      VALUES
        ($1, $2)
    `
    return db.none(sql, [bookId, genreId])
  })
  return Promise.all(queries)
}

const createAuthor = function(attributes){
  console.log(attributes)
  const sql = `
    INSERT INTO
      authors(image_url, name, description)
    VALUES
      ($1, $2, $3)
    RETURNING
      id
  `
  return db.one(sql, ['', attributes.name, attributes.description])
}

const createBook = function(attributes){
  const sql = `
    INSERT INTO
      books(title, description, published_at, fiction)
    VALUES
      ($1, $2, $3, $4)
    RETURNING
      id
  `
  var queries = [
    db.one(sql, [
      attributes.book.title,
      attributes.book.description,
      'now()',
      true
    ])
  ]
  // also create the authors
  attributes.book.authors.forEach(author =>
    queries.push(createAuthor(author))
  )

  console.log('queries', queries);

  return Promise.all(queries)
    .then(authorIds => {
      authorIds = authorIds.map(x => x.id)
      const bookId = authorIds.shift()
      return Promise.all([
        associateAuthorsWithBook(authorIds, bookId),
        associateGenresWithBook(attributes.book.genres, bookId),
      ]).then(function(){
        return bookId;
      })
    })
}

const searchForBooks = function(options){
  console.log(options)

  const variables = []
  let sql = `
    SELECT
      DISTINCT(books.*)
    FROM
      books
  `
  if (options.search_query){
    let search_query = options.search_query
      .toLowerCase()
      .replace(/^ */, '%')
      .replace(/ *$/, '%')
      .replace(/ +/g, '%')

    variables.push(search_query)
    sql += `
        WHERE
      LOWER(books.title) LIKE $${variables.length}
    `
  }
  console.log('----->', sql, variables)
  return db.any(sql, variables)
}

const searchForBook = searchTerm => {
  const sql = `
    SELECT
      DISTINCT(books.*)
    FROM
      books
    JOIN
      book_author
    ON
      authors.id=book_author.author_id
    JOIN
      books
    ON
      book_author.book_id=books.id
    WHERE
      authors.author LIKE '$1%';
  `
  return db.any(sql, [searchTerm])
}

const searchForAuthor = searchTerm => {
  const sql = `
    SELECT
      DISTINCT(authors.*)
    FROM
      authors
    JOIN
      book_author
    ON
      books.id=book_author.book_id
    JOIN
      authors
    ON
      book_author.author_id=authors.id
    WHERE
      authors.author LIKE '$1%';
  `
  return db.any(sql, [searchTerm])
}

module.exports = {
  pgp: pgp,
  db: db,
  getAllBooks: getAllBooks,
  getAllBooksWithAuthorsAndGenres: getAllBooksWithAuthorsAndGenres,
  getAllAuthors: getAllAuthors,
  getAllGenres: getAllGenres,
  createAuthor:createAuthor,
  createBook: createBook,
  getGenresByBookIds: getGenresByBookIds,
  getBookById: getBookById,
  getBooksByGenreId: getBooksByGenreId,
  getBooksByAuthorId: getBooksByAuthorId,
  getAuthorById: getAuthorById,
  getAuthorsByBookIds: getAuthorsByBookIds,
  getBookWithGenresAndAuthorsById: getBookWithGenresAndAuthorsById,
  searchForBooks: searchForBooks,
  searchForBook: searchForBook,
  searchForAuthor: searchForAuthor,
};
<commit_msg>removed search by author function<commit_after>const connectionString = `postgres://${process.env.USER}@localhost:5432/bookstoredb`
const pgp = require('pg-promise')();

const db = pgp(connectionString);

// make querys here

const getAllBooks = function(){
  return db.any("select * from books");
}

const getBookById = function(bookId) {
  return db.one('select books.* from books where id=$1', [bookId]);
}

const getBookWithGenresAndAuthorsById = function(bookId){
  return Promise.all([
    getBookById(bookId),
    getGenresByBookIds([bookId]),
    getAuthorsByBookIds([bookId]),
  ]).then(function(data){
    const book = data[0]
    book.genres = data[1]
    book.authors = data[2]
    return book;
  })
}

const getGenresByBookIds = function(bookId){
  const sql = `
    SELECT
      genres.*,
      book_genres.book_id
    FROM
      genres
    JOIN
      book_genres
    ON
      genres.id = book_genres.genre_id
    WHERE
      book_genres.book_id IN ($1:csv)
  `;
  return db.many(sql, [bookId]);
}

const getAuthorsByBookIds = function(bookId){
  const sql = `
    SELECT
      authors.*,
      author_books.book_id
    FROM
      authors
    JOIN
      author_books
    ON
      authors.id = author_books.author_id
    WHERE
      author_books.book_id IN ($1:csv)
  `;
  return db.many(sql, [bookId]);
}

const getAllBooksWithAuthorsAndGenres = function(){
  return getAllBooks().then(function(books){
    const bookIds = books.map(book => book.id)

    return Promise.all([
      getGenresByBookIds(bookIds),
      getAuthorsByBookIds(bookIds),
    ]).then(function(data){
      console.log('data', data)

      const genres = data[0]
      const authors = data[1]
      books.forEach(function(book){
        book.authors = authors.filter(author => author.book_id === book.id)
        book.genres = genres.filter(genre => genre.book_id === book.id)
      })

      console.log('Books 2nd', books)
      return books
    })
  })
}

const getAllAuthors = function(){
  return db.any('select * from authors');
}

const getAuthorById = function(id){
  return db.one('select authors.* from authors where id=$1', [id]);
}

const getAllGenres = function(){
  return db.any('select * from genres');
}

const getBooksByGenreId = function(genre_id){
  const sql = `
    SELECT
      books.*
    FROM
      books
    JOIN
      book_genres
    ON
      books.id = book_genres.book_id
    WHERE
      book_genres.genre_id=$1
  `;
  console.log(sql)
  return db.any(sql, [genre_id]);
}

const getBooksByAuthorId = function(author_id) {
  const sql = `
    SELECT
      books.*
    FROM
      books
    JOIN
      author_books
    ON
      books.id = author_books.book_id
    WHERE
      author_books.author_id=$1
  `;
  console.log(sql)
  return db.any(sql, [author_id]);
}

const associateAuthorsWithBook = function(authorIds, bookId){
  authorIds = Array.isArray(authorIds) ? authorIds : [authorIds]
  let queries = authorIds.map(authorId => {
    const sql = `
      INSERT INTO
        author_books(book_id, author_id)
      VALUES
        ($1, $2)
    `
    return db.none(sql, [bookId, authorId])
  })
  return Promise.all(queries)
}

const associateGenresWithBook = function(genreIds, bookId){
  genreIds = Array.isArray(genreIds) ? genreIds : [genreIds]

  console.log('IDs', genreIds, bookId);

  let queries = genreIds.map(genreId => {
    let sql = `
      INSERT INTO
        book_genres(book_id, genre_id)
      VALUES
        ($1, $2)
    `
    return db.none(sql, [bookId, genreId])
  })
  return Promise.all(queries)
}

const createAuthor = function(attributes){
  console.log(attributes)
  const sql = `
    INSERT INTO
      authors(image_url, name, description)
    VALUES
      ($1, $2, $3)
    RETURNING
      id
  `
  return db.one(sql, ['', attributes.name, attributes.description])
}

const createBook = function(attributes){
  const sql = `
    INSERT INTO
      books(title, description, published_at, fiction)
    VALUES
      ($1, $2, $3, $4)
    RETURNING
      id
  `
  var queries = [
    db.one(sql, [
      attributes.book.title,
      attributes.book.description,
      'now()',
      true
    ])
  ]
  // also create the authors
  attributes.book.authors.forEach(author =>
    queries.push(createAuthor(author))
  )

  console.log('queries', queries);

  return Promise.all(queries)
    .then(authorIds => {
      authorIds = authorIds.map(x => x.id)
      const bookId = authorIds.shift()
      return Promise.all([
        associateAuthorsWithBook(authorIds, bookId),
        associateGenresWithBook(attributes.book.genres, bookId),
      ]).then(function(){
        return bookId;
      })
    })
}

const searchForBooks = function(options){
  console.log(options)

  const variables = []
  let sql = `
    SELECT
      DISTINCT(books.*)
    FROM
      books
  `
  if (options.search_query){
    let search_query = options.search_query
      .toLowerCase()
      .replace(/^ */, '%')
      .replace(/ *$/, '%')
      .replace(/ +/g, '%')

    variables.push(search_query)
    sql += `
        WHERE
      LOWER(books.title) LIKE $${variables.length}
    `
  }
  console.log('----->', sql, variables)
  return db.any(sql, variables)
}

const searchForBook = searchTerm => {
  const sql = `
    SELECT
      DISTINCT(books.*)
    FROM
      books
    JOIN
      book_author
    ON
      authors.id=book_author.author_id
    JOIN
      books
    ON
      book_author.book_id=books.id
    WHERE
      authors.author LIKE '$1%';
  `
  return db.any(sql, [searchTerm])
}

module.exports = {
  pgp: pgp,
  db: db,
  getAllBooks: getAllBooks,
  getAllBooksWithAuthorsAndGenres: getAllBooksWithAuthorsAndGenres,
  getAllAuthors: getAllAuthors,
  getAllGenres: getAllGenres,
  createAuthor:createAuthor,
  createBook: createBook,
  getGenresByBookIds: getGenresByBookIds,
  getBookById: getBookById,
  getBooksByGenreId: getBooksByGenreId,
  getBooksByAuthorId: getBooksByAuthorId,
  getAuthorById: getAuthorById,
  getAuthorsByBookIds: getAuthorsByBookIds,
  getBookWithGenresAndAuthorsById: getBookWithGenresAndAuthorsById,
  searchForBooks: searchForBooks,
  searchForBook: searchForBook,
};
<|endoftext|>


<commit_before>
// require dependencies
const controller = require ('controller');

// require helpers
const menuHelper = helper ('menu');

// require cache
const menuConfig = cache ('menus');

// require local dependencies
const acl = require ('lib/utilities/acl');

/**
 * build menu controller
 *
 * @priority 5
 */
class menu extends controller {
  /**
   * construct home controller
   *
   * @param {eden} eden
   */
  constructor () {
    // run super
    super ();

    // bind methods
    this.build = this.build.bind (this);

    // bind private methods
    this._menus = this._menus.bind (this);

    // build eden
    this.build ();
  }

  /**
   * build app
   *
   * @param {express} app
   */
  build () {
    // on render
    this.eden.pre ('view.compile', (render) => {
      // move menus
      if (render.state.menus) render.menus = render.state.menus;

      // delete from state
      delete render.state.menus;
    });

    // set app
    this.eden.router.use (async (req, res, next) => {
      // set req remove
      req.menu = {};

      // set menus
      res.locals.menus = await this._menus (req.user, menuConfig);

      // add function
      req.menu.create = async (type, route, item) => {
        // add menu item
        menuHelper.create (type, route, item, res);

        // set menus
        res.locals.menus = await this._menus (req.user, res.locals.menus);

        // return
        return true;
      };

      // edit function
      req.menu.update = async (type, route, item) => {
        // edit menu item
        menuHelper.update (type, route, item, res);

        // set menus
        res.locals.menus = await this._menus (req.user, res.locals.menus);

        // return
        return true;
      };

      // remove function
      req.menu.remove = async (type, route) => {
        // remove menu item
        menuHelper.remove (type, route, res);

        // set menus
        res.locals.menus = res.locals.menus;

        // return
        return true;
      };

      // run next
      next ();
    });
  }

  /**
   * returns menus
   *
   * @param {user} User
   * @param {Array} menus
   *
   * @return {Array}
   */
  async _menus (User, menus) {
    // clone menus
    let Menus = JSON.parse (JSON.stringify (menus));

    // add hook
    await this.eden.hook ('menu.load', Menus);

    // loop menu types
    for (var type in Menus) {
      // check property
      if (!Menus.hasOwnProperty (type)) continue;

      // set routes
      let Routes = [];

      // set menu
      Menus[type] = Menus[type] || [];

      // loop actual menus
      for (var i = (Menus[type].length - 1); i >= 0; i--) {
        // check routes
        if (Routes.indexOf (Menus[type][i].route) > -1) {
          // delete from menu
          Menus[type].splice (i, 1);

          // continue
          continue;
        }

        // push to routes
        Routes.push (Menus[type][i].route);

        // check if valid acl
        if (Menus[type][i].acl) {
          // validate acl
          if (!await acl.validate (User, Menus[type][i].acl)) {
            // delete from menu
            Menus[type].splice (i, 1);

            // continue
            continue;
          }
        }
      }

      // check menu type
      if (!Menus[type].length) {
        // remove menu
        delete Menus[type];

        // continue
        continue;
      }
    }

    // return menus
    return Menus;
  }
}

/**
 * export menu controller
 *
 * @type {menu}
 */
exports = module.exports = menu;
<commit_msg>amend issues with menu logic<commit_after>
// require dependencies
const controller = require ('controller');

// require helpers
const menuHelper = helper ('menu');

// require cache
const menuConfig = cache ('menus');

// require local dependencies
const acl = require ('lib/utilities/acl');

/**
 * build menu controller
 *
 * @priority 5
 */
class menu extends controller {
  /**
   * construct home controller
   *
   * @param {eden} eden
   */
  constructor () {
    // run super
    super ();

    // bind methods
    this.build = this.build.bind (this);

    // bind private methods
    this._menus = this._menus.bind (this);

    // build eden
    this.build ();
  }

  /**
   * build app
   *
   * @param {express} app
   */
  build () {
    // on render
    this.eden.pre ('view.compile', (render) => {
      // move menus
      if (render.state.menus) render.menus = render.state.menus;

      // delete from state
      delete render.state.menus;
    });

    // set app
    this.eden.router.use (async (req, res, next) => {
      // set req remove
      req.menu = {};

      // set menus
      res.locals.menus = await this._menus (req.user, menuConfig);

      // add function
      req.menu.create = async (type, route, item) => {
        // add menu item
        menuHelper.create (type, route, item, res);

        // return
        return req.menu;
      };

      // edit function
      req.menu.update = async (type, route, item) => {
        // edit menu item
        menuHelper.update (type, route, item, res);

        // return
        return req.menu;
      };

      // remove function
      req.menu.remove = async (type, route) => {
        // remove menu item
        menuHelper.remove (type, route, res);

        // return
        return req.menu;
      };

      // run next
      next ();
    });
  }

  /**
   * returns menus
   *
   * @param {user} User
   * @param {Array} menus
   *
   * @return {Array}
   */
  async _menus (User, menus) {
    // clone menus
    let Menus = JSON.parse (JSON.stringify (menus));

    // add hook
    await this.eden.hook ('menus.init', Menus);

    // loop menu types
    for (var type in Menus) {
      // check property
      if (!Menus.hasOwnProperty (type)) continue;

      // set routes
      let Routes = [];

      // set menu
      Menus[type] = Menus[type] || [];

      // loop actual menus
      for (var i = (Menus[type].length - 1); i >= 0; i--) {
        // check routes
        if (Routes.indexOf (Menus[type][i].route) > -1) {
          // delete from menu
          Menus[type].splice (i, 1);

          // continue
          continue;
        }

        // push to routes
        Routes.push (Menus[type][i].route);

        // check if valid acl
        if (Menus[type][i].acl) {
          // validate acl
          if (!await acl.validate (User, Menus[type][i].acl)) {
            // delete from menu
            Menus[type].splice (i, 1);

            // continue
            continue;
          }
        }
      }

      // check menu type
      if (!Menus[type].length) {
        // remove menu
        delete Menus[type];

        // continue
        continue;
      }
    }

    // return menus
    return Menus;
  }
}

/**
 * export menu controller
 *
 * @type {menu}
 */
exports = module.exports = menu;
<|endoftext|>


<commit_before>/*!
 * Chai - addChainingMethod utility
 * Copyright(c) 2012 Jake Luer <jake@alogicalparadox.com>
 * MIT Licensed
 */

/*!
 * Module dependencies
 */

var transferFlags = require('./transferFlags');

/**
 * ### addChainableMethod (ctx, name, method, chainingBehavior)
 *
 * Adds a method to an object, such that the method can also be chained.
 *
 *     utils.addChainableMethod(chai.Assertion.prototype, 'foo', function (str) {
 *       var obj = utils.flag(this, 'object');
 *       new chai.Assertion(obj).to.be.equal(str);
 *     });
 *
 * Can also be accessed directly from `chai.Assertion`.
 *
 *     chai.Assertion.addChainableMethod('foo', fn, chainingBehavior);
 *
 * The result can then be used as both a method assertion, executing both `method` and
 * `chainingBehavior`, or as a language chain, which only executes `chainingBehavior`.
 *
 *     expect(fooStr).to.be.foo('bar');
 *     expect(fooStr).to.be.foo.equal('foo');
 *
 * @param {Object} ctx object to which the method is added
 * @param {String} name of method to add
 * @param {Function} method function to be used for `name`, when called
 * @param {Function} chainingBehavior function to be called every time the property is accessed
 * @name addChainableMethod
 * @api public
 */

module.exports = function (ctx, name, method, chainingBehavior) {
  if (typeof chainingBehavior !== 'function')
    chainingBehavior = function () { };

  Object.defineProperty(ctx, name,
    { get: function () {
        chainingBehavior.call(this);

        var assert = function () {
          var result = method.apply(this, arguments);
          return result === undefined ? this : result;
        };

        // Re-enumerate every time to better accomodate plugins.
        var asserterNames = Object.getOwnPropertyNames(ctx);
        asserterNames.forEach(function (asserterName) {
          var pd = Object.getOwnPropertyDescriptor(ctx, asserterName)
            , functionProtoPD = Object.getOwnPropertyDescriptor(Function.prototype, asserterName);
          // Avoid trying to overwrite things that we can't, like `length` and `arguments`.
          if (functionProtoPD && !functionProtoPD.configurable) return;
          if (asserterName === 'arguments') return; // @see chaijs/chai/issues/69
          Object.defineProperty(assert, asserterName, pd);
        });

        transferFlags(this, assert);
        return assert;
      }
    , configurable: true
  });
};
<commit_msg>Determine the property names to exclude beforehand.<commit_after>/*!
 * Chai - addChainingMethod utility
 * Copyright(c) 2012 Jake Luer <jake@alogicalparadox.com>
 * MIT Licensed
 */

/*!
 * Module dependencies
 */

var transferFlags = require('./transferFlags');

/*!
 * Module variables
 */

// This method will need to add properties to a function.
// However, some Function.prototype methods cannot be overwritten,
// and there seems no easy cross-platform way to detect them (@see chaijs/chai/issues/69).
var excludeNames = /^(?:length|name|arguments|caller)$/;

/**
 * ### addChainableMethod (ctx, name, method, chainingBehavior)
 *
 * Adds a method to an object, such that the method can also be chained.
 *
 *     utils.addChainableMethod(chai.Assertion.prototype, 'foo', function (str) {
 *       var obj = utils.flag(this, 'object');
 *       new chai.Assertion(obj).to.be.equal(str);
 *     });
 *
 * Can also be accessed directly from `chai.Assertion`.
 *
 *     chai.Assertion.addChainableMethod('foo', fn, chainingBehavior);
 *
 * The result can then be used as both a method assertion, executing both `method` and
 * `chainingBehavior`, or as a language chain, which only executes `chainingBehavior`.
 *
 *     expect(fooStr).to.be.foo('bar');
 *     expect(fooStr).to.be.foo.equal('foo');
 *
 * @param {Object} ctx object to which the method is added
 * @param {String} name of method to add
 * @param {Function} method function to be used for `name`, when called
 * @param {Function} chainingBehavior function to be called every time the property is accessed
 * @name addChainableMethod
 * @api public
 */

module.exports = function (ctx, name, method, chainingBehavior) {
  if (typeof chainingBehavior !== 'function')
    chainingBehavior = function () { };

  Object.defineProperty(ctx, name,
    { get: function () {
        chainingBehavior.call(this);

        var assert = function () {
          var result = method.apply(this, arguments);
          return result === undefined ? this : result;
        };

        // Re-enumerate every time to better accomodate plugins.
        var asserterNames = Object.getOwnPropertyNames(ctx);
        asserterNames.forEach(function (asserterName) {
          if (!excludeNames.test(asserterName)) {
            var pd = Object.getOwnPropertyDescriptor(ctx, asserterName);
            Object.defineProperty(assert, asserterName, pd);
          }
        });

        transferFlags(this, assert);
        return assert;
      }
    , configurable: true
  });
};
<|endoftext|>


<commit_before>var _ = require('../../util').lodash,
    RequestBody = require('../request-body').RequestBody,
    signer = require('aws4'),

    AWSV4 = 'awsv4';

module.exports = {
    name: AWSV4,
    update: function (options) {
        _.extend(this, {
            accessKey: options.accessKey,
            secretKey: options.secretKey,
            region: options.region,
            service: options.service
        });
    },

    /**
     * Processes a request for AWS Signature (V4) authentication. It inserts an "Authorization" header, a
     * header for "X-Amz-Date". The original values of the headers (if any) are overwritten.
     *
     * @param request
     * @returns {*}
     */
    authorize: function (request) {
        var self = this,
            params = request.auth ? request.auth[AWSV4] : undefined,
            mode,
            signedData;

        if (!params) { return request; } // Nothing to do if no parameters are present.

        // Clean up the request (if needed)
        request.removeHeader('Authorization', { ignoreCase: true });
        request.removeHeader('X-Amz-Date', { ignoreCase: true });
        if (!request.getHeaders({ ignoreCase: true })['content-type']) {
            // Since AWS v4 requires a content type header to be set, add one.
            mode = _.get(request, 'body.mode');
            request.addHeader({
                key: 'Content-Type',
                value: (mode === RequestBody.MODES.formdata) ?
                    'multipart/form-data' : 'application/x-www-form-urlencoded'
            });
        }
        signedData = self.sign({
            credentials: {
                accessKeyId: params.accessKey,
                secretAccessKey: params.secretKey
            },
            host: request.url.getHost(),
            path: request.url.getPathWithQuery(),
            service: params.serviceName || 'execute-api', // AWS API Gateway is the default service.
            region: params.region || 'us-east-1',
            method: request.method,
            body: request.body ? request.body.toString() : undefined,
            headers: request.getHeaders()
        });

        _.map(signedData.headers, function (value, key) {
            // TODO: figure out a better way of handling errors.
            if (!_.contains(['content-length', 'content-type'], key.toLowerCase())) {
                request.addHeader({ key: key, value: value });
            }
        });
        return request;
    },

    /**
     * Generates the signature, and returns the Authorization, X-Amz-Date and Content-Type headers.
     * AWS v4 auth mandates that a content type header be present in each request.
     *
     * @param params
     * @param params.credentials {Object} Should contain the AWS credentials, "accessKeyId" and "secretAccessKey"
     * @param params.host {String} Contains the host name for the request
     * @param params.path {String} Contains the complete path, with query string as well, e.g: /something/kane?hi=ho
     * @param params.service {String} The name of the AWS service
     * @param params.region {String} AWS region
     * @param params.method {String} Request method
     * @param params.body {String} Stringified request body
     * @param params.headers {Object} Each key should be a header key, and the value should be a header value
     */
    sign: function (params) {
        return signer.sign(params, params.credentials);
    }
};
<commit_msg>hotfix: use the correct service name in the aws auth helper<commit_after>var _ = require('../../util').lodash,
    RequestBody = require('../request-body').RequestBody,
    signer = require('aws4'),

    AWSV4 = 'awsv4';

module.exports = {
    name: AWSV4,
    update: function (options) {
        _.extend(this, {
            accessKey: options.accessKey,
            secretKey: options.secretKey,
            region: options.region,
            service: options.service
        });
    },

    /**
     * Processes a request for AWS Signature (V4) authentication. It inserts an "Authorization" header, a
     * header for "X-Amz-Date". The original values of the headers (if any) are overwritten.
     *
     * @param request
     * @returns {*}
     */
    authorize: function (request) {
        var self = this,
            params = request.auth ? request.auth[AWSV4] : undefined,
            mode,
            signedData;

        if (!params) { return request; } // Nothing to do if no parameters are present.

        // Clean up the request (if needed)
        request.removeHeader('Authorization', { ignoreCase: true });
        request.removeHeader('X-Amz-Date', { ignoreCase: true });
        if (!request.getHeaders({ ignoreCase: true })['content-type']) {
            // Since AWS v4 requires a content type header to be set, add one.
            mode = _.get(request, 'body.mode');
            request.addHeader({
                key: 'Content-Type',
                value: (mode === RequestBody.MODES.formdata) ?
                    'multipart/form-data' : 'application/x-www-form-urlencoded'
            });
        }
        signedData = self.sign({
            credentials: {
                accessKeyId: params.accessKey,
                secretAccessKey: params.secretKey
            },
            host: request.url.getHost(),
            path: request.url.getPathWithQuery(),
            service: params.service || params.serviceName || 'execute-api', // AWS API Gateway is the default service.
            region: params.region || 'us-east-1',
            method: request.method,
            body: request.body ? request.body.toString() : undefined,
            headers: request.getHeaders()
        });

        _.map(signedData.headers, function (value, key) {
            // TODO: figure out a better way of handling errors.
            if (!_.contains(['content-length', 'content-type'], key.toLowerCase())) {
                request.addHeader({ key: key, value: value });
            }
        });
        return request;
    },

    /**
     * Generates the signature, and returns the Authorization, X-Amz-Date and Content-Type headers.
     * AWS v4 auth mandates that a content type header be present in each request.
     *
     * @param params
     * @param params.credentials {Object} Should contain the AWS credentials, "accessKeyId" and "secretAccessKey"
     * @param params.host {String} Contains the host name for the request
     * @param params.path {String} Contains the complete path, with query string as well, e.g: /something/kane?hi=ho
     * @param params.service {String} The name of the AWS service
     * @param params.region {String} AWS region
     * @param params.method {String} Request method
     * @param params.body {String} Stringified request body
     * @param params.headers {Object} Each key should be a header key, and the value should be a header value
     */
    sign: function (params) {
        return signer.sign(params, params.credentials);
    }
};
<|endoftext|>