`, and ``.
+$font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace !default;
+$font-family-base: $font-family-sans-serif !default;
+
+$font-size-base: 14px !default;
+$font-size-large: ceil(($font-size-base * 1.25)) !default; // ~18px
+$font-size-small: ceil(($font-size-base * 0.85)) !default; // ~12px
+
+$font-size-h1: floor(($font-size-base * 2.6)) !default; // ~36px
+$font-size-h2: floor(($font-size-base * 2.15)) !default; // ~30px
+$font-size-h3: ceil(($font-size-base * 1.7)) !default; // ~24px
+$font-size-h4: ceil(($font-size-base * 1.25)) !default; // ~18px
+$font-size-h5: $font-size-base !default;
+$font-size-h6: ceil(($font-size-base * 0.85)) !default; // ~12px
+
+//** Unit-less `line-height` for use in components like buttons.
+$line-height-base: 1.428571429 !default; // 20/14
+//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
+$line-height-computed: floor(($font-size-base * $line-height-base)) !default; // ~20px
+
+//** By default, this inherits from the ``.
+$headings-font-family: inherit !default;
+$headings-font-weight: 500 !default;
+$headings-line-height: 1.1 !default;
+$headings-color: inherit !default;
+
+
+//== Iconography
+//
+//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
+
+//** Load fonts from this directory.
+
+// [converter] If $bootstrap-sass-asset-helper if used, provide path relative to the assets load path.
+// [converter] This is because some asset helpers, such as Sprockets, do not work with file-relative paths.
+$icon-font-path: if($bootstrap-sass-asset-helper, "bootstrap/", "../fonts/bootstrap/") !default;
+
+//** File name for all font files.
+$icon-font-name: "glyphicons-halflings-regular" !default;
+//** Element ID within SVG icon file.
+$icon-font-svg-id: "glyphicons_halflingsregular" !default;
+
+
+//== Components
+//
+//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
+
+$padding-base-vertical: 6px !default;
+$padding-base-horizontal: 12px !default;
+
+$padding-large-vertical: 10px !default;
+$padding-large-horizontal: 16px !default;
+
+$padding-small-vertical: 5px !default;
+$padding-small-horizontal: 10px !default;
+
+$padding-xs-vertical: 1px !default;
+$padding-xs-horizontal: 5px !default;
+
+$line-height-large: 1.3333333 !default; // extra decimals for Win 8.1 Chrome
+$line-height-small: 1.5 !default;
+
+$border-radius-base: 4px !default;
+$border-radius-large: 6px !default;
+$border-radius-small: 3px !default;
+
+//** Global color for active items (e.g., navs or dropdowns).
+$component-active-color: #fff !default;
+//** Global background color for active items (e.g., navs or dropdowns).
+$component-active-bg: $brand-primary !default;
+
+//** Width of the `border` for generating carets that indicate dropdowns.
+$caret-width-base: 4px !default;
+//** Carets increase slightly in size for larger components.
+$caret-width-large: 5px !default;
+
+
+//== Tables
+//
+//## Customizes the `.table` component with basic values, each used across all table variations.
+
+//** Padding for ``s and ` `s.
+$table-cell-padding: 8px !default;
+//** Padding for cells in `.table-condensed`.
+$table-condensed-cell-padding: 5px !default;
+
+//** Default background color used for all tables.
+$table-bg: transparent !default;
+//** Background color used for `.table-striped`.
+$table-bg-accent: #f9f9f9 !default;
+//** Background color used for `.table-hover`.
+$table-bg-hover: #f5f5f5 !default;
+$table-bg-active: $table-bg-hover !default;
+
+//** Border color for table and cell borders.
+$table-border-color: #ddd !default;
+
+
+//== Buttons
+//
+//## For each of Bootstrap's buttons, define text, background and border color.
+
+$btn-font-weight: normal !default;
+
+$btn-default-color: #333 !default;
+$btn-default-bg: #fff !default;
+$btn-default-border: #ccc !default;
+
+$btn-primary-color: #fff !default;
+$btn-primary-bg: $brand-primary !default;
+$btn-primary-border: darken($btn-primary-bg, 5%) !default;
+
+$btn-success-color: #fff !default;
+$btn-success-bg: $brand-success !default;
+$btn-success-border: darken($btn-success-bg, 5%) !default;
+
+$btn-info-color: #fff !default;
+$btn-info-bg: $brand-info !default;
+$btn-info-border: darken($btn-info-bg, 5%) !default;
+
+$btn-warning-color: #fff !default;
+$btn-warning-bg: $brand-warning !default;
+$btn-warning-border: darken($btn-warning-bg, 5%) !default;
+
+$btn-danger-color: #fff !default;
+$btn-danger-bg: $brand-danger !default;
+$btn-danger-border: darken($btn-danger-bg, 5%) !default;
+
+$btn-link-disabled-color: $gray-light !default;
+
+// Allows for customizing button radius independently from global border radius
+$btn-border-radius-base: $border-radius-base !default;
+$btn-border-radius-large: $border-radius-large !default;
+$btn-border-radius-small: $border-radius-small !default;
+
+
+//== Forms
+//
+//##
+
+//** ` ` background color
+$input-bg: #fff !default;
+//** ` ` background color
+$input-bg-disabled: $gray-lighter !default;
+
+//** Text color for ` `s
+$input-color: $gray !default;
+//** ` ` border color
+$input-border: #ccc !default;
+
+// TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4
+//** Default `.form-control` border radius
+// This has no effect on ``s in some browsers, due to the limited stylability of ``s in CSS.
+$input-border-radius: $border-radius-base !default;
+//** Large `.form-control` border radius
+$input-border-radius-large: $border-radius-large !default;
+//** Small `.form-control` border radius
+$input-border-radius-small: $border-radius-small !default;
+
+//** Border color for inputs on focus
+$input-border-focus: #66afe9 !default;
+
+//** Placeholder text color
+$input-color-placeholder: #999 !default;
+
+//** Default `.form-control` height
+$input-height-base: ($line-height-computed + ($padding-base-vertical * 2) + 2) !default;
+//** Large `.form-control` height
+$input-height-large: (ceil($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 2) !default;
+//** Small `.form-control` height
+$input-height-small: (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 2) !default;
+
+//** `.form-group` margin
+$form-group-margin-bottom: 15px !default;
+
+$legend-color: $gray-dark !default;
+$legend-border-color: #e5e5e5 !default;
+
+//** Background color for textual input addons
+$input-group-addon-bg: $gray-lighter !default;
+//** Border color for textual input addons
+$input-group-addon-border-color: $input-border !default;
+
+//** Disabled cursor for form controls and buttons.
+$cursor-disabled: not-allowed !default;
+
+
+//== Dropdowns
+//
+//## Dropdown menu container and contents.
+
+//** Background for the dropdown menu.
+$dropdown-bg: #fff !default;
+//** Dropdown menu `border-color`.
+$dropdown-border: rgba(0,0,0,.15) !default;
+//** Dropdown menu `border-color` **for IE8**.
+$dropdown-fallback-border: #ccc !default;
+//** Divider color for between dropdown items.
+$dropdown-divider-bg: #e5e5e5 !default;
+
+//** Dropdown link text color.
+$dropdown-link-color: $gray-dark !default;
+//** Hover color for dropdown links.
+$dropdown-link-hover-color: darken($gray-dark, 5%) !default;
+//** Hover background for dropdown links.
+$dropdown-link-hover-bg: #f5f5f5 !default;
+
+//** Active dropdown menu item text color.
+$dropdown-link-active-color: $component-active-color !default;
+//** Active dropdown menu item background color.
+$dropdown-link-active-bg: $component-active-bg !default;
+
+//** Disabled dropdown menu item background color.
+$dropdown-link-disabled-color: $gray-light !default;
+
+//** Text color for headers within dropdown menus.
+$dropdown-header-color: $gray-light !default;
+
+//** Deprecated `$dropdown-caret-color` as of v3.1.0
+$dropdown-caret-color: #000 !default;
+
+
+//-- Z-index master list
+//
+// Warning: Avoid customizing these values. They're used for a bird's eye view
+// of components dependent on the z-axis and are designed to all work together.
+//
+// Note: These variables are not generated into the Customizer.
+
+$zindex-navbar: 1000 !default;
+$zindex-dropdown: 1000 !default;
+$zindex-popover: 1060 !default;
+$zindex-tooltip: 1070 !default;
+$zindex-navbar-fixed: 1030 !default;
+$zindex-modal-background: 1040 !default;
+$zindex-modal: 1050 !default;
+
+
+//== Media queries breakpoints
+//
+//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
+
+// Extra small screen / phone
+//** Deprecated `$screen-xs` as of v3.0.1
+$screen-xs: 480px !default;
+//** Deprecated `$screen-xs-min` as of v3.2.0
+$screen-xs-min: $screen-xs !default;
+//** Deprecated `$screen-phone` as of v3.0.1
+$screen-phone: $screen-xs-min !default;
+
+// Small screen / tablet
+//** Deprecated `$screen-sm` as of v3.0.1
+$screen-sm: 768px !default;
+$screen-sm-min: $screen-sm !default;
+//** Deprecated `$screen-tablet` as of v3.0.1
+$screen-tablet: $screen-sm-min !default;
+
+// Medium screen / desktop
+//** Deprecated `$screen-md` as of v3.0.1
+$screen-md: 992px !default;
+$screen-md-min: $screen-md !default;
+//** Deprecated `$screen-desktop` as of v3.0.1
+$screen-desktop: $screen-md-min !default;
+
+// Large screen / wide desktop
+//** Deprecated `$screen-lg` as of v3.0.1
+$screen-lg: 1200px !default;
+$screen-lg-min: $screen-lg !default;
+//** Deprecated `$screen-lg-desktop` as of v3.0.1
+$screen-lg-desktop: $screen-lg-min !default;
+
+// So media queries don't overlap when required, provide a maximum
+$screen-xs-max: ($screen-sm-min - 1) !default;
+$screen-sm-max: ($screen-md-min - 1) !default;
+$screen-md-max: ($screen-lg-min - 1) !default;
+
+
+//== Grid system
+//
+//## Define your custom responsive grid.
+
+//** Number of columns in the grid.
+$grid-columns: 12 !default;
+//** Padding between columns. Gets divided in half for the left and right.
+$grid-gutter-width: 30px !default;
+// Navbar collapse
+//** Point at which the navbar becomes uncollapsed.
+$grid-float-breakpoint: $screen-sm-min !default;
+//** Point at which the navbar begins collapsing.
+$grid-float-breakpoint-max: ($grid-float-breakpoint - 1) !default;
+
+
+//== Container sizes
+//
+//## Define the maximum width of `.container` for different screen sizes.
+
+// Small screen / tablet
+$container-tablet: (720px + $grid-gutter-width) !default;
+//** For `$screen-sm-min` and up.
+$container-sm: $container-tablet !default;
+
+// Medium screen / desktop
+$container-desktop: (940px + $grid-gutter-width) !default;
+//** For `$screen-md-min` and up.
+$container-md: $container-desktop !default;
+
+// Large screen / wide desktop
+$container-large-desktop: (1140px + $grid-gutter-width) !default;
+//** For `$screen-lg-min` and up.
+$container-lg: $container-large-desktop !default;
+
+
+//== Navbar
+//
+//##
+
+// Basics of a navbar
+$navbar-height: 50px !default;
+$navbar-margin-bottom: $line-height-computed !default;
+$navbar-border-radius: $border-radius-base !default;
+$navbar-padding-horizontal: floor(($grid-gutter-width / 2)) !default;
+$navbar-padding-vertical: (($navbar-height - $line-height-computed) / 2) !default;
+$navbar-collapse-max-height: 340px !default;
+
+$navbar-default-color: #777 !default;
+$navbar-default-bg: #f8f8f8 !default;
+$navbar-default-border: darken($navbar-default-bg, 6.5%) !default;
+
+// Navbar links
+$navbar-default-link-color: #777 !default;
+$navbar-default-link-hover-color: #333 !default;
+$navbar-default-link-hover-bg: transparent !default;
+$navbar-default-link-active-color: #555 !default;
+$navbar-default-link-active-bg: darken($navbar-default-bg, 6.5%) !default;
+$navbar-default-link-disabled-color: #ccc !default;
+$navbar-default-link-disabled-bg: transparent !default;
+
+// Navbar brand label
+$navbar-default-brand-color: $navbar-default-link-color !default;
+$navbar-default-brand-hover-color: darken($navbar-default-brand-color, 10%) !default;
+$navbar-default-brand-hover-bg: transparent !default;
+
+// Navbar toggle
+$navbar-default-toggle-hover-bg: #ddd !default;
+$navbar-default-toggle-icon-bar-bg: #888 !default;
+$navbar-default-toggle-border-color: #ddd !default;
+
+
+//=== Inverted navbar
+// Reset inverted navbar basics
+$navbar-inverse-color: lighten($gray-light, 15%) !default;
+$navbar-inverse-bg: #222 !default;
+$navbar-inverse-border: darken($navbar-inverse-bg, 10%) !default;
+
+// Inverted navbar links
+$navbar-inverse-link-color: lighten($gray-light, 15%) !default;
+$navbar-inverse-link-hover-color: #fff !default;
+$navbar-inverse-link-hover-bg: transparent !default;
+$navbar-inverse-link-active-color: $navbar-inverse-link-hover-color !default;
+$navbar-inverse-link-active-bg: darken($navbar-inverse-bg, 10%) !default;
+$navbar-inverse-link-disabled-color: #444 !default;
+$navbar-inverse-link-disabled-bg: transparent !default;
+
+// Inverted navbar brand label
+$navbar-inverse-brand-color: $navbar-inverse-link-color !default;
+$navbar-inverse-brand-hover-color: #fff !default;
+$navbar-inverse-brand-hover-bg: transparent !default;
+
+// Inverted navbar toggle
+$navbar-inverse-toggle-hover-bg: #333 !default;
+$navbar-inverse-toggle-icon-bar-bg: #fff !default;
+$navbar-inverse-toggle-border-color: #333 !default;
+
+
+//== Navs
+//
+//##
+
+//=== Shared nav styles
+$nav-link-padding: 10px 15px !default;
+$nav-link-hover-bg: $gray-lighter !default;
+
+$nav-disabled-link-color: $gray-light !default;
+$nav-disabled-link-hover-color: $gray-light !default;
+
+//== Tabs
+$nav-tabs-border-color: #ddd !default;
+
+$nav-tabs-link-hover-border-color: $gray-lighter !default;
+
+$nav-tabs-active-link-hover-bg: $body-bg !default;
+$nav-tabs-active-link-hover-color: $gray !default;
+$nav-tabs-active-link-hover-border-color: #ddd !default;
+
+$nav-tabs-justified-link-border-color: #ddd !default;
+$nav-tabs-justified-active-link-border-color: $body-bg !default;
+
+//== Pills
+$nav-pills-border-radius: $border-radius-base !default;
+$nav-pills-active-link-hover-bg: $component-active-bg !default;
+$nav-pills-active-link-hover-color: $component-active-color !default;
+
+
+//== Pagination
+//
+//##
+
+$pagination-color: $link-color !default;
+$pagination-bg: #fff !default;
+$pagination-border: #ddd !default;
+
+$pagination-hover-color: $link-hover-color !default;
+$pagination-hover-bg: $gray-lighter !default;
+$pagination-hover-border: #ddd !default;
+
+$pagination-active-color: #fff !default;
+$pagination-active-bg: $brand-primary !default;
+$pagination-active-border: $brand-primary !default;
+
+$pagination-disabled-color: $gray-light !default;
+$pagination-disabled-bg: #fff !default;
+$pagination-disabled-border: #ddd !default;
+
+
+//== Pager
+//
+//##
+
+$pager-bg: $pagination-bg !default;
+$pager-border: $pagination-border !default;
+$pager-border-radius: 15px !default;
+
+$pager-hover-bg: $pagination-hover-bg !default;
+
+$pager-active-bg: $pagination-active-bg !default;
+$pager-active-color: $pagination-active-color !default;
+
+$pager-disabled-color: $pagination-disabled-color !default;
+
+
+//== Jumbotron
+//
+//##
+
+$jumbotron-padding: 30px !default;
+$jumbotron-color: inherit !default;
+$jumbotron-bg: $gray-lighter !default;
+$jumbotron-heading-color: inherit !default;
+$jumbotron-font-size: ceil(($font-size-base * 1.5)) !default;
+$jumbotron-heading-font-size: ceil(($font-size-base * 4.5)) !default;
+
+
+//== Form states and alerts
+//
+//## Define colors for form feedback states and, by default, alerts.
+
+$state-success-text: #3c763d !default;
+$state-success-bg: #dff0d8 !default;
+$state-success-border: darken(adjust-hue($state-success-bg, -10), 5%) !default;
+
+$state-info-text: #31708f !default;
+$state-info-bg: #d9edf7 !default;
+$state-info-border: darken(adjust-hue($state-info-bg, -10), 7%) !default;
+
+$state-warning-text: #8a6d3b !default;
+$state-warning-bg: #fcf8e3 !default;
+$state-warning-border: darken(adjust-hue($state-warning-bg, -10), 5%) !default;
+
+$state-danger-text: #a94442 !default;
+$state-danger-bg: #f2dede !default;
+$state-danger-border: darken(adjust-hue($state-danger-bg, -10), 5%) !default;
+
+
+//== Tooltips
+//
+//##
+
+//** Tooltip max width
+$tooltip-max-width: 200px !default;
+//** Tooltip text color
+$tooltip-color: #fff !default;
+//** Tooltip background color
+$tooltip-bg: #000 !default;
+$tooltip-opacity: .9 !default;
+
+//** Tooltip arrow width
+$tooltip-arrow-width: 5px !default;
+//** Tooltip arrow color
+$tooltip-arrow-color: $tooltip-bg !default;
+
+
+//== Popovers
+//
+//##
+
+//** Popover body background color
+$popover-bg: #fff !default;
+//** Popover maximum width
+$popover-max-width: 276px !default;
+//** Popover border color
+$popover-border-color: rgba(0,0,0,.2) !default;
+//** Popover fallback border color
+$popover-fallback-border-color: #ccc !default;
+
+//** Popover title background color
+$popover-title-bg: darken($popover-bg, 3%) !default;
+
+//** Popover arrow width
+$popover-arrow-width: 10px !default;
+//** Popover arrow color
+$popover-arrow-color: $popover-bg !default;
+
+//** Popover outer arrow width
+$popover-arrow-outer-width: ($popover-arrow-width + 1) !default;
+//** Popover outer arrow color
+$popover-arrow-outer-color: fade_in($popover-border-color, 0.05) !default;
+//** Popover outer arrow fallback color
+$popover-arrow-outer-fallback-color: darken($popover-fallback-border-color, 20%) !default;
+
+
+//== Labels
+//
+//##
+
+//** Default label background color
+$label-default-bg: $gray-light !default;
+//** Primary label background color
+$label-primary-bg: $brand-primary !default;
+//** Success label background color
+$label-success-bg: $brand-success !default;
+//** Info label background color
+$label-info-bg: $brand-info !default;
+//** Warning label background color
+$label-warning-bg: $brand-warning !default;
+//** Danger label background color
+$label-danger-bg: $brand-danger !default;
+
+//** Default label text color
+$label-color: #fff !default;
+//** Default text color of a linked label
+$label-link-hover-color: #fff !default;
+
+
+//== Modals
+//
+//##
+
+//** Padding applied to the modal body
+$modal-inner-padding: 15px !default;
+
+//** Padding applied to the modal title
+$modal-title-padding: 15px !default;
+//** Modal title line-height
+$modal-title-line-height: $line-height-base !default;
+
+//** Background color of modal content area
+$modal-content-bg: #fff !default;
+//** Modal content border color
+$modal-content-border-color: rgba(0,0,0,.2) !default;
+//** Modal content border color **for IE8**
+$modal-content-fallback-border-color: #999 !default;
+
+//** Modal backdrop background color
+$modal-backdrop-bg: #000 !default;
+//** Modal backdrop opacity
+$modal-backdrop-opacity: .5 !default;
+//** Modal header border color
+$modal-header-border-color: #e5e5e5 !default;
+//** Modal footer border color
+$modal-footer-border-color: $modal-header-border-color !default;
+
+$modal-lg: 900px !default;
+$modal-md: 600px !default;
+$modal-sm: 300px !default;
+
+
+//== Alerts
+//
+//## Define alert colors, border radius, and padding.
+
+$alert-padding: 15px !default;
+$alert-border-radius: $border-radius-base !default;
+$alert-link-font-weight: bold !default;
+
+$alert-success-bg: $state-success-bg !default;
+$alert-success-text: $state-success-text !default;
+$alert-success-border: $state-success-border !default;
+
+$alert-info-bg: $state-info-bg !default;
+$alert-info-text: $state-info-text !default;
+$alert-info-border: $state-info-border !default;
+
+$alert-warning-bg: $state-warning-bg !default;
+$alert-warning-text: $state-warning-text !default;
+$alert-warning-border: $state-warning-border !default;
+
+$alert-danger-bg: $state-danger-bg !default;
+$alert-danger-text: $state-danger-text !default;
+$alert-danger-border: $state-danger-border !default;
+
+
+//== Progress bars
+//
+//##
+
+//** Background color of the whole progress component
+$progress-bg: #f5f5f5 !default;
+//** Progress bar text color
+$progress-bar-color: #fff !default;
+//** Variable for setting rounded corners on progress bar.
+$progress-border-radius: $border-radius-base !default;
+
+//** Default progress bar color
+$progress-bar-bg: $brand-primary !default;
+//** Success progress bar color
+$progress-bar-success-bg: $brand-success !default;
+//** Warning progress bar color
+$progress-bar-warning-bg: $brand-warning !default;
+//** Danger progress bar color
+$progress-bar-danger-bg: $brand-danger !default;
+//** Info progress bar color
+$progress-bar-info-bg: $brand-info !default;
+
+
+//== List group
+//
+//##
+
+//** Background color on `.list-group-item`
+$list-group-bg: #fff !default;
+//** `.list-group-item` border color
+$list-group-border: #ddd !default;
+//** List group border radius
+$list-group-border-radius: $border-radius-base !default;
+
+//** Background color of single list items on hover
+$list-group-hover-bg: #f5f5f5 !default;
+//** Text color of active list items
+$list-group-active-color: $component-active-color !default;
+//** Background color of active list items
+$list-group-active-bg: $component-active-bg !default;
+//** Border color of active list elements
+$list-group-active-border: $list-group-active-bg !default;
+//** Text color for content within active list items
+$list-group-active-text-color: lighten($list-group-active-bg, 40%) !default;
+
+//** Text color of disabled list items
+$list-group-disabled-color: $gray-light !default;
+//** Background color of disabled list items
+$list-group-disabled-bg: $gray-lighter !default;
+//** Text color for content within disabled list items
+$list-group-disabled-text-color: $list-group-disabled-color !default;
+
+$list-group-link-color: #555 !default;
+$list-group-link-hover-color: $list-group-link-color !default;
+$list-group-link-heading-color: #333 !default;
+
+
+//== Panels
+//
+//##
+
+$panel-bg: #fff !default;
+$panel-body-padding: 15px !default;
+$panel-heading-padding: 10px 15px !default;
+$panel-footer-padding: $panel-heading-padding !default;
+$panel-border-radius: $border-radius-base !default;
+
+//** Border color for elements within panels
+$panel-inner-border: #ddd !default;
+$panel-footer-bg: #f5f5f5 !default;
+
+$panel-default-text: $gray-dark !default;
+$panel-default-border: #ddd !default;
+$panel-default-heading-bg: #f5f5f5 !default;
+
+$panel-primary-text: #fff !default;
+$panel-primary-border: $brand-primary !default;
+$panel-primary-heading-bg: $brand-primary !default;
+
+$panel-success-text: $state-success-text !default;
+$panel-success-border: $state-success-border !default;
+$panel-success-heading-bg: $state-success-bg !default;
+
+$panel-info-text: $state-info-text !default;
+$panel-info-border: $state-info-border !default;
+$panel-info-heading-bg: $state-info-bg !default;
+
+$panel-warning-text: $state-warning-text !default;
+$panel-warning-border: $state-warning-border !default;
+$panel-warning-heading-bg: $state-warning-bg !default;
+
+$panel-danger-text: $state-danger-text !default;
+$panel-danger-border: $state-danger-border !default;
+$panel-danger-heading-bg: $state-danger-bg !default;
+
+
+//== Thumbnails
+//
+//##
+
+//** Padding around the thumbnail image
+$thumbnail-padding: 4px !default;
+//** Thumbnail background color
+$thumbnail-bg: $body-bg !default;
+//** Thumbnail border color
+$thumbnail-border: #ddd !default;
+//** Thumbnail border radius
+$thumbnail-border-radius: $border-radius-base !default;
+
+//** Custom text color for thumbnail captions
+$thumbnail-caption-color: $text-color !default;
+//** Padding around the thumbnail caption
+$thumbnail-caption-padding: 9px !default;
+
+
+//== Wells
+//
+//##
+
+$well-bg: #f5f5f5 !default;
+$well-border: darken($well-bg, 7%) !default;
+
+
+//== Badges
+//
+//##
+
+$badge-color: #fff !default;
+//** Linked badge text color on hover
+$badge-link-hover-color: #fff !default;
+$badge-bg: $gray-light !default;
+
+//** Badge text color in active nav link
+$badge-active-color: $link-color !default;
+//** Badge background color in active nav link
+$badge-active-bg: #fff !default;
+
+$badge-font-weight: bold !default;
+$badge-line-height: 1 !default;
+$badge-border-radius: 10px !default;
+
+
+//== Breadcrumbs
+//
+//##
+
+$breadcrumb-padding-vertical: 8px !default;
+$breadcrumb-padding-horizontal: 15px !default;
+//** Breadcrumb background color
+$breadcrumb-bg: #f5f5f5 !default;
+//** Breadcrumb text color
+$breadcrumb-color: #ccc !default;
+//** Text color of current page in the breadcrumb
+$breadcrumb-active-color: $gray-light !default;
+//** Textual separator for between breadcrumb elements
+$breadcrumb-separator: "/" !default;
+
+
+//== Carousel
+//
+//##
+
+$carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6) !default;
+
+$carousel-control-color: #fff !default;
+$carousel-control-width: 15% !default;
+$carousel-control-opacity: .5 !default;
+$carousel-control-font-size: 20px !default;
+
+$carousel-indicator-active-bg: #fff !default;
+$carousel-indicator-border-color: #fff !default;
+
+$carousel-caption-color: #fff !default;
+
+
+//== Close
+//
+//##
+
+$close-font-weight: bold !default;
+$close-color: #000 !default;
+$close-text-shadow: 0 1px 0 #fff !default;
+
+
+//== Code
+//
+//##
+
+$code-color: #c7254e !default;
+$code-bg: #f9f2f4 !default;
+
+$kbd-color: #fff !default;
+$kbd-bg: #333 !default;
+
+$pre-bg: #f5f5f5 !default;
+$pre-color: $gray-dark !default;
+$pre-border-color: #ccc !default;
+$pre-scrollable-max-height: 340px !default;
+
+
+//== Type
+//
+//##
+
+//** Horizontal offset for forms and lists.
+$component-offset-horizontal: 180px !default;
+//** Text muted color
+$text-muted: $gray-light !default;
+//** Abbreviations and acronyms border color
+$abbr-border-color: $gray-light !default;
+//** Headings small color
+$headings-small-color: $gray-light !default;
+//** Blockquote small color
+$blockquote-small-color: $gray-light !default;
+//** Blockquote font size
+$blockquote-font-size: ($font-size-base * 1.25) !default;
+//** Blockquote border color
+$blockquote-border-color: $gray-lighter !default;
+//** Page header border color
+$page-header-border-color: $gray-lighter !default;
+//** Width of horizontal description list titles
+$dl-horizontal-offset: $component-offset-horizontal !default;
+//** Point at which .dl-horizontal becomes horizontal
+$dl-horizontal-breakpoint: $grid-float-breakpoint !default;
+//** Horizontal line color.
+$hr-border: $gray-lighter !default;
diff --git a/lab-raziyeh/app/scss/lib/_custom-bootstrap.scss b/lab-raziyeh/app/scss/lib/_custom-bootstrap.scss
new file mode 100644
index 0000000..74cd7a9
--- /dev/null
+++ b/lab-raziyeh/app/scss/lib/_custom-bootstrap.scss
@@ -0,0 +1,57 @@
+/*!
+ * Bootstrap v3.3.7 (http://getbootstrap.com)
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+// Core variables and mixins
+//@import "~bootstrap-sass/assets/stylesheets/bootstrap/variables";
+@import "custom-bootstrap-variables";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/mixins";
+
+// Reset and dependencies
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/normalize";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/print";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/glyphicons";
+
+// Core CSS
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/scaffolding";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/type";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/code";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/grid";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/tables";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/forms";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/buttons";
+
+// Components
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/component-animations";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/dropdowns";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/button-groups";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/input-groups";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/navs";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/navbar";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/breadcrumbs";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/pagination";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/pager";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/labels";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/badges";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/jumbotron";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/thumbnails";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/alerts";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/progress-bars";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/media";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/list-group";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/panels";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/responsive-embed";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/wells";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/close";
+
+// Components w/ JavaScript
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/modals";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/tooltip";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/popovers";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/carousel";
+
+// Utility classes
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/utilities";
+@import "~bootstrap-sass/assets/stylesheets/bootstrap/responsive-utilities";
diff --git a/lab-raziyeh/app/scss/lib/_theme.scss b/lab-raziyeh/app/scss/lib/_theme.scss
new file mode 100644
index 0000000..6b13123
--- /dev/null
+++ b/lab-raziyeh/app/scss/lib/_theme.scss
@@ -0,0 +1,14 @@
+$app-primary: #a7f7fa;
+$app-secondary: #7fa7af;
+$app-disabled: #abeaeb;
+$app-active: #fa7fa7;
+$app-success: #a7ff7a;
+$app-error: #ff7a7a;
+$app-warn: #ffa77a;
+$app-info: #7aa7ff;
+$app-white: #fff;
+$app-black: #000;
+
+.navbar-nav{
+ margin-top:5px;
+}
\ No newline at end of file
diff --git a/lab-raziyeh/app/scss/lib/_vendor.scss b/lab-raziyeh/app/scss/lib/_vendor.scss
new file mode 100644
index 0000000..693ed46
--- /dev/null
+++ b/lab-raziyeh/app/scss/lib/_vendor.scss
@@ -0,0 +1,4 @@
+
+@import "custom-bootstrap";
+// font-awesome
+@import "~font-awesome/scss/font-awesome.scss";
\ No newline at end of file
diff --git a/lab-raziyeh/app/scss/main.scss b/lab-raziyeh/app/scss/main.scss
new file mode 100644
index 0000000..e91e094
--- /dev/null
+++ b/lab-raziyeh/app/scss/main.scss
@@ -0,0 +1,9 @@
+@import "_custom-bootstrap";
+@import "theme";
+
+html, body {
+ width: 100%;
+ height: 100%;
+ background:#f5f5f5;
+}
+
diff --git a/lab-raziyeh/app/service/auth-service.js b/lab-raziyeh/app/service/auth-service.js
new file mode 100644
index 0000000..a9c4656
--- /dev/null
+++ b/lab-raziyeh/app/service/auth-service.js
@@ -0,0 +1,85 @@
+'use strict';
+
+module.exports = ['$q', '$log', '$http', '$window', authService];
+
+function authService($q, $log, $http, $window){
+ $log.debug('init authService');
+ // create service
+ let service = {};
+ service.token = null;
+
+ service.setToken = function(_token){
+ $log.debug('authService.service.setToken()');
+ if (! _token)
+ return $q.reject(new Error('no service.token'));
+ $window.localStorage.setItem('token', _token);
+ service.token = _token;
+ return $q.resolve(service.token);
+ };
+
+ service.getToken = function(){
+ $log.debug('authService.getToken');
+ if (service.token) return $q.resolve(service.token);
+ service.token = $window.localStorage.getItem('service.token');
+ if (service.token) return $q.resolve(service.token);
+ return $q.reject(new Error('token not found'));
+ };
+
+ service.logout = function(){
+ $log.debug('authService.logout()');
+ $window.localStorage.removeItem('service.token');
+ service.token = null;
+ return $q.resolve();
+ };
+
+ service.signup = function(user) {
+ $log.debug('authService.signup()');
+ let url = `${__API_URL__}/api/signup`;
+ console.log('signup url', url);
+
+ let config = {
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ },
+ };
+
+ return $http.post(url, user, config)
+ .then( res => {
+ $log.log('success', res.data);
+ // res.data is the response body aka the service.token
+ return service.setToken(res.data);
+ })
+ .catch(err => {
+ $log.error('fail', err.message);
+ return $q.reject(err);
+ });
+ };
+
+ service.login = function(user){
+ $log.debug('authService.login()');
+ let url = `${__API_URL__}/api/login`;
+ // base64 encoded 'username:password'
+ let base64 = $window.btoa(`${user.username}:${user.password}`);
+
+ let config = {
+ headers: {
+ Accept: 'application/json',
+ Authorization: `Basic ${base64}`,
+ },
+ };
+
+ return $http.get(url, config)
+ .then( res => {
+ $log.log('success', res.data);
+ return service.setToken(res.data);
+ })
+ .catch( err => {
+ $log.error(err.message);
+ return $q.reject(err);
+ });
+ };
+
+ // return service
+ return service;
+}
\ No newline at end of file
diff --git a/lab-raziyeh/app/service/gallery-service.js b/lab-raziyeh/app/service/gallery-service.js
new file mode 100644
index 0000000..dbef112
--- /dev/null
+++ b/lab-raziyeh/app/service/gallery-service.js
@@ -0,0 +1,122 @@
+'use strict';
+
+module.exports = ['$q', '$log', '$http', 'authService', function($q, $log, $http , authService) {
+ $log.debug('init gallery Service');
+
+ let service = {};
+
+ service.galleries = [];
+ service.createGallery = function(gallery) {
+ $log.debug('galleryService.createGallery()');
+
+ return authService.getToken()
+ .then( token => {
+ let url = `${__API_URL__}/api/gallery`;
+ let config = {
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`,
+ },
+ };
+
+ return $http.post(url, gallery, config);
+ })
+ .then(res => {
+ $log.log('successful create gallery');
+ let gallery = res.data;
+ service.galleries.unshift(gallery);
+ return gallery;
+ })
+ .catch( err => {
+ $log.error(err.message);
+ return $q.reject(err);
+ });
+ };
+
+ service.deleteGallery = function (galleryID){
+ $log.debug('galleryService.deleteGallery()');
+ return authService.getToken()
+ .then( token => {
+ let url = `${__API_URL__}/api/gallery/${galleryID}`;
+ let config = {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: 'application/json, text/plain, */*',
+ },
+ };
+
+ return $http.delete(url, config);
+ })
+ .then( () => {
+ $log.log('successful delete user galleries');
+ for (let i=0; i< service.galleries.length; i++) {
+ if(service.galleries[i]._id === galleryID)
+ service.galleries.splice(i,1);
+ }
+ return;
+ })
+ .catch(err => {
+ return $q.reject(err);
+ });
+ };
+
+ service.fetchGalleries = function() {
+ $log.debug('galleryService.fetchGallery()');
+
+ return authService.getToken()
+ .then( token => {
+ let url = `${__API_URL__}/api/gallery/`;
+ let config = {
+ headers: {
+ Accept: 'application/json',
+ Authorization: `Bearer ${token}`,
+ },
+ };
+
+ return $http.get(url, config);
+ })
+ .then(res => {
+ $log.log('successful fetch user galleries');
+ service.galleries = res.data;
+ return service.galleries;
+ })
+ .catch(err => {
+ $log.error(err.message);
+ return $q.reject(err);
+ });
+ };
+
+ service.updateGallery = function (galleryID, galleryData){
+ $log.debug('galleryService.updateGallery()');
+ return authService.getToken()
+ .then( token => {
+ let url = `${__API_URL__}/api/gallery/${galleryID}`;
+ let config = {
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`,
+ },
+ };
+
+ return $http.put(url, galleryData, config);
+ })
+ .then( res => {
+ let gallery = res.data;
+ $log.log('successful update user galleries');
+ for( let i=0; i< service.galleries.length; i++) {
+ if(galleryID === service.galleries[i]._id) {
+ service.galleries[i] = gallery;
+ }
+ }
+ return gallery;
+ })
+ .catch(err => {
+ $log.error(err.message);
+ return $q.reject(err);
+ });
+ };
+
+ return service;
+}];
\ No newline at end of file
diff --git a/lab-raziyeh/app/service/pic-service.js b/lab-raziyeh/app/service/pic-service.js
new file mode 100644
index 0000000..4c68ca3
--- /dev/null
+++ b/lab-raziyeh/app/service/pic-service.js
@@ -0,0 +1,69 @@
+'use strict';
+
+module.exports = ['$q', '$log', '$http', 'Upload', 'authService', picService];
+
+function picService($q, $log, $http, Upload, authService){
+ $log.debug('init picService');
+ let service = {};
+
+ service.uploadGalleryPic = function(galleryData, picData){
+ $log.debug('picService.uploadGalleryPic()');
+
+ return authService.getToken()
+ .then( token => {
+ let url = `${__API_URL__}/api/gallery/${galleryData._id}/pic`;
+ let headers = {
+ Authorization: `Bearer ${token}`,
+ Accept: 'application/json',
+ };
+
+ return Upload.upload({
+ url,
+ headers,
+ method: 'POST',
+ data: {
+ name: picData.name,
+ desc: picData.desc,
+ file: picData.file,
+ },
+ });
+ })
+ .then(res => {
+ galleryData.pics.unshift(res.data);
+ $log.log('success\n', res.data);
+ return res.data;
+ })
+ .catch( err => {
+ $log.error(err.message);
+ return $q.reject(err);
+ });
+ };
+
+
+ service.deleteGalleryPic = function(galleryData, picData){
+ $log.debug('picService.deleteGalleryPic()');
+ return authService.getToken()
+ .then( token => {
+ let url = `${__API_URL__}/api/gallery/${galleryData._id}/pic/${picData._id}`;
+ let config = {
+ headers: {
+ Accept: 'application/json',
+ Authorization: `Bearer ${token}`,
+ },
+ };
+
+ return $http.delete(url, config);
+ })
+ .then( () => {
+ $log.log('successful delete Pic');
+ var index = galleryData.pics.indexOf(picData._id);
+ return galleryData.pics.splice(index,1);
+ })
+ .catch(err => {
+ $log.error(err.message);
+ return $q.reject(err);
+ });
+ };
+
+ return service;
+}
\ No newline at end of file
diff --git a/lab-raziyeh/app/view/home/_home.scss b/lab-raziyeh/app/view/home/_home.scss
new file mode 100644
index 0000000..872b4fc
--- /dev/null
+++ b/lab-raziyeh/app/view/home/_home.scss
@@ -0,0 +1,19 @@
+@import "theme";
+
+.home {
+ .panel-heading {
+ background-color: $app-primary;
+ span {
+ font-weight: bold;
+ color: $app-secondary;
+ }
+
+ span:hover {
+ color: $app-black;
+ }
+ }
+}
+
+.textBold {
+ font-weight: 600;
+}
\ No newline at end of file
diff --git a/lab-raziyeh/app/view/home/home-controller.js b/lab-raziyeh/app/view/home/home-controller.js
new file mode 100644
index 0000000..bea131a
--- /dev/null
+++ b/lab-raziyeh/app/view/home/home-controller.js
@@ -0,0 +1,34 @@
+'use strict';
+
+require('./_home.scss');
+
+module.exports = ['$log', '$rootScope', 'galleryService', HomeController ];
+
+function HomeController($log, $rootScope, galleryService){
+ $log.debug('init homeCtrl');
+ this.galleries = [];
+
+ this.fetchGalleries = function(){
+ galleryService.fetchGalleries()
+ .then( galleries => {
+ this.galleries = galleries;
+ this.currentGallery = galleries[0];
+ console.log('this.galleries', this.galleries);
+ });
+ };
+
+ this.galleryDeleteDone = function(gallery){
+ $log.debug('homeCtrl.galleryDeleteDone()');
+ if (this.currentGallery._id == gallery._id){
+ this.currentGallery = null;
+ }
+ };
+
+ this.fetchGalleries();
+
+ // when the locationChangeSuccess fetchGalleries
+ $rootScope.$on('$locationChangeSuccess', () => {
+ this.fetchGalleries();
+ });
+
+}
\ No newline at end of file
diff --git a/lab-raziyeh/app/view/home/home.html b/lab-raziyeh/app/view/home/home.html
new file mode 100644
index 0000000..515dab7
--- /dev/null
+++ b/lab-raziyeh/app/view/home/home.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lab-raziyeh/app/view/landing/_landing.scss b/lab-raziyeh/app/view/landing/_landing.scss
new file mode 100644
index 0000000..9c9f32f
--- /dev/null
+++ b/lab-raziyeh/app/view/landing/_landing.scss
@@ -0,0 +1,30 @@
+@import "_theme";
+
+.landing {
+ background-color: $app-secondary;
+}
+
+.navbar-inverse {
+ background-color: #6f94a2 !important;
+ border-color: #6f94a2 !important;
+}
+.navbar-inverse a {
+ color:#fff!important;
+ font-weight: 400;
+}
+.navbar-nav > li > a {
+ padding-top:10px !important;
+ padding-bottom:10px !important;
+}
+img {
+ max-width: 100px;
+}
+
+// .mainDiv{
+// width: 100%;
+// height: 100%;
+// background-image: url('../data/background.png');
+// background-repeat: no-repeat;
+// background-size: cover;
+// margin: 0 auto;
+// }
\ No newline at end of file
diff --git a/lab-raziyeh/app/view/landing/landing-controller.js b/lab-raziyeh/app/view/landing/landing-controller.js
new file mode 100644
index 0000000..1736688
--- /dev/null
+++ b/lab-raziyeh/app/view/landing/landing-controller.js
@@ -0,0 +1,13 @@
+'use strict';
+
+require('./_landing.scss');
+
+module.exports = ['$log', function LandingController($log){
+ $log.debug('Landing controller');
+
+ this.showsignin = false;
+
+ this.signin = function() {
+ this.showsignin? this.showsignin = false : this.showsignin = true;
+ };
+}];
diff --git a/lab-raziyeh/app/view/landing/landing.html b/lab-raziyeh/app/view/landing/landing.html
new file mode 100644
index 0000000..80e333c
--- /dev/null
+++ b/lab-raziyeh/app/view/landing/landing.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/lab-raziyeh/app/view/login/_login.scss b/lab-raziyeh/app/view/login/_login.scss
new file mode 100644
index 0000000..091f421
--- /dev/null
+++ b/lab-raziyeh/app/view/login/_login.scss
@@ -0,0 +1,8 @@
+div.login {
+ margin-top: 100px;
+}
+
+.active {
+ color: #111;
+ font-weight: 600;
+}
\ No newline at end of file
diff --git a/lab-raziyeh/app/view/login/login-controller.js b/lab-raziyeh/app/view/login/login-controller.js
new file mode 100644
index 0000000..b4963e3
--- /dev/null
+++ b/lab-raziyeh/app/view/login/login-controller.js
@@ -0,0 +1,23 @@
+'use strict';
+
+require('./_login.scss');
+
+module.exports = ['$log',LoginController];
+
+function LoginController($log){
+ $log.debug('Login controller');
+
+ this.showsignin = false;
+
+ this.signin = function() {
+ this.showsignin? this.showsignin = false : this.showsignin = true;
+ };
+ let googleAuthBase = 'https://accounts.google.com/o/oauth2/v2/auth';
+ let googleAuthResponseType = 'response_type=code';
+ let googleAuthClientID = `client_id=${__GOOGLE_CLIENT_ID__}`;
+ let googleAuthScope = 'scope=profile%20email%20openid';
+ let googleAuthRedirectURI = 'redirect_uri=http://localhost:3000/api/oauth/signout-api';
+ let googleAuthAccessType = 'access_type=offline';
+ let googleAuthPrompt = 'prompt=consent';
+ this.googleAuthURL = `${googleAuthBase}?${googleAuthResponseType}&${googleAuthClientID}&${googleAuthScope}&${googleAuthRedirectURI}&${googleAuthAccessType}&${googleAuthPrompt}`;
+}
diff --git a/lab-raziyeh/app/view/login/login.html b/lab-raziyeh/app/view/login/login.html
new file mode 100644
index 0000000..72c1094
--- /dev/null
+++ b/lab-raziyeh/app/view/login/login.html
@@ -0,0 +1,31 @@
+
+
diff --git a/lab-raziyeh/client-test/controller-test/create-gallery-controller-test.js b/lab-raziyeh/client-test/controller-test/create-gallery-controller-test.js
new file mode 100644
index 0000000..c7dce3d
--- /dev/null
+++ b/lab-raziyeh/client-test/controller-test/create-gallery-controller-test.js
@@ -0,0 +1,50 @@
+'use strict';
+
+describe('testing edit-gallery controller', function() {
+ var url = 'http://localhost:3000/api/gallery';
+
+ beforeEach(() => {
+ angular.mock.module('demoApp');
+ angular.mock.inject(($rootScope, authService, $componentController, $httpBackend ) => {
+ authService.setToken('1234');
+
+ this.authService = authService;
+ this.$rootScope = $rootScope;
+ this.$componentController = $componentController;
+ this.$httpBackend = $httpBackend;
+ });
+ });
+
+ afterEach(() => {
+ this.$httpBackend.verifyNoOutstandingExpectation();
+ this.$httpBackend.verifyNoOutstandingRequest();
+ this.authService.logout();
+ });
+
+ describe('testing createGalley', () => {
+ it('it should create a gallery', () => {
+ let gallery = {
+ name: 'lulwat',
+ desc: 'hello',
+ };
+ let headers = {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer 1234',
+ };
+
+ this.$httpBackend.expectPOST(url, gallery, headers)
+ .respond(200, {_id:'23770504', username: 'Rozi', name: 'xxx', desc:'xxx' , pics: []});
+
+ let createGalleryCtrl = this.$componentController('createGallery');
+
+ createGalleryCtrl.gallery = { name: 'lulwat', desc: 'hello'};
+ createGalleryCtrl.createGallery();
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+
+ expect(createGalleryCtrl.gallery.name).toBe.null;
+ expect(createGalleryCtrl.gallery.desc).toBe.null;
+ });
+ });
+});
\ No newline at end of file
diff --git a/lab-raziyeh/client-test/controller-test/edit-gallery-controller-test.js b/lab-raziyeh/client-test/controller-test/edit-gallery-controller-test.js
new file mode 100644
index 0000000..7db1bdc
--- /dev/null
+++ b/lab-raziyeh/client-test/controller-test/edit-gallery-controller-test.js
@@ -0,0 +1,67 @@
+'use strict';
+
+describe('testing edit-gallery controller', function() {
+ var url = 'http://localhost:3000/api/gallery';
+
+ beforeEach(() => {
+ angular.mock.module('demoApp');
+ angular.mock.inject(($rootScope, authService, $componentController, $httpBackend ) => {
+ authService.setToken('1234');
+
+ this.authService = authService;
+ this.$rootScope = $rootScope;
+ this.$componentController = $componentController;
+ this.$httpBackend = $httpBackend;
+ });
+ });
+
+ afterEach(() => {
+ this.$httpBackend.verifyNoOutstandingExpectation();
+ this.$httpBackend.verifyNoOutstandingRequest();
+ this.authService.logout();
+ });
+
+ describe('testing component', () => {
+ it('testing component bindings', () => {
+ let mockBindings = {
+ gallery: {
+ name: 'lulwat',
+ desc: 'this is a test',
+ },
+ };
+
+ let editGalleryCtrl = this.$componentController('editGallery', null, mockBindings);
+ expect(editGalleryCtrl.gallery.name).toEqual(mockBindings.gallery.name);
+ expect(editGalleryCtrl.gallery.desc).toEqual(mockBindings.gallery.desc);
+ this.$rootScope.$apply();
+ });
+ });
+
+ describe('testing updateGalley', () => {
+ it('it make a valid PUT request', () => {
+ let headers = {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer 1234',
+ };
+
+ this.$httpBackend.expectPUT(`${url}/12345`, {_id: '12345', name: 'new name', desc: 'hello'}, headers)
+ .respond(200);
+
+ let mockBindings = {
+ gallery: {
+ _id: '12345',
+ name: 'lulwat',
+ desc: 'hello',
+ },
+ };
+ let editGalleryCtrl = this.$componentController('editGallery', null, mockBindings);
+ editGalleryCtrl.gallery.name = 'new name';
+
+ editGalleryCtrl.updateGallery();
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+ });
+ });
+
+});
\ No newline at end of file
diff --git a/lab-raziyeh/client-test/controller-test/gallery-items-controller-test.js b/lab-raziyeh/client-test/controller-test/gallery-items-controller-test.js
new file mode 100644
index 0000000..f2cecf3
--- /dev/null
+++ b/lab-raziyeh/client-test/controller-test/gallery-items-controller-test.js
@@ -0,0 +1,70 @@
+'use strict';
+
+describe('testing gallery-li controller', function(){
+ beforeEach(() => {
+ angular.mock.module('demoApp');
+ angular.mock.inject(($rootScope, authService, $componentController, $httpBackend) => {
+ authService.setToken('secrettoken');
+
+ this.authService = authService;
+ this.$httpBackend = $httpBackend;
+ this.$rootScope = $rootScope;
+ this.$componentController = $componentController;
+ });
+ });
+
+ afterEach(() => {
+ this.$httpBackend.verifyNoOutstandingExpectation();
+ this.$httpBackend.verifyNoOutstandingRequest();
+ this.authService.logout();
+ });
+
+ describe('testing #deleteDone', () => {
+ it('should call deleteDone', () => {
+ let mockBindings = {
+ gallery: {
+ _id: '65432ONE',
+ name: 'hello',
+ desc: 'infomative',
+ pics: [],
+ },
+ deleteDone: function(data){
+ expect(data.galleryData._id).toEqual('65432ONE');
+ },
+ };
+
+ let galleryLICtrl = this.$componentController('galleryItems', null, mockBindings);
+ galleryLICtrl.deleteDone({galleryData: galleryLICtrl.gallery});
+
+ this.$rootScope.$apply();
+ });
+ });
+
+ it('should call deleteDone with gallery after galleryDelete', () => {
+ let url = 'http://localhost:3000/api/gallery/65432ONE';
+ let headers = {
+ Authorization: 'Bearer secrettoken',
+ Accept: 'application/json, text/plain, */*',
+ };
+
+ let mockBindings = {
+ gallery: {
+ _id: '65432ONE',
+ name: 'hello',
+ desc: 'infomative',
+ pics: [],
+ },
+ deleteDone: function(data){
+ expect(data.galleryData._id).toEqual(mockBindings.gallery._id);
+ },
+ };
+
+ this.$httpBackend.expectDELETE(url, headers).respond(204);
+
+ let galleryLICtrl = this.$componentController('galleryItems', null, mockBindings);
+ galleryLICtrl.deleteGallery();
+
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+ });
+});
\ No newline at end of file
diff --git a/lab-raziyeh/client-test/controller-test/signin-controller-test.js b/lab-raziyeh/client-test/controller-test/signin-controller-test.js
new file mode 100644
index 0000000..9242941
--- /dev/null
+++ b/lab-raziyeh/client-test/controller-test/signin-controller-test.js
@@ -0,0 +1,54 @@
+'use strict';
+
+describe('testing edit-gallery controller', function() {
+ var url = 'http://localhost:3000/api/login';
+
+ beforeEach(() => {
+ angular.mock.module('demoApp');
+ angular.mock.inject(($rootScope, authService, $componentController, $httpBackend, $location, $window) => {
+ authService.setToken('1234');
+
+ this.authService = authService;
+ this.$rootScope = $rootScope;
+ this.$componentController = $componentController;
+ this.$httpBackend = $httpBackend;
+ this.$location = $location;
+ this.$window = $window;
+ });
+ });
+
+ afterEach(() => {
+ this.$httpBackend.verifyNoOutstandingExpectation();
+ this.$httpBackend.verifyNoOutstandingRequest();
+ this.authService.logout();
+ });
+
+ describe('testing signin', () => {
+ it('it should create a user', () => {
+
+ let user = {
+ name: 'lulwat',
+ password: '1234',
+ username: 'rozi',
+ };
+
+ var base64 = this.$window.btoa(`${user.username}:${user.password}`);
+
+ let headers = {
+ 'Accept': 'application/json',
+ Authorization:`Basic ${base64}`,
+ };
+
+ this.$httpBackend.expectGET(url, headers)
+ .respond(200, {_id:'23770504', username: 'Rozi', name: 'lulwat', password:'1234'});
+
+ let signinCtrl = this.$componentController('signin');
+
+ signinCtrl.signin(user);
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+
+ expect(this.$location.path()).toBe('/home');
+ });
+ });
+});
\ No newline at end of file
diff --git a/lab-raziyeh/client-test/controller-test/signup-controller-test.js b/lab-raziyeh/client-test/controller-test/signup-controller-test.js
new file mode 100644
index 0000000..c566cd4
--- /dev/null
+++ b/lab-raziyeh/client-test/controller-test/signup-controller-test.js
@@ -0,0 +1,50 @@
+'use strict';
+
+describe('testing edit-gallery controller', function() {
+ var url = 'http://localhost:3000/api/signup';
+
+ beforeEach(() => {
+ angular.mock.module('demoApp');
+ angular.mock.inject(($rootScope, authService, $componentController, $httpBackend, $location) => {
+ authService.setToken('1234');
+
+ this.authService = authService;
+ this.$rootScope = $rootScope;
+ this.$componentController = $componentController;
+ this.$httpBackend = $httpBackend;
+ this.$location = $location;
+ });
+ });
+
+ afterEach(() => {
+ this.$httpBackend.verifyNoOutstandingExpectation();
+ this.$httpBackend.verifyNoOutstandingRequest();
+ this.authService.logout();
+ });
+
+ describe('testing signup', () => {
+ it('it should create a user', () => {
+ let user = {
+ name: 'lulwat',
+ password: '1234',
+ username: 'rozi',
+ };
+
+ let headers = {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ };
+
+ this.$httpBackend.expectPOST(url, user, headers)
+ .respond(200, {_id:'23770504', username: 'Rozi', name: 'lulwat', password:'1234'});
+
+ let signupCtrl = this.$componentController('signup');
+
+ signupCtrl.signup(user);
+ this.$httpBackend.flush();
+ this.$rootScope.$apply();
+
+ expect(this.$location.path()).toBe('/home');
+ });
+ });
+});
\ No newline at end of file
diff --git a/lab-raziyeh/client-test/controller-test/thumbnail-container-controller-test.js b/lab-raziyeh/client-test/controller-test/thumbnail-container-controller-test.js
new file mode 100644
index 0000000..c58a167
--- /dev/null
+++ b/lab-raziyeh/client-test/controller-test/thumbnail-container-controller-test.js
@@ -0,0 +1,39 @@
+'use strict';
+
+describe('testing edit-gallery controller', function() {
+ var url = 'http://localhost:3000/api/gallery';
+
+ beforeEach(() => {
+ angular.mock.module('demoApp');
+ angular.mock.inject(($rootScope, authService, $componentController, $httpBackend ) => {
+ authService.setToken('1234');
+
+ this.authService = authService;
+ this.$rootScope = $rootScope;
+ this.$componentController = $componentController;
+ this.$httpBackend = $httpBackend;
+ });
+ });
+
+ afterEach(() => {
+ this.$httpBackend.verifyNoOutstandingExpectation();
+ this.$httpBackend.verifyNoOutstandingRequest();
+ this.authService.logout();
+ });
+
+ describe('testing component', () => {
+ it('testing component bindings', () => {
+ let mockBindings = {
+ gallery: {
+ name: 'lulwat',
+ desc: 'this is a test',
+ },
+ };
+
+ let thumbnailContainerCtrl = this.$componentController('thumbnailContainer', null, mockBindings);
+ expect(thumbnailContainerCtrl.gallery.name).toEqual(mockBindings.gallery.name);
+ expect(thumbnailContainerCtrl.gallery.desc).toEqual(mockBindings.gallery.desc);
+ this.$rootScope.$apply();
+ });
+ });
+});
\ No newline at end of file
diff --git a/lab-raziyeh/client-test/service-test/auth-service-test.js b/lab-raziyeh/client-test/service-test/auth-service-test.js
new file mode 100644
index 0000000..8f609d7
--- /dev/null
+++ b/lab-raziyeh/client-test/service-test/auth-service-test.js
@@ -0,0 +1,86 @@
+'use strict';
+
+describe('testing auth service', function() {
+ beforeEach(() => {
+ angular.mock.module('demoApp');
+ angular.mock.inject(($rootScope, authService, $window, $httpBackend) => {
+ this.authService = authService;
+ this.$window = $window;
+ this.$rootScope = $rootScope;
+ authService.setToken('1234');
+
+ this.$httpBackend = $httpBackend;
+ });
+ });
+
+ var url = 'http://localhost:3000/api';
+ var user = {
+ name: 'Rozi',
+ username: 'rozibzargan',
+ password: '123',
+ };
+
+ describe('testing setToken()', ()=> {
+ it('should return a Token', ()=> {
+ this.authService.setToken('hello')
+ .then(token => {
+ expect(token).toBe('hello');
+ });
+
+ this.$rootScope.$apply();
+ });
+ });
+
+ describe('testing getToken()', ()=> {
+ it('should return a Token', ()=> {
+ this.authService.token = null;
+ this.$window.localStorage.setItem('token', 'hello');
+
+ this.authService.getToken()
+ .then(token => {
+ expect(token).toBe('hello');
+ });
+
+ this.$rootScope.$apply();
+ });
+ });
+
+ describe('testing sign up', () => {
+ it('should create user', () => {
+
+ let headers = {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ };
+
+ this.$httpBackend.expectPOST(`${url}/signup`, user, headers)
+ .respond(200, '100001');
+
+ this.authService.signup(user)
+ .then(token => {
+ expect(token).toBe('100001');
+ });
+ this.$httpBackend.flush();
+ });
+ });
+
+ describe('testing sign in', () => {
+ it('should return a user', () => {
+ var base64 = this.$window.btoa(`${user.username}:${user.password}`);
+
+ let headers = {
+ Accept: 'application/json',
+ Authorization: `Basic ${base64}`,
+ };
+
+ this.$httpBackend.expectGET(`${url}/login`, headers)
+ .respond(200, '10000');
+
+ this.authService.login(user)
+ .then(token => {
+ expect(token).toBe('10000');
+ });
+ this.$httpBackend.flush();
+ });
+ });
+});
\ No newline at end of file
diff --git a/lab-raziyeh/client-test/service-test/gallery-service-test.js b/lab-raziyeh/client-test/service-test/gallery-service-test.js
new file mode 100644
index 0000000..016373b
--- /dev/null
+++ b/lab-raziyeh/client-test/service-test/gallery-service-test.js
@@ -0,0 +1,125 @@
+'use strict';
+
+describe('testing gallery service', function() {
+ var url = 'http://localhost:3000/api';
+ let galleryData = {
+ name: 'documents',
+ desc: 'all notes from my interviews',
+ };
+
+ beforeEach(() => {
+ angular.mock.module('demoApp');
+ angular.mock.inject((authService, galleryService, $httpBackend, $window) => {
+ this.authService = authService;
+ authService.setToken('1234');
+
+ this.galleryService = galleryService;
+ this.$httpBackend = $httpBackend;
+ this.$window = $window;
+ });
+ });
+
+ afterEach(() => {
+ this.authService.setToken(null);
+ this.$window.localStorage.clear();
+ });
+
+ describe('testing galleryService.createGallery', () => {
+ it('should return a gallery', () => {
+ let galleryData = {
+ name: 'documents',
+ desc: 'all notes from my interviews',
+ };
+
+ let headers = {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ Authorization: 'Bearer 1234',
+ };
+
+ this.$httpBackend.expectPOST(`${url}/gallery`, galleryData, headers)
+ .respond(200, {_id:'23770504', username: 'Rozi', name: galleryData.name, desc: galleryData.desc, pics: []});
+
+ this.galleryService.createGallery(galleryData)
+ .then(gallery => {
+ expect(gallery._id).toBe('23770504');
+ expect(gallery.name).toBe(galleryData.name);
+ expect(gallery.desc).toBe(galleryData.desc);
+ });
+ this.$httpBackend.flush();
+ });
+ });
+
+ describe('testing galleryService.fetchGalleries', () => {
+ let galleries = [
+ {
+ name: 'documents',
+ desc: 'all notes from my interviews',
+ },
+ {
+ name: 'documents2',
+ desc: 'all notes from my interviews',
+ },
+ {
+ name: 'documents3',
+ desc: 'all notes from my interviews',
+ },
+
+ ];
+
+ it('should return a gallery', () => {
+
+ let headers = {
+ Accept: 'application/json',
+ Authorization: 'Bearer 1234',
+ };
+
+ this.$httpBackend.expectGET(`${url}/gallery/`, headers)
+ .respond(200, galleries);
+
+ this.galleryService.fetchGalleries()
+ .then(galleries => {
+ expect(galleries.length).toBe(3);
+ expect(galleries[0].name).toBe(galleries[0].name);
+ expect(galleries[0].desc).toBe(galleries[0].desc);
+ });
+ this.$httpBackend.flush();
+ });
+ });
+
+ describe('testing galleryService.updateGallery', () => {
+ it('should update a gallery', () => {
+ let galleryId = '1000',
+ headers = {
+ 'Content-Type':'application/json',
+ Accept: 'application/json',
+ Authorization:'Bearer 1234',
+ };
+ this.$httpBackend.expectPUT(`${url}/gallery/1000`, galleryData, headers)
+ .respond(200, {_id:'1000', username: 'Rozi', name: 'updated name', desc: 'updated Desc', pics: []});
+
+ this.galleryService.updateGallery(galleryId, galleryData)
+ .then(gallery => {
+ expect(gallery._id).toBe(galleryId);
+ expect(gallery.name).toEqual('updated name');
+ expect(gallery.desc).toEqual('updated Desc');
+ });
+ this.$httpBackend.flush();
+ });
+ });
+
+ describe('testing galleryService.deleteGallery', () => {
+ it('should delete a gallery', () => {
+ let galleryId = '1000',
+ headers = {
+ Authorization: 'Bearer 1234',
+ Accept: 'application/json, text/plain, */*',
+ };
+ this.$httpBackend.expectDELETE(`${url}/gallery/1000`, headers)
+ .respond(204);
+
+ this.galleryService.deleteGallery(galleryId);
+ this.$httpBackend.flush();
+ });
+ });
+});
\ No newline at end of file
diff --git a/lab-raziyeh/client-test/service-test/pic-service-test.js b/lab-raziyeh/client-test/service-test/pic-service-test.js
new file mode 100644
index 0000000..c38148e
--- /dev/null
+++ b/lab-raziyeh/client-test/service-test/pic-service-test.js
@@ -0,0 +1,66 @@
+// 'use strict';
+
+// describe('testing Pic service', function() {
+// var url = 'http://localhost:3000/api';
+// // let galleryData = {
+// // name: 'documents',
+// // desc: 'all notes from my interviews',
+// // };
+
+
+// beforeEach(() => {
+// angular.mock.module('demoApp');
+// angular.mock.inject(($rootScope,authService, galleryService, picService, $httpBackend, Upload) => {
+// this.authService = authService;
+// authService.setToken('1234');
+// this.$rootScope =$rootScope ;
+// this.galleryService = galleryService;
+// this.picService = picService;
+// this.$httpBackend = $httpBackend;
+// this.$upload = Upload;
+// });
+// });
+
+// describe('testing picService.uploadGalleryPic', () => {
+// let gallery = {
+// _id: '111',
+// name: 'documents',
+// desc: 'all notes from my interviews',
+// };
+
+// it('should create a pic', () => {
+
+
+// var pic = {
+// name: 'test',
+// desc: 'test data',
+// file: `${__dirname}/data/razi.jpg`,
+// };
+
+// let headers = {
+// 'Content-Type': 'application/json',
+// Accept: 'application/json',
+// Authorization: 'Bearer 1234',
+// };
+
+// this.$httpBackend.expectPOST(`${url}/gallery/${111}/pic`, pic, headers)
+// .respond(200);
+// // {
+// // name: pic.name,
+// // desc: pic.desc,
+// // imageURI: 'https://gallery01.s3.amazonaws.com/e44df56e5139f271c341736e82065aae.png',
+// // objectKey: 'e44df56e5139f271c341736e82065aae.png',
+// // userID: '"58111458118eb6427278f8b0"',
+// // username:'omidomid'}
+
+// this.picService.uploadGalleryPic(gallery, pic)
+// .then((data) => {
+// expect(data.name).toBe(pic.name);
+
+// });
+// this.$httpBackend.flush();
+// this.$rootScope.$apply();
+
+// });
+// });
+// });
\ No newline at end of file
diff --git a/lab-raziyeh/db/WiredTiger b/lab-raziyeh/db/WiredTiger
new file mode 100644
index 0000000..f586677
--- /dev/null
+++ b/lab-raziyeh/db/WiredTiger
@@ -0,0 +1,2 @@
+WiredTiger
+WiredTiger 2.8.1: (March 24, 2016)
diff --git a/lab-raziyeh/db/WiredTiger.lock b/lab-raziyeh/db/WiredTiger.lock
new file mode 100644
index 0000000..3d84206
--- /dev/null
+++ b/lab-raziyeh/db/WiredTiger.lock
@@ -0,0 +1 @@
+WiredTiger lock file
diff --git a/lab-raziyeh/db/WiredTiger.turtle b/lab-raziyeh/db/WiredTiger.turtle
new file mode 100644
index 0000000..d257c74
--- /dev/null
+++ b/lab-raziyeh/db/WiredTiger.turtle
@@ -0,0 +1,6 @@
+WiredTiger version string
+WiredTiger 2.8.1: (March 24, 2016)
+WiredTiger version
+major=2,minor=8,patch=1
+file:WiredTiger.wt
+allocation_size=4KB,app_metadata=,block_allocation=best,block_compressor=,cache_resident=0,checkpoint=(WiredTigerCheckpoint.279=(addr="018981e4449adf688a81e4b4b8def28b81e42f5baaf1808080e3016fc0e311dfc0",order=279,time=1478558181,size=1179648,write_gen=685)),checkpoint_lsn=(8,3584),checksum=uncompressed,collator=,columns=,dictionary=0,encryption=(keyid=,name=),format=btree,huffman_key=,huffman_value=,id=0,internal_item_max=0,internal_key_max=0,internal_key_truncate=,internal_page_max=4KB,key_format=S,key_gap=10,leaf_item_max=0,leaf_key_max=0,leaf_page_max=32KB,leaf_value_max=0,log=(enabled=),memory_page_max=5MB,os_cache_dirty_max=0,os_cache_max=0,prefix_compression=0,prefix_compression_min=4,split_deepen_min_child=0,split_deepen_per_child=0,split_pct=75,value_format=S,version=(major=1,minor=1)
diff --git a/lab-raziyeh/db/WiredTiger.wt b/lab-raziyeh/db/WiredTiger.wt
new file mode 100644
index 0000000..445bec0
Binary files /dev/null and b/lab-raziyeh/db/WiredTiger.wt differ
diff --git a/lab-raziyeh/db/WiredTigerLAS.wt b/lab-raziyeh/db/WiredTigerLAS.wt
new file mode 100644
index 0000000..3f019cb
Binary files /dev/null and b/lab-raziyeh/db/WiredTigerLAS.wt differ
diff --git a/lab-raziyeh/db/_mdb_catalog.wt b/lab-raziyeh/db/_mdb_catalog.wt
new file mode 100644
index 0000000..fae007b
Binary files /dev/null and b/lab-raziyeh/db/_mdb_catalog.wt differ
diff --git a/lab-raziyeh/db/collection-0--3143307283938210215.wt b/lab-raziyeh/db/collection-0--3143307283938210215.wt
new file mode 100644
index 0000000..2c6ffe1
Binary files /dev/null and b/lab-raziyeh/db/collection-0--3143307283938210215.wt differ
diff --git a/lab-raziyeh/db/collection-0-6564260200608951873.wt b/lab-raziyeh/db/collection-0-6564260200608951873.wt
new file mode 100644
index 0000000..3f019cb
Binary files /dev/null and b/lab-raziyeh/db/collection-0-6564260200608951873.wt differ
diff --git a/lab-raziyeh/db/collection-0-8845950676657731124.wt b/lab-raziyeh/db/collection-0-8845950676657731124.wt
new file mode 100644
index 0000000..03beea9
Binary files /dev/null and b/lab-raziyeh/db/collection-0-8845950676657731124.wt differ
diff --git a/lab-raziyeh/db/collection-3-6564260200608951873.wt b/lab-raziyeh/db/collection-3-6564260200608951873.wt
new file mode 100644
index 0000000..3f019cb
Binary files /dev/null and b/lab-raziyeh/db/collection-3-6564260200608951873.wt differ
diff --git a/lab-raziyeh/db/collection-3-8845950676657731124.wt b/lab-raziyeh/db/collection-3-8845950676657731124.wt
new file mode 100644
index 0000000..49879e5
Binary files /dev/null and b/lab-raziyeh/db/collection-3-8845950676657731124.wt differ
diff --git a/lab-raziyeh/db/collection-9-6564260200608951873.wt b/lab-raziyeh/db/collection-9-6564260200608951873.wt
new file mode 100644
index 0000000..3f019cb
Binary files /dev/null and b/lab-raziyeh/db/collection-9-6564260200608951873.wt differ
diff --git a/lab-raziyeh/db/collection-9-8845950676657731124.wt b/lab-raziyeh/db/collection-9-8845950676657731124.wt
new file mode 100644
index 0000000..ccc0d7c
Binary files /dev/null and b/lab-raziyeh/db/collection-9-8845950676657731124.wt differ
diff --git a/lab-raziyeh/db/diagnostic.data/metrics.2016-10-25T17-11-24Z-00000 b/lab-raziyeh/db/diagnostic.data/metrics.2016-10-25T17-11-24Z-00000
new file mode 100644
index 0000000..378c6e6
Binary files /dev/null and b/lab-raziyeh/db/diagnostic.data/metrics.2016-10-25T17-11-24Z-00000 differ
diff --git a/lab-raziyeh/db/diagnostic.data/metrics.2016-10-25T20-27-51Z-00000 b/lab-raziyeh/db/diagnostic.data/metrics.2016-10-25T20-27-51Z-00000
new file mode 100644
index 0000000..df7b8f7
Binary files /dev/null and b/lab-raziyeh/db/diagnostic.data/metrics.2016-10-25T20-27-51Z-00000 differ
diff --git a/lab-raziyeh/db/diagnostic.data/metrics.2016-10-29T05-14-51Z-00000 b/lab-raziyeh/db/diagnostic.data/metrics.2016-10-29T05-14-51Z-00000
new file mode 100644
index 0000000..29fa144
Binary files /dev/null and b/lab-raziyeh/db/diagnostic.data/metrics.2016-10-29T05-14-51Z-00000 differ
diff --git a/lab-raziyeh/db/diagnostic.data/metrics.2016-10-31T20-19-37Z-00000 b/lab-raziyeh/db/diagnostic.data/metrics.2016-10-31T20-19-37Z-00000
new file mode 100644
index 0000000..c154467
Binary files /dev/null and b/lab-raziyeh/db/diagnostic.data/metrics.2016-10-31T20-19-37Z-00000 differ
diff --git a/lab-raziyeh/db/diagnostic.data/metrics.2016-10-31T20-51-24Z-00000 b/lab-raziyeh/db/diagnostic.data/metrics.2016-10-31T20-51-24Z-00000
new file mode 100644
index 0000000..859a203
Binary files /dev/null and b/lab-raziyeh/db/diagnostic.data/metrics.2016-10-31T20-51-24Z-00000 differ
diff --git a/lab-raziyeh/db/diagnostic.data/metrics.2016-10-31T21-10-31Z-00000 b/lab-raziyeh/db/diagnostic.data/metrics.2016-10-31T21-10-31Z-00000
new file mode 100644
index 0000000..6a34f5a
Binary files /dev/null and b/lab-raziyeh/db/diagnostic.data/metrics.2016-10-31T21-10-31Z-00000 differ
diff --git a/lab-raziyeh/db/diagnostic.data/metrics.2016-11-06T08-48-31Z-00000 b/lab-raziyeh/db/diagnostic.data/metrics.2016-11-06T08-48-31Z-00000
new file mode 100644
index 0000000..6385f75
Binary files /dev/null and b/lab-raziyeh/db/diagnostic.data/metrics.2016-11-06T08-48-31Z-00000 differ
diff --git a/lab-raziyeh/db/diagnostic.data/metrics.2016-11-07T22-34-23Z-00000 b/lab-raziyeh/db/diagnostic.data/metrics.2016-11-07T22-34-23Z-00000
new file mode 100644
index 0000000..eb29472
Binary files /dev/null and b/lab-raziyeh/db/diagnostic.data/metrics.2016-11-07T22-34-23Z-00000 differ
diff --git a/lab-raziyeh/db/diagnostic.data/metrics.interim b/lab-raziyeh/db/diagnostic.data/metrics.interim
new file mode 100644
index 0000000..c099bef
Binary files /dev/null and b/lab-raziyeh/db/diagnostic.data/metrics.interim differ
diff --git a/lab-raziyeh/db/index-1--3143307283938210215.wt b/lab-raziyeh/db/index-1--3143307283938210215.wt
new file mode 100644
index 0000000..f4b90fe
Binary files /dev/null and b/lab-raziyeh/db/index-1--3143307283938210215.wt differ
diff --git a/lab-raziyeh/db/index-1-6564260200608951873.wt b/lab-raziyeh/db/index-1-6564260200608951873.wt
new file mode 100644
index 0000000..3f019cb
Binary files /dev/null and b/lab-raziyeh/db/index-1-6564260200608951873.wt differ
diff --git a/lab-raziyeh/db/index-1-8845950676657731124.wt b/lab-raziyeh/db/index-1-8845950676657731124.wt
new file mode 100644
index 0000000..dcac98c
Binary files /dev/null and b/lab-raziyeh/db/index-1-8845950676657731124.wt differ
diff --git a/lab-raziyeh/db/index-10-6564260200608951873.wt b/lab-raziyeh/db/index-10-6564260200608951873.wt
new file mode 100644
index 0000000..3f019cb
Binary files /dev/null and b/lab-raziyeh/db/index-10-6564260200608951873.wt differ
diff --git a/lab-raziyeh/db/index-10-8845950676657731124.wt b/lab-raziyeh/db/index-10-8845950676657731124.wt
new file mode 100644
index 0000000..56c5e08
Binary files /dev/null and b/lab-raziyeh/db/index-10-8845950676657731124.wt differ
diff --git a/lab-raziyeh/db/index-2-6564260200608951873.wt b/lab-raziyeh/db/index-2-6564260200608951873.wt
new file mode 100644
index 0000000..3f019cb
Binary files /dev/null and b/lab-raziyeh/db/index-2-6564260200608951873.wt differ
diff --git a/lab-raziyeh/db/index-2-8845950676657731124.wt b/lab-raziyeh/db/index-2-8845950676657731124.wt
new file mode 100644
index 0000000..c14343c
Binary files /dev/null and b/lab-raziyeh/db/index-2-8845950676657731124.wt differ
diff --git a/lab-raziyeh/db/index-4-6564260200608951873.wt b/lab-raziyeh/db/index-4-6564260200608951873.wt
new file mode 100644
index 0000000..3f019cb
Binary files /dev/null and b/lab-raziyeh/db/index-4-6564260200608951873.wt differ
diff --git a/lab-raziyeh/db/index-4-8845950676657731124.wt b/lab-raziyeh/db/index-4-8845950676657731124.wt
new file mode 100644
index 0000000..1de98ca
Binary files /dev/null and b/lab-raziyeh/db/index-4-8845950676657731124.wt differ
diff --git a/lab-raziyeh/db/index-5-6564260200608951873.wt b/lab-raziyeh/db/index-5-6564260200608951873.wt
new file mode 100644
index 0000000..3f019cb
Binary files /dev/null and b/lab-raziyeh/db/index-5-6564260200608951873.wt differ
diff --git a/lab-raziyeh/db/index-5-8845950676657731124.wt b/lab-raziyeh/db/index-5-8845950676657731124.wt
new file mode 100644
index 0000000..8ccbd51
Binary files /dev/null and b/lab-raziyeh/db/index-5-8845950676657731124.wt differ
diff --git a/lab-raziyeh/db/index-6-6564260200608951873.wt b/lab-raziyeh/db/index-6-6564260200608951873.wt
new file mode 100644
index 0000000..3f019cb
Binary files /dev/null and b/lab-raziyeh/db/index-6-6564260200608951873.wt differ
diff --git a/lab-raziyeh/db/index-6-8845950676657731124.wt b/lab-raziyeh/db/index-6-8845950676657731124.wt
new file mode 100644
index 0000000..d9dd9f3
Binary files /dev/null and b/lab-raziyeh/db/index-6-8845950676657731124.wt differ
diff --git a/lab-raziyeh/db/index-7-6564260200608951873.wt b/lab-raziyeh/db/index-7-6564260200608951873.wt
new file mode 100644
index 0000000..3f019cb
Binary files /dev/null and b/lab-raziyeh/db/index-7-6564260200608951873.wt differ
diff --git a/lab-raziyeh/db/index-7-8845950676657731124.wt b/lab-raziyeh/db/index-7-8845950676657731124.wt
new file mode 100644
index 0000000..d8535e2
Binary files /dev/null and b/lab-raziyeh/db/index-7-8845950676657731124.wt differ
diff --git a/lab-raziyeh/db/index-8-6564260200608951873.wt b/lab-raziyeh/db/index-8-6564260200608951873.wt
new file mode 100644
index 0000000..3f019cb
Binary files /dev/null and b/lab-raziyeh/db/index-8-6564260200608951873.wt differ
diff --git a/lab-raziyeh/db/index-8-8845950676657731124.wt b/lab-raziyeh/db/index-8-8845950676657731124.wt
new file mode 100644
index 0000000..8c0a536
Binary files /dev/null and b/lab-raziyeh/db/index-8-8845950676657731124.wt differ
diff --git a/lab-raziyeh/db/journal/WiredTigerLog.0000000008 b/lab-raziyeh/db/journal/WiredTigerLog.0000000008
new file mode 100644
index 0000000..58f776e
Binary files /dev/null and b/lab-raziyeh/db/journal/WiredTigerLog.0000000008 differ
diff --git a/lab-raziyeh/db/journal/WiredTigerPreplog.0000000001 b/lab-raziyeh/db/journal/WiredTigerPreplog.0000000001
new file mode 100644
index 0000000..170ccc6
Binary files /dev/null and b/lab-raziyeh/db/journal/WiredTigerPreplog.0000000001 differ
diff --git a/lab-raziyeh/db/journal/WiredTigerPreplog.0000000002 b/lab-raziyeh/db/journal/WiredTigerPreplog.0000000002
new file mode 100644
index 0000000..170ccc6
Binary files /dev/null and b/lab-raziyeh/db/journal/WiredTigerPreplog.0000000002 differ
diff --git a/lab-raziyeh/db/mongod.lock b/lab-raziyeh/db/mongod.lock
new file mode 100644
index 0000000..ba3de0c
--- /dev/null
+++ b/lab-raziyeh/db/mongod.lock
@@ -0,0 +1 @@
+61194
diff --git a/lab-raziyeh/db/sizeStorer.wt b/lab-raziyeh/db/sizeStorer.wt
new file mode 100644
index 0000000..a647722
Binary files /dev/null and b/lab-raziyeh/db/sizeStorer.wt differ
diff --git a/lab-raziyeh/db/storage.bson b/lab-raziyeh/db/storage.bson
new file mode 100644
index 0000000..d07b1bc
Binary files /dev/null and b/lab-raziyeh/db/storage.bson differ
diff --git a/lab-raziyeh/karma.conf.js b/lab-raziyeh/karma.conf.js
new file mode 100644
index 0000000..8827f97
--- /dev/null
+++ b/lab-raziyeh/karma.conf.js
@@ -0,0 +1,31 @@
+// Karma configuration
+// Generated on Wed Oct 19 2016 09:49:07 GMT-0700 (PDT)
+
+
+const webpack= require('./webpack.config.js');
+webpack.entry = {};
+
+module.exports = function(config) {
+ config.set({
+ webpack,
+ port: 9876,
+ colors: true,
+ basePath: '',
+ autoWatch: true,
+ singleRun: false,
+ concurrency: Infinity,
+ frameworks: ['jasmine'],
+ reporters: ['progress'],
+ browsers: ['PhantomJS'],
+ logLevel: config.LOG_INFO,
+ preprocessors: {
+ 'client-test/**/*-test.js': ['webpack'],
+ 'app/entry.js': ['webpack'],
+ },
+ files: [
+ 'app/entry.js',
+ 'client-test/**/*-test.js',
+ 'node_modules/angular-mocks/angular-mocks.js',
+ ],
+ });
+};
diff --git a/lab-raziyeh/lib/basic-auth-middleware.js b/lab-raziyeh/lib/basic-auth-middleware.js
new file mode 100644
index 0000000..7feb9e8
--- /dev/null
+++ b/lab-raziyeh/lib/basic-auth-middleware.js
@@ -0,0 +1,27 @@
+'use strict'
+
+const createError = require('http-errors')
+const debug = require('debug')('slugram:basic-auth-middleware')
+
+module.exports = function(req, res, next){
+ debug()
+
+ var authHeader = req.headers.authorization
+ if (!authHeader) return next(createError(401, 'requires authorization header'))
+
+ let base64String = authHeader.split('Basic ')[1]
+ if (!base64String) return next(createError(401, 'require username and password'))
+
+ let utf8String = new Buffer(base64String, 'base64').toString()
+ let authArray = utf8String.split(':')
+ req.auth = {
+ username: authArray[0],
+ password: authArray[1],
+ }
+
+ if(!req.auth.username) return next(createError(401, 'basic auth requires username'))
+ if(!req.auth.password) return next(createError(401, 'basic auth requires password'))
+ next()
+}
+
+
diff --git a/lab-raziyeh/lib/bearer-auth-middleware.js b/lab-raziyeh/lib/bearer-auth-middleware.js
new file mode 100644
index 0000000..d5dc431
--- /dev/null
+++ b/lab-raziyeh/lib/bearer-auth-middleware.js
@@ -0,0 +1,31 @@
+'use strict'
+
+// npm modules
+const jwt = require('jsonwebtoken')
+const createError = require('http-errors')
+const debug = require('debug')('slugram:bearer-middleware')
+
+// app modules
+const User = require('../model/user.js')
+
+module.exports = function(req, res, next){
+ debug()
+ let authHeader = req.headers.authorization
+ if (!authHeader)
+ return next(createError(400, 'requires auth header'))
+
+ let token = authHeader.split('Bearer ')[1]
+ if (!token)
+ return next(createError(400, 'requires token'))
+
+ jwt.verify(token, process.env.APP_SECRET, (err, decoded) => {
+ if (err)
+ return next(createError(401, 'requires token'))
+ User.findOne({findHash: decoded.token})
+ .then( user => {
+ if (!user) return next(createError(401, 'user no longer exists or has new token'))
+ req.user = user
+ next()
+ })
+ })
+}
diff --git a/lab-raziyeh/lib/error-middleware.js b/lab-raziyeh/lib/error-middleware.js
new file mode 100644
index 0000000..97bcccd
--- /dev/null
+++ b/lab-raziyeh/lib/error-middleware.js
@@ -0,0 +1,34 @@
+'use strict'
+
+const createError = require('http-errors')
+const debug = require('debug')('slugram:error-middleware')
+
+module.exports = function(err, req, res, next){
+ debug('error middleware')
+ debug('ERROR:', err.message)
+
+ if (err.status){
+ res.status(err.status).send(err.name)
+ next()
+ return
+ }
+
+ if (err.name === 'ValidationError'){
+ err = createError(400, err.message)
+ res.status(err.status).send(err.name)
+ next()
+ return
+ }
+
+ if (err.name === 'MongoError' && err.message.startsWith('E11000 duplicate')){
+ // 409 means conflict im using it for duplicate errors
+ err = createError(409, err.message)
+ res.status(err.status).send(err.name)
+ next()
+ return
+ }
+
+ err = createError(500, err.message)
+ res.status(err.status).send(err.name)
+ next()
+}
diff --git a/lab-raziyeh/lib/fuzzy-query.js b/lab-raziyeh/lib/fuzzy-query.js
new file mode 100644
index 0000000..942ad81
--- /dev/null
+++ b/lab-raziyeh/lib/fuzzy-query.js
@@ -0,0 +1,19 @@
+'use strict'
+
+let debug = require('debug')('slugram:fuzzy-query')
+
+let fuzzyRegex = require('./fuzzy-regex.js')
+
+module.exports = function(fieldnames, reqQuerys){
+ debug('generating fuzzy queries')
+ if (!Array.isArray(fieldnames))
+ return {}
+ if (typeof reqQuerys !== 'object')
+ return {}
+ let query = {}
+ fieldnames.forEach(val => {
+ if (reqQuerys[val])
+ query[val] = {$regex: fuzzyRegex(reqQuerys[val])}
+ })
+ return query
+}
diff --git a/lab-raziyeh/lib/fuzzy-regex.js b/lab-raziyeh/lib/fuzzy-regex.js
new file mode 100644
index 0000000..ea66e0b
--- /dev/null
+++ b/lab-raziyeh/lib/fuzzy-regex.js
@@ -0,0 +1,11 @@
+'use strict'
+
+const debug = require('debug')('slugram:fuzzy-regex')
+
+module.exports = function(input){
+ debug(`creating fuzzy regex from ${input}`)
+ if (!input || typeof input !== 'string')
+ return new RegExp('.*')
+ let result = input.split('').join('.*')
+ return new RegExp(`.*${result}.*`)
+}
diff --git a/lab-raziyeh/lib/google-oauth-middleware.js b/lab-raziyeh/lib/google-oauth-middleware.js
new file mode 100644
index 0000000..7fedc9c
--- /dev/null
+++ b/lab-raziyeh/lib/google-oauth-middleware.js
@@ -0,0 +1,46 @@
+'use strict';
+
+const request = require('superagent');
+const debug = require('debug')('slugram:google-oauth-middleware');
+
+module.exports = function(req, res, next){
+ debug('getting google user info');
+ if (req.query.error){
+ req.googleError = new Error(req.query.error);
+ return next();
+ }
+
+ let data = {
+ code: req.query.code,
+ client_id: process.env.GOOGLE_CLIENT_ID,
+ client_secret: process.env.GOOGLE_CLIENT_SECRET,
+ redirect_uri: `${process.env.API_URL}/api/oauth/signout-api`,
+ grant_type: 'authorization_code',
+ };
+
+ let accessToken, refreshToken, tokenTTL;
+ request.post('https://www.googleapis.com/oauth2/v4/token')
+ .type('form')
+ .send(data)
+ .then(response => {
+ accessToken = response.body.access_token;
+ refreshToken = response.body.refresh_token;
+ tokenTTL = response.body.expires_in; // how long the accessToken token will work in seconds
+ return request.get('https://www.googleapis.com/plus/v1/people/me/openIdConnect')
+ .set('Authorization', `Bearer ${response.body.access_token}`)
+ })
+ .then( response => {
+ req.googleOAUTH = {
+ googleID: response.body.sub,
+ email: response.body.email,
+ accessToken,
+ refreshToken,
+ tokenTTL,
+ };
+ next();
+ })
+ .catch((err) => {
+ req.googleError = err;
+ next();
+ })
+};
\ No newline at end of file
diff --git a/lab-raziyeh/lib/item-query-middleware.js b/lab-raziyeh/lib/item-query-middleware.js
new file mode 100644
index 0000000..2fa0631
--- /dev/null
+++ b/lab-raziyeh/lib/item-query-middleware.js
@@ -0,0 +1,30 @@
+'use strict'
+
+const debug = require('debug')('slugram:query-middleware')
+
+module.exports = function(req, res, next){
+ debug('creating itempage queries')
+ let itempage = 1
+ let itemoffset = 0
+ let itemcount = 100
+ let itemsort = 1
+ if (!isNaN(Number(req.query.itempage))) itempage = Number(req.query.itempage)
+ if (!isNaN(Number(req.query.itemoffset))) itemoffset = Number(req.query.itemoffset)
+ if (!isNaN(Number(req.query.itemcount))) itemcount = Number(req.query.itemcount)
+ if (req.query.itemsort === 'dsc') itemsort = -1
+ if (req.query.itemsort === 'asc') itemsort = 1
+
+ if (itempage < 1) itempage = 1
+ if (itemoffset < 0) itemoffset = 0
+ if (itemcount < 1) itemcount = 1
+ if (itemcount > 250) itemcount = 250
+
+ if (itempage > 1)
+ itemoffset = (itempage - 1) * itemcount + itemoffset
+
+ req.query.itempage = itempage
+ req.query.itemoffset = itemoffset
+ req.query.itemcount = itemcount
+ req.query.itemsort = itemsort
+ next()
+}
diff --git a/lab-raziyeh/lib/page-query-middleware.js b/lab-raziyeh/lib/page-query-middleware.js
new file mode 100644
index 0000000..ac9d8c0
--- /dev/null
+++ b/lab-raziyeh/lib/page-query-middleware.js
@@ -0,0 +1,30 @@
+'use strict'
+
+const debug = require('debug')('slugram:query-middleware')
+
+module.exports = function(req, res, next){
+ debug('creating page queries')
+ let page = 1
+ let offset = 0
+ let pagesize = 50
+ let sort = 1
+ if (!isNaN(Number(req.query.page))) page = Number(req.query.page)
+ if (!isNaN(Number(req.query.offset))) offset = Number(req.query.offset)
+ if (!isNaN(Number(req.query.pagesize))) pagesize = Number(req.query.pagesize)
+ if (req.query.sort === 'dsc') sort = -1
+ if (req.query.sort === 'asc') sort = 1
+
+ if (page < 1) page = 1
+ if (offset < 0) offset = 0
+ if (pagesize < 1) pagesize = 1
+ if (pagesize > 250) pagesize = 250
+
+ if (page > 1)
+ offset = (page - 1) * pagesize + offset
+
+ req.query.page = page
+ req.query.offset = offset
+ req.query.pagesize = pagesize
+ req.query.sort = sort
+ next()
+}
diff --git a/lab-raziyeh/lib/s3-upload-promise.js b/lab-raziyeh/lib/s3-upload-promise.js
new file mode 100644
index 0000000..2d3fa80
--- /dev/null
+++ b/lab-raziyeh/lib/s3-upload-promise.js
@@ -0,0 +1,17 @@
+'use strict'
+
+const AWS = require('aws-sdk')
+const debug = require('debug')('slugram:s3-upload-promise')
+
+const s3 = new AWS.S3()
+
+module.exports = function s3UploadPromise(params){
+ debug('uploading file to s3')
+ return new Promise((resolve, reject) => {
+ s3.upload(params, (err, s3data) => {
+ if (err)
+ return reject(err)
+ resolve(s3data)
+ })
+ })
+}
diff --git a/lab-raziyeh/model/gallery.js b/lab-raziyeh/model/gallery.js
new file mode 100644
index 0000000..7c311cd
--- /dev/null
+++ b/lab-raziyeh/model/gallery.js
@@ -0,0 +1,14 @@
+'use strict'
+
+const mongoose = require('mongoose')
+
+const gallerySchema = mongoose.Schema({
+ name: {type: String, required: true},
+ desc: {type: String, required: true},
+ username: {type: String, required: true},
+ created: {type: Date, required: true, default: Date.now},
+ userID: {type: mongoose.Schema.Types.ObjectId, required: true},
+ pics: [{type: mongoose.Schema.Types.ObjectId, ref: 'pic'}],
+})
+
+module.exports = mongoose.model('gallery', gallerySchema)
diff --git a/lab-raziyeh/model/pic.js b/lab-raziyeh/model/pic.js
new file mode 100644
index 0000000..3e61cb4
--- /dev/null
+++ b/lab-raziyeh/model/pic.js
@@ -0,0 +1,20 @@
+'use strict'
+
+const mongoose = require('mongoose')
+
+const picSchema = mongoose.Schema({
+ name: {type: String, required: true},
+ desc: {type: String, required: true},
+ username: {type: String, required: true},
+ userID: {type: mongoose.Schema.Types.ObjectId, required: true},
+ imageURI: {type: String, required: true, unique: true},
+ objectKey: {type: String, required: true, unique: true},
+ created: {type: Date, default: Date.now},
+})
+
+module.exports = mongoose.model('pic', picSchema)
+
+//Pic.schema.path('name').validate(function(val) {
+ //return /(slug|byte)/.test(val)
+//})
+
diff --git a/lab-raziyeh/model/user.js b/lab-raziyeh/model/user.js
new file mode 100644
index 0000000..a1a634b
--- /dev/null
+++ b/lab-raziyeh/model/user.js
@@ -0,0 +1,78 @@
+'use strict'
+
+const crypto = require('crypto')
+const bcrypt = require('bcrypt')
+const jwt = require('jsonwebtoken')
+const Promise = require('bluebird')
+const mongoose = require('mongoose')
+const createError = require('http-errors')
+const debug = require('debug')('slugram:user')
+
+// mondule constant
+const Schema = mongoose.Schema
+
+const userSchema = Schema({
+ username: {type: String, required: true, unique: true, minlength: 5},
+ email: {type: String, required: true, unique: true},
+ password: {type: String, required: true},
+ findHash: {type: String, unique: true},
+})
+
+// for signup
+// store a password that has been encrypted as a hash
+userSchema.methods.generatePasswordHash = function(password){
+ debug('generatePasswordHash')
+ return new Promise((resolve, reject) => {
+ bcrypt.hash(password, 10, (err, hash) => {
+ if (err) return reject(err) // 500 error
+ this.password = hash
+ resolve(this)
+ })
+ })
+}
+
+// for signin
+// compare a plain text password with the stored hashed password
+userSchema.methods.comparePasswordHash = function(password){
+ debug('comparePasswordHash')
+ return new Promise((resolve, reject) => {
+ bcrypt.compare(password, this.password, (err, valid) => {
+ if (err) return reject(err) // 500 error bcrypt failed
+ if (!valid) return reject(createError(401, 'wrong password'))
+ resolve(this)
+ })
+ })
+}
+
+// for signup
+userSchema.methods.generateFindHash = function(){
+ debug('generateFindHash')
+ return new Promise((resolve, reject) => {
+ let tries = 0
+ _generateFindHash.call(this)
+
+ function _generateFindHash(){
+ this.findHash = crypto.randomBytes(32).toString('hex')
+ this.save()
+ .then(() => resolve(this.findHash))
+ .catch(err => {
+ if (tries > 3) return reject(err) // 500 error
+ tries++
+ _generateFindHash.call(this)
+ })
+ }
+ })
+}
+
+// for sinup and signin
+userSchema.methods.generateToken = function(){
+ debug('generateToken')
+ return new Promise((resolve, reject) => {
+ this.generateFindHash()
+ .then(findHash => resolve(jwt.sign({token: findHash}, process.env.APP_SECRET)))
+ .catch(err => reject(err)) // 500 error from find hash
+ })
+}
+
+module.exports = mongoose.model('user', userSchema)
+// static methods
diff --git a/lab-raziyeh/package.json b/lab-raziyeh/package.json
new file mode 100644
index 0000000..49de439
--- /dev/null
+++ b/lab-raziyeh/package.json
@@ -0,0 +1,86 @@
+{
+ "name": "basic-auth-demo",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "engines": {
+ "node": "4.4.7"
+ },
+ "scripts": {
+ "build": "./node_modules/webpack/bin/webpack.js",
+ "build-watch": "./node_modules/webpack/bin/webpack.js -w",
+ "frontend-test": "./node_modules/karma/bin/karma start --single-run",
+ "test-watch": "./node_modules/karma/bin/karma start",
+ "start": "node server.js",
+ "start-debug": "DEBUG='slug*' npm start",
+ "start-watch": "DEBUG='slug*' ./node_modules/nodemon/bin/nodemon.js server.js",
+ "test": "./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha",
+ "coveralls": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls",
+ "lint": "./node_modules/eslint/bin/eslint.js .",
+ "test-debug": "DEBUG='slug*' ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "aws-sdk": "^2.6.6",
+ "bcrypt": "^0.8.7",
+ "bluebird": "^3.4.6",
+ "body-parser": "^1.15.2",
+ "cors": "^2.8.1",
+ "debug": "^2.2.0",
+ "del": "^2.2.2",
+ "dotenv": "^2.0.0",
+ "express": "^4.14.0",
+ "http-errors": "^1.5.0",
+ "jsonwebtoken": "^7.1.9",
+ "mongoose": "^4.6.2",
+ "morgan": "^1.7.0",
+ "multer": "^1.2.0",
+ "angular": "^1.5.8",
+ "angular-animate": "^1.5.8",
+ "angular-route": "^1.5.8",
+ "angular-touch": "^1.5.8",
+ "angular-ui-bootstrap": "^2.2.0",
+ "angular-ui-router": "^0.3.1",
+ "babel-core": "^6.17.0",
+ "babel-loader": "^6.2.5",
+ "babel-preset-es2015": "^6.16.0",
+ "bootstrap-sass": "^3.3.7",
+ "camelcase": "^3.0.0",
+ "clean-webpack-plugin": "^0.1.13",
+ "css-loader": "^0.25.0",
+ "extract-text-webpack-plugin": "^1.0.1",
+ "file-loader": "^0.9.0",
+ "font-awesome": "^4.6.3",
+ "html-loader": "^0.4.4",
+ "html-webpack-plugin": "^2.22.0",
+ "ng-file-upload": "^12.2.12",
+ "node-sass": "^3.10.1",
+ "pascalcase": "^0.1.1",
+ "resolve-url-loader": "^1.6.0",
+ "sass-loader": "^4.0.2",
+ "style-loader": "^0.13.1",
+ "ui-router": "^1.0.0-alpha.3",
+ "url-loader": "^0.5.7",
+ "webpack": "^1.13.2"
+ },
+ "devDependencies": {
+ "angular-mocks": "^1.5.8",
+ "aws-sdk-mock": "^1.5.0",
+ "chai": "^3.5.0",
+ "coveralls": "^2.11.14",
+ "eslint": "^3.8.1",
+ "istanbul": "^0.4.5",
+ "jasmine-core": "^2.5.2",
+ "karma": "^1.3.0",
+ "karma-jasmine": "^1.0.2",
+ "karma-phantomjs-launcher": "^1.0.2",
+ "karma-webpack": "^1.8.0",
+ "lorem-ipsum": "^1.0.3",
+ "mocha": "^3.1.0",
+ "nodemon": "^1.11.0",
+ "superagent": "^2.3.0",
+ "webpack-dev-server": "^1.16.2"
+ }
+}
diff --git a/lab-raziyeh/route/auth-router.js b/lab-raziyeh/route/auth-router.js
new file mode 100644
index 0000000..ebe1550
--- /dev/null
+++ b/lab-raziyeh/route/auth-router.js
@@ -0,0 +1,81 @@
+'use strict'
+
+const Router = require('express').Router
+const createError = require('http-errors')
+const jsonParser = require('body-parser').json()
+const debug = require('debug')('slugram:auth-router')
+const basicAuth = require('../lib/basic-auth-middleware.js')
+const googleOAUTH = require('../lib/google-oauth-middleware.js');
+const User = require('../model/user.js')
+
+// module constants
+const authRouter = module.exports = Router()
+
+authRouter.post('/api/signup', jsonParser, function(req, res, next){
+ debug('POST /api/signup')
+
+ let password = req.body.password
+ delete req.body.password
+ let user = new User(req.body)
+
+ // checkfor password before running generatePasswordHash
+ if (!password)
+ return next(createError(400, 'requires password'))
+ if (password.length < 8)
+ return next(createError(400, 'password must be 8 characters'))
+
+ user.generatePasswordHash(password)
+ .then( user => user.save()) // check for unique username with mongoose unique
+ .then( user => user.generateToken())
+ .then( token => res.send(token))
+ .catch(next)
+})
+
+authRouter.get('/api/login', basicAuth, function(req, res, next){
+ debug('GET /api/login')
+
+ User.findOne({username: req.auth.username})
+ .then( user => user.comparePasswordHash(req.auth.password))
+ .catch(err => Promise.reject(createError(401, err.message)))
+ .then( user => user.generateToken())
+ .then( token => res.send(token))
+ .catch(next)
+})
+
+authRouter.get('/api/oauth/signout-api',googleOAUTH, function(req, res) {
+ debug('GET /api/oauth/signout-api');
+ if(req.googleError) {
+ return res.redirect('/#/login');
+ }
+
+ //check if user already exists
+ User.findOne({email: req.googleOAUTH.email})
+ .then(user => {
+ if(!user) return Promise.reject(new Error('user not found'));
+ return user;
+ })
+ .catch( err => {
+ if(err.message === 'user not found') {
+ let userData = {
+ username: req.googleOAUTH.email,
+ email: req.googleOAUTH.email,
+ google :{
+ googleID: req.googleOAUTH.googleID,
+ tokenTTL: req.googleOAUTH.tokenTTL,
+ tokenTimestamp: Date.now(),
+ refreshToken: req.googleOAUTH.refreshToken,
+ accessToken: req.googleOAUTH.accessToken,
+ },
+ }
+ return new User(userData).save();
+ }
+ return Promise.reject(err);
+ })
+ .then(user => user.generateToken())
+ .then(token => {
+ res.redirect(`/#/login/?token=${token}`);
+ })
+ .catch(() => {
+ res.redirect('/#/login');
+ })
+});
\ No newline at end of file
diff --git a/lab-raziyeh/route/gallery-router.js b/lab-raziyeh/route/gallery-router.js
new file mode 100644
index 0000000..20ed222
--- /dev/null
+++ b/lab-raziyeh/route/gallery-router.js
@@ -0,0 +1,131 @@
+'use strict'
+
+// npm
+const AWS = require('aws-sdk')
+const Router = require('express').Router
+const jsonParser = require('body-parser').json()
+const createError = require('http-errors')
+const debug = require('debug')('slugram:gallery-route')
+
+// app
+const Pic = require('../model/pic.js')
+const Gallery = require('../model/gallery.js')
+const bearerAuth = require('../lib/bearer-auth-middleware.js')
+const pageQueries = require('../lib/page-query-middleware.js')
+const itemQueries = require('../lib/item-query-middleware.js')
+const fuzzyQuery = require('../lib/fuzzy-query.js')
+
+// constants
+const s3 = new AWS.S3()
+const galleryRouter = module.exports = Router()
+
+galleryRouter.post('/api/gallery', bearerAuth, jsonParser, function(req, res, next){
+ debug('POST /api/gallery')
+ req.body.userID = req.user._id
+ req.body.username = req.user.username
+ new Gallery(req.body).save()
+ .then( gallery => res.json(gallery))
+ .catch(next)
+})
+
+
+galleryRouter.get('/api/gallery/:id', bearerAuth, itemQueries, function(req, res, next){
+ debug('GET /api/gallery/:id')
+ Gallery.findById(req.params.id)
+ .populate({
+ path: 'pics',
+ options: {
+ sort: {_id: req.query.itemsort},
+ limit: req.query.itemcount,
+ skip: req.query.itemoffset,
+ },
+ })
+ .catch(err => Promise.reject(createError(400, err.message)))
+ .then(gallery => {
+ if (gallery.userID.toString() !== req.user._id.toString())
+ return Promise.reject(createError(401, 'invalid userid'))
+ res.json(gallery)
+ })
+ .catch(next)
+})
+
+galleryRouter.put('/api/gallery/:id', bearerAuth, jsonParser, function(req, res, next){
+ debug('PUT /api/gallery/:id')
+ Gallery.findById(req.params.id)
+ .catch(err => Promise.reject(createError(404, err.message)))
+ .then(gallery => {
+ if (gallery.userID.toString() !== req.user._id.toString())
+ return Promise.reject(createError(401, 'not users gallery'))
+ let options = { runValidators: true, new: true}
+ return Gallery.findByIdAndUpdate(req.params.id, req.body, options)
+ })
+ .then(gallery => res.json(gallery))
+ .catch(next)
+})
+
+galleryRouter.delete('/api/gallery/:id', bearerAuth, function(req, res, next){
+ debug('DELETE /api/gallery/:id')
+ let tempGallrey = null
+ Gallery.findById(req.params.id)
+ .populate('pics')
+ .catch(err => Promise.reject(createError(404, err.message)))
+ .then(gallery => {
+ tempGallrey = gallery
+ if (gallery.userID.toString() !== req.user._id.toString())
+ return Promise.reject(createError(401, 'not users gallery'))
+ let deletePhotos = []
+
+ gallery.pics.forEach(pic => {
+ let params = {
+ Bucket: process.env.AWS_BUCKET,
+ Key: pic.objectKey,
+ }
+ deletePhotos.push(Pic.findByIdAndRemove(pic._id))
+ deletePhotos.push(s3.deleteObject(params).promise())
+ })
+
+ return Promise.all(deletePhotos)
+ })
+ .then(() => tempGallrey.remove())
+ .then(() => res.sendStatus(204))
+ .catch(next)
+})
+
+galleryRouter.get('/api/gallery', bearerAuth, pageQueries, itemQueries, function(req, res, next){
+ debug('GET /api/gallery')
+
+ let fields = ['name', 'desc']
+ let query = fuzzyQuery(fields, req.query)
+ query.userID = req.user._id.toString()
+ Gallery.find(query)
+ .populate({
+ path: 'pics',
+ options: {
+ sort: {_id: req.query.itemsort},
+ limit: req.query.itemcount,
+ skip: req.query.itemoffset,
+ },
+ })
+ .sort({_id: req.query.sort}).skip(req.query.offset).limit(req.query.pagesize)
+ .then(galleries => res.json(galleries))
+ .catch(next)
+})
+
+// public anyone can call
+galleryRouter.get('/api/public/gallery', pageQueries, itemQueries, function(req, res, next){
+ let fields = ['username', 'name', 'desc']
+ let query = fuzzyQuery(fields, req.query)
+ console.log('req.query.itemcount', req.query.itemcount)
+ Gallery.find(query)
+ .populate({
+ path: 'pics',
+ options: {
+ sort: {_id: req.query.itemsort},
+ limit: req.query.itemcount,
+ skip: req.query.itemoffset,
+ },
+ })
+ .sort({_id: req.query.sort}).skip(req.query.offset).limit(req.query.pagesize)
+ .then(galleries => res.json(galleries))
+ .catch(next)
+})
diff --git a/lab-raziyeh/route/pic-router.js b/lab-raziyeh/route/pic-router.js
new file mode 100644
index 0000000..d725d62
--- /dev/null
+++ b/lab-raziyeh/route/pic-router.js
@@ -0,0 +1,146 @@
+'use strict'
+
+// node module
+const fs = require('fs')
+const path = require('path')
+
+// npm module
+const del = require('del')
+const AWS = require('aws-sdk')
+const multer = require('multer')
+const createError = require('http-errors')
+const debug = require('debug')('sulgram:pic-router')
+
+// app module
+const Pic = require('../model/pic.js')
+const Gallery = require('../model/gallery.js')
+const fuzzyQuery = require('../lib/fuzzy-query.js')
+const bearerAuth = require('../lib/bearer-auth-middleware.js')
+const pageQuery = require('../lib/page-query-middleware.js')
+
+// Use bluebird implementation of Promise
+// will add a .promise() to AWS.Request
+AWS.config.setPromisesDependency(require('bluebird'))
+
+// module constants
+const s3 = new AWS.S3()
+const dataDir =`${__dirname}/../data`
+const upload = multer({dest: dataDir })
+const s3UploadPromise = require('../lib/s3-upload-promise.js')
+const picRouter = module.exports = require('express').Router()
+
+
+picRouter.post('/api/gallery/:galleryID/pic', bearerAuth, upload.single('file'), function(req, res, next){
+ debug('POST /api/gallery/:galleryID/pic')
+ if(!req.file)
+ return next(createError(400, 'no file found'))
+
+ let ext = path.extname(req.file.originalname) // '.png' | '.gif' | '.tar.gz'
+
+ let params = {
+ ACL: 'public-read',
+ Bucket: process.env.AWS_BUCKET,
+ Key: `${req.file.filename}${ext}`,
+ Body: fs.createReadStream(req.file.path),
+ }
+
+ // first check that the gallery exists
+ // then upload image
+ // remove the image that multer stored on the local disk
+ // then store monogo Pic
+ // then respond to user
+
+ let tempGallery, tempPic
+ Gallery.findById(req.params.galleryID)
+ .catch(err => Promise.reject(createError(404, err.message)))
+ .then(gallery => {
+ tempGallery = gallery
+ return s3UploadPromise(params)// IF FAILS 500 ERROR
+ })
+ .catch(err => err.status ? Promise.reject(err) : Promise.reject(createError(500, err.message)))
+ .then(s3data => {
+ del([`${dataDir}/*`])
+ let picData = {
+ name: req.body.name,
+ username: req.user.username,
+ desc: req.body.desc,
+ objectKey: s3data.Key,
+ imageURI: s3data.Location,
+ userID: req.user._id,
+ }
+ return new Pic(picData).save()
+ })
+ .then(pic => {
+ tempPic = pic
+ tempGallery.pics.push(pic._id)
+ return tempGallery.save()
+ })
+ .then(() => res.json(tempPic))
+ .catch(err => {
+ del([`${dataDir}/*`])
+ next(err)
+ })
+})
+
+picRouter.delete('/api/gallery/:galleryID/pic/:picID', bearerAuth, function(req, res, next){
+ debug('DELETE /api/gallery/:galleryID/pic/:picID')
+
+
+ //check that the pic exists if not 404
+ //make sure there userID matches the pic.userID if not 401
+ //check that gallery id is correct if not 404
+ //remove the picID from the gallery
+ //delete the picture from aws
+ //delete the pic from mongo
+ //respond to the client
+ let tempPic
+ Pic.findById(req.params.picID) // 404
+ .then( pic => {
+ if(pic.userID.toString() !== req.user._id.toString())
+ return Promise.reject(createError(401, 'user not authtorized to delete this pic'))
+ tempPic = pic
+ return Gallery.findById(req.params.galleryID) // 404
+ })
+ .catch(err => err.status? Promise.reject(err) : Promise.reject(createError(404, err.message))) // if no pic or gal found
+ .then( gallery => {
+ gallery.pics = gallery.pics.filter( id => {
+ if (id === req.params.picID) return false
+ return true
+ })
+ return gallery.save() // 500 error
+ })
+ .then(() => {
+ let params = {
+ Bucket: process.env.AWS_BUCKET,
+ Key: tempPic.objectKey,
+ }
+ return s3.deleteObject(params).promise() // 500 error
+ })
+ .then(() => {
+ return Pic.findByIdAndRemove(req.params.picID) //500
+ })
+ .then(() => res.sendStatus(204))
+ .catch(next)
+})
+
+picRouter.get('/api/public/pic', pageQuery, function(req, res, next){
+ let fields = ['username', 'name', 'desc']
+ let query = fuzzyQuery(fields, req.query)
+
+ Pic.find(query)
+ .sort({_id: req.query.sort}).skip(req.query.offset).limit(req.query.pagesize)
+ .then(pics => res.json(pics))
+ .catch(next)
+})
+
+ // this route is private and only returns a users pictures
+picRouter.get('/api/pic', bearerAuth, pageQuery, function(req, res, next){
+ let fuzzyFields = [ 'name', 'desc' ]
+ let query = fuzzyQuery(fuzzyFields, req.query)
+ query.userID = req.user._id.toString()
+ Pic.find(query)
+ .sort({_id: req.query.sort}).skip(req.query.offset).limit(req.query.pagesize)
+ .then(pics => res.json(pics))
+ .catch(next)
+})
+
diff --git a/lab-raziyeh/server.js b/lab-raziyeh/server.js
new file mode 100644
index 0000000..e8bacf1
--- /dev/null
+++ b/lab-raziyeh/server.js
@@ -0,0 +1,48 @@
+'use strict'
+
+// npm modules
+const cors = require('cors')
+const dotenv = require('dotenv')
+const morgan = require('morgan')
+const express = require('express')
+const Promise = require('bluebird')
+const mongoose = require('mongoose')
+const debug = require('debug')('slugram:sever')
+
+// app modules
+const picRouter = require('./route/pic-router.js')
+const authRouter = require('./route/auth-router.js')
+const galleryRouter = require('./route/gallery-router.js')
+const errorMiddleware = require('./lib/error-middleware.js')
+
+// load env vars
+dotenv.load({path: `${__dirname}/.server.env`})
+
+// setup mongoose
+mongoose.Promise = Promise
+mongoose.connect(process.env.MONGODB_URI);
+
+// module constants
+const PORT = process.env.PORT
+const app = express()
+
+
+// app middleware
+app.use(cors())
+let production = process.env.NODE_ENV === 'production'
+let morganFormat = production ? 'common' : 'dev'
+app.use(morgan(morganFormat))
+
+// app routes
+app.use(express.static(`${__dirname}/build`));
+app.use(picRouter)
+app.use(authRouter)
+app.use(galleryRouter)
+app.use(errorMiddleware)
+
+// start server
+const server = module.exports = app.listen(PORT , () => {
+ debug(`server up on ${PORT}`)
+})
+
+server.isRunning = true
diff --git a/lab-raziyeh/test/auth-router-test.js b/lab-raziyeh/test/auth-router-test.js
new file mode 100644
index 0000000..c965da8
--- /dev/null
+++ b/lab-raziyeh/test/auth-router-test.js
@@ -0,0 +1,219 @@
+'use strict'
+
+require('./lib/test-env.js')
+require('./lib/aws-mocks.js')
+const expect = require('chai').expect
+const request = require('superagent')
+const Promise = require('bluebird')
+const mongoose = require('mongoose')
+const serverCtrl = require('./lib/server-ctrl.js')
+const cleanDB = require('./lib/clean-db.js')
+const mockUser = require('./lib/user-mock.js')
+
+mongoose.Promise = Promise
+
+const server = require('../server.js')
+const url = `http://localhost:${process.env.PORT}`
+
+const exampleUser = {
+ username: 'slugbyte',
+ password: '12345678',
+ email: 'slug@slug.slime',
+}
+
+describe('testing auth-router', function(){
+ // start server at for this test file
+ before(done => serverCtrl.serverUp(server, done))
+ // stop server after this test file
+ after(done => serverCtrl.serverDown(server, done))
+ // clean all models from db after each test
+ afterEach(done => cleanDB(done))
+
+ describe('testing POST /api/signup', function(){
+ describe('with valid body', function(){
+ it('should return a token', (done) => {
+ request.post(`${url}/api/signup`)
+ .send(exampleUser)
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.status).to.equal(200)
+ expect(!!res.text).to.equal(true)
+ done()
+ })
+ })
+ })
+
+ describe('with no username', function(){
+ it('should respond with status 400', (done) => {
+ request.post(`${url}/api/signup`)
+ .send({
+ password: exampleUser.password,
+ email: exampleUser.email,
+ })
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+
+ describe('with username < 5', function(){
+ it('should respond with status 400', (done) => {
+ request.post(`${url}/api/signup`)
+ .send({
+ username: 'slug',
+ password: exampleUser.password,
+ email: exampleUser.email,
+ })
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+
+ describe('with duplicate username', function(){
+ before( done => mockUser.call(this, done))
+ it('should respond with status 409', (done) => {
+ request.post(`${url}/api/signup`)
+ .send({
+ username: this.tempUser.username,
+ password: exampleUser.password,
+ email: exampleUser.email,
+ })
+ .end((err, res) => {
+ expect(res.status).to.equal(409)
+ expect(res.text).to.equal('ConflictError')
+ done()
+ })
+ })
+ })
+
+ describe('with duplicate email', function(){
+ before( done => mockUser.call(this, done))
+ it('should respond with status 409', (done) => {
+ request.post(`${url}/api/signup`)
+ .send({
+ username: exampleUser.username,
+ password: exampleUser.password,
+ email: this.tempUser.email,
+ })
+ .end((err, res) => {
+ expect(res.status).to.equal(409)
+ expect(res.text).to.equal('ConflictError')
+ done()
+ })
+ })
+ })
+
+
+ describe('with no email', function(){
+ it('should respond with status 400', (done) => {
+ request.post(`${url}/api/signup`)
+ .send({
+ username: exampleUser.username,
+ password: exampleUser.password,
+ })
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+
+ describe('with no password', function(){
+ it('should respond with status 400', (done) => {
+ request.post(`${url}/api/signup`)
+ .send({
+ email: exampleUser.email,
+ username: exampleUser.username,
+ })
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+
+ describe('with password.length < 8', function(){
+ it('should respond with status 400', (done) => {
+ request.post(`${url}/api/signup`)
+ .send({
+ email: exampleUser.email,
+ password: '124567',
+ username: exampleUser.username,
+ })
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+ })
+
+ describe('testing GET /api/signup', function(){
+ describe('with valid auth', function(){
+ before(done => mockUser.call(this, done))
+ it('should return a token', (done) => {
+ request.get(`${url}/api/login`)
+ // this has to be the same user and pass from mockUser
+ .auth(this.tempUser.username, this.tempPassword)
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.status).to.equal(200)
+ expect(!!res.text).to.equal(true)
+ done()
+ })
+ })
+ })
+
+ describe('with bad username', function(){
+ before(done => mockUser.call(this, done))
+ it('should respond with status 401', (done) => {
+ request.get(`${url}/api/login`)
+ // this has to be the same user and pass from mockUser
+ .auth('bad', this.tempPassword)
+ .end((err, res) => {
+ expect(res.status).to.equal(401)
+ expect(res.text).to.equal('UnauthorizedError')
+ done()
+ })
+ })
+ })
+
+ describe('with bad username', function(){
+ before(done => mockUser.call(this, done))
+ it('should respond with status 401', (done) => {
+ request.get(`${url}/api/login`)
+ // this has to be the same user and pass from mockUser
+ .auth('', this.tempPassword)
+ .end((err, res) => {
+ expect(res.status).to.equal(401)
+ expect(res.text).to.equal('UnauthorizedError')
+ done()
+ })
+ })
+ })
+
+ describe('with bad password', function(){
+ before(done => mockUser.call(this, done))
+ it('should respond with status 401', (done) => {
+ request.get(`${url}/api/login`)
+ // this has to be the same user and pass from mockUser
+ .auth(this.tempUser.username, 'bad')
+ .end((err, res) => {
+ expect(res.status).to.equal(401)
+ expect(res.text).to.equal('UnauthorizedError')
+ done()
+ })
+ })
+ })
+ })
+})
diff --git a/lab-raziyeh/test/data/shield.png b/lab-raziyeh/test/data/shield.png
new file mode 100644
index 0000000..42969d0
Binary files /dev/null and b/lab-raziyeh/test/data/shield.png differ
diff --git a/lab-raziyeh/test/error-middleware-test.js b/lab-raziyeh/test/error-middleware-test.js
new file mode 100644
index 0000000..262ca7c
--- /dev/null
+++ b/lab-raziyeh/test/error-middleware-test.js
@@ -0,0 +1,84 @@
+'use strict'
+
+require('./lib/test-env.js')
+require('./lib/aws-mocks.js')
+
+const expect = require('chai').expect
+const errorMiddleware = require('../lib/error-middleware.js')
+const createError = require('http-errors')
+
+function mockRes (){
+ let res = {}
+ res.status = function(num){
+ this.statusCode = num
+ return this
+ }
+ res.send = function(data){
+ this.text = data.toString()
+ return this
+ }
+ res.json = function(data){
+ this.body = data
+ return this
+ }
+
+ return res
+}
+
+describe('testing errorMiddleware', function(){
+ describe('with vanilla Error', function(){
+ let res
+ before(() => res = mockRes())
+ it('should respond with status 500', done => {
+ let err = new Error('ordinary error')
+ errorMiddleware(err, {}, res, () => {
+ expect(res.statusCode).to.equal(500)
+ expect(res.text).to.equal('InternalServerError')
+ done()
+ })
+ })
+ })
+
+ describe('http-errors error', function(){
+ let res
+ before(() => res = mockRes())
+ it('should respond with status 418', done => {
+ let err = createError(418, 'example error message')
+ errorMiddleware(err, {}, res, () => {
+ expect(res.statusCode).to.equal(418)
+ expect(res.text).to.equal('ImATeapotError')
+ done()
+ })
+ })
+ })
+
+ describe('with ValidationError', function(){
+ let res
+ before(() => res = mockRes())
+ it('should respond with status 418', done => {
+ let err = new Error('You should read the docs')
+ err.name = 'ValidationError'
+ errorMiddleware(err, {}, res, () => {
+ expect(res.statusCode).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+
+ describe('with MongoError && err.message = "E11000 duplicate"', function(){
+ let res
+ before(() => res = mockRes())
+ it('should respond with status 409', done => {
+ let err = new Error('E11000 duplicate')
+ err.name = 'MongoError'
+ errorMiddleware(err, {}, res, () => {
+ expect(res.statusCode).to.equal(409)
+ expect(res.text).to.equal('ConflictError')
+ done()
+ })
+ })
+ })
+})
+
+
diff --git a/lab-raziyeh/test/fuzzy-query-test.js b/lab-raziyeh/test/fuzzy-query-test.js
new file mode 100644
index 0000000..9859684
--- /dev/null
+++ b/lab-raziyeh/test/fuzzy-query-test.js
@@ -0,0 +1,47 @@
+'use strict'
+
+const expect = require('chai').expect
+const fuzzyQuery = require('../lib/fuzzy-query.js')
+
+describe('testing module fuzzy-query', function(){
+ describe('with valid input', function(){
+ let fields = ['name', 'duck']
+ let query = {
+ name: 'slug',
+ duck: 'quack',
+ }
+
+ it('should return a mongo query', done => {
+ let result = fuzzyQuery(fields, query)
+ console.log('result', result)
+ expect(result.name['$regex'].toString()).to.equal('/.*s.*l.*u.*g.*/')
+ expect(result.duck.$regex.toString()).to.equal('/.*q.*u.*a.*c.*k.*/')
+ done()
+ })
+ })
+
+ describe('with bad array', function(){
+ let fields = ''
+ let query = {
+ name: 'slug',
+ duck: 'quack',
+ }
+
+ it('should return a mongo query', done => {
+ let result = fuzzyQuery(fields, query)
+ expect(JSON.stringify(result)).to.equal('{}')
+ done()
+ })
+ })
+
+ describe('with bad array', function(){
+ let fields = []
+ let query = ''
+
+ it('should return a mongo query', done => {
+ let result = fuzzyQuery(fields, query)
+ expect(JSON.stringify(result)).to.equal('{}')
+ done()
+ })
+ })
+})
diff --git a/lab-raziyeh/test/fuzzy-regex-test.js b/lab-raziyeh/test/fuzzy-regex-test.js
new file mode 100644
index 0000000..183f48f
--- /dev/null
+++ b/lab-raziyeh/test/fuzzy-regex-test.js
@@ -0,0 +1,30 @@
+'use strict'
+
+const expect = require('chai').expect
+const fuzzyRegex = require('../lib/fuzzy-regex.js')
+
+describe('testing module fuzzy-regex', function(){
+ describe('with string "yep"', function(){
+ it('should return /.*y.*e.*p.*/', done => {
+ let exp = fuzzyRegex('yep')
+ expect(exp.toString()).to.equal('/.*y.*e.*p.*/')
+ done()
+ })
+ })
+
+ describe('with no input', function(){
+ it('should return /.*/', done => {
+ let exp = fuzzyRegex()
+ expect(exp.toString()).to.equal('/.*/')
+ done()
+ })
+ })
+
+ describe('with non string input', function(){
+ it('should return /.*/', done => {
+ let exp = fuzzyRegex(4)
+ expect(exp.toString()).to.equal('/.*/')
+ done()
+ })
+ })
+})
diff --git a/lab-raziyeh/test/gallery-router-test.js b/lab-raziyeh/test/gallery-router-test.js
new file mode 100644
index 0000000..71db951
--- /dev/null
+++ b/lab-raziyeh/test/gallery-router-test.js
@@ -0,0 +1,969 @@
+'use strict'
+
+require('./lib/test-env.js')
+require('./lib/aws-mocks.js')
+
+// npm
+const expect = require('chai').expect
+const request = require('superagent')
+const mongoose = require('mongoose')
+const Promise = require('bluebird')
+
+// app
+const User = require('../model/user.js')
+const server = require('../server.js')
+const cleanDB = require('./lib/clean-db.js')
+const mockUser = require('./lib/user-mock.js')
+const serverCtrl = require('./lib/server-ctrl.js')
+const fuzzyRegex = require('../lib/fuzzy-regex.js')
+const mockGallery = require('./lib/gallery-mock.js')
+const mockManyPics = require('./lib/mock-many-pics.js')
+const mockManyGallerys = require('./lib/mock-many-gallerys.js')
+//const mockManyEverything = require('./lib/mock-many-everything.js')
+const mockManyEverything = require('./lib/everything-mock.js')
+
+// const
+const url = `http://localhost:${process.env.PORT}`
+ // config
+mongoose.Promise = Promise
+let exampleGallery = {
+ name: 'beach adventure',
+ desc: 'not enough sun screan ouch',
+}
+
+describe('test /api/gallery', function(){
+ // start server at for this test file
+ before(done => serverCtrl.serverUp(server, done))
+ // stop server after this test file
+ after(done => serverCtrl.serverDown(server, done))
+ // clean all models from db after each test
+ afterEach(done => cleanDB(done))
+
+ describe('testing POST to /api/gallery', () => {
+ // create this.tempUser and this.tempToken
+ describe('with valid token and body', () => {
+ before(done => mockUser.call(this, done))
+ it('should return a gallery', done => {
+ request.post(`${url}/api/gallery`)
+ .send(exampleGallery)
+ .set({Authorization: `Bearer ${this.tempToken}`})
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.body.name).to.equal(exampleGallery.name)
+ expect(res.body.desc).to.equal(exampleGallery.desc)
+ expect(res.body.userID).to.equal(this.tempUser._id.toString())
+ let date = new Date(res.body.created).toString()
+ expect(date).to.not.equal('Invalid Date')
+ done()
+ })
+ })
+ })
+
+ describe('with invalid token', () => {
+ before(done => mockUser.call(this, done))
+ it('should respond with status 401', done => {
+ request.post(`${url}/api/gallery`)
+ .send(exampleGallery)
+ .set({Authorization: `Bearer ${this.tempToken}bad`})
+ .end((err, res) => {
+ expect(res.status).to.equal(401)
+ expect(res.text).to.equal('UnauthorizedError')
+ done()
+ })
+ })
+ })
+
+ describe('with invalid Bearer auth', () => {
+ before(done => mockUser.call(this, done))
+ it('should respond with status 400', done => {
+ request.post(`${url}/api/gallery`)
+ .send(exampleGallery)
+ .set({Authorization: 'not bearer auth'})
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+
+ describe('with no Authorization header', () => {
+ before(done => mockUser.call(this, done))
+ it('should respond with status 400', done => {
+ request.post(`${url}/api/gallery`)
+ .send(exampleGallery)
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+
+ describe('with no name', () => {
+ before(done => mockUser.call(this, done))
+ it('should respond with status 400', done => {
+ request.post(`${url}/api/gallery`)
+ .set({Authorization: `Bearer ${this.tempToken}`})
+ .send({ desc: exampleGallery.desc})
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+
+ describe('with no desc', () => {
+ before(done => mockUser.call(this, done))
+ it('should respond with status 400', done => {
+ request.post(`${url}/api/gallery`)
+ .set({Authorization: `Bearer ${this.tempToken}`})
+ .send({ name: exampleGallery.name})
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+ })
+
+ describe('testing GET to /api/gallery/:id', () => {
+ // create this.tempToken, this.tempUser, this.tempGallery
+ describe('with valid token and id', function(){
+ before(done => mockGallery.call(this, done))
+ it('should return a gallery', done => {
+ request.get(`${url}/api/gallery/${this.tempGallery._id}`)
+ .set({
+ Authorization: `Bearer ${this.tempToken}`,
+ })
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.body.name).to.equal(exampleGallery.name)
+ expect(res.body.desc).to.equal(exampleGallery.desc)
+ expect(res.body.userID).to.equal(this.tempUser._id.toString())
+ let date = new Date(res.body.created).toString()
+ expect(date).to.equal(this.tempGallery.created.toString())
+ done()
+ })
+ })
+ })
+
+ describe('with many pictures', function(){
+ before(done => mockManyPics.call(this, 100, done))
+ it('should return a gallery', done => {
+ request.get(`${url}/api/gallery/${this.tempGallery._id}`)
+ .set({
+ Authorization: `Bearer ${this.tempToken}`,
+ })
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.body.name).to.equal(exampleGallery.name)
+ expect(res.body.desc).to.equal(exampleGallery.desc)
+ expect(res.body.userID).to.equal(this.tempUser._id.toString())
+ expect(Array.isArray(res.body.pics)).to.equal(true)
+ expect(res.body.pics.length).to.equal(100)
+ let date = new Date(res.body.created).toString()
+ expect(date).to.equal(this.tempGallery.created.toString())
+ for (let i=0; i< res.body.pics.length; i++){
+ expect(res.body.pics[i]._id.toString()).to.equal(this.tempPics[i]._id.toString())
+ expect(res.body.pics[i].name).to.equal(this.tempPics[i].name)
+ expect(res.body.pics[i].desc).to.equal(this.tempPics[i].desc)
+ expect(res.body.pics[i].imageURI).to.equal(this.tempPics[i].imageURI)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with ?itemcount=10&itempage=2', function(){
+ before(done => mockManyPics.call(this, 100, done))
+ it('should return a gallery', done => {
+ request.get(`${url}/api/gallery/${this.tempGallery._id}?itemcount=10&itempage=2`)
+ .set({
+ Authorization: `Bearer ${this.tempToken}`,
+ })
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.body.name).to.equal(exampleGallery.name)
+ expect(res.body.desc).to.equal(exampleGallery.desc)
+ expect(res.body.userID).to.equal(this.tempUser._id.toString())
+ expect(Array.isArray(res.body.pics)).to.equal(true)
+ expect(res.body.pics.length).to.equal(10)
+ let date = new Date(res.body.created).toString()
+ expect(date).to.equal(this.tempGallery.created.toString())
+ for (let i=0; i< res.body.pics.length; i++){
+ expect(res.body.pics[i]._id.toString()).to.equal(this.tempPics[i + 10 ]._id.toString())
+ expect(res.body.pics[i].name).to.equal(this.tempPics[i + 10].name)
+ expect(res.body.pics[i].desc).to.equal(this.tempPics[i + 10].desc)
+ expect(res.body.pics[i].imageURI).to.equal(this.tempPics[i + 10].imageURI)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with many pictures and ?itemcount=10', function(){
+ before(done => mockManyPics.call(this, 100, done))
+ it('should return a gallery', done => {
+ request.get(`${url}/api/gallery/${this.tempGallery._id}?itemcount=10`)
+ .set({
+ Authorization: `Bearer ${this.tempToken}`,
+ })
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.body.name).to.equal(exampleGallery.name)
+ expect(res.body.desc).to.equal(exampleGallery.desc)
+ expect(res.body.userID).to.equal(this.tempUser._id.toString())
+ expect(Array.isArray(res.body.pics)).to.equal(true)
+ expect(res.body.pics.length).to.equal(10)
+ let date = new Date(res.body.created).toString()
+ expect(date).to.equal(this.tempGallery.created.toString())
+ for (let i=0; i< res.body.pics.length; i++){
+ expect(res.body.pics[i]._id.toString()).to.equal(this.tempPics[i]._id.toString())
+ expect(res.body.pics[i].name).to.equal(this.tempPics[i].name)
+ expect(res.body.pics[i].desc).to.equal(this.tempPics[i].desc)
+ expect(res.body.pics[i].imageURI).to.equal(this.tempPics[i].imageURI)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with many pictures and ?itemcount=10&itemsort=dsc', function(){
+ before(done => mockManyPics.call(this, 100, done))
+ it('should return a gallery', done => {
+ request.get(`${url}/api/gallery/${this.tempGallery._id}?itemcount=10&itemsort=dsc`)
+ .set({
+ Authorization: `Bearer ${this.tempToken}`,
+ })
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.body.name).to.equal(exampleGallery.name)
+ expect(res.body.desc).to.equal(exampleGallery.desc)
+ expect(res.body.userID).to.equal(this.tempUser._id.toString())
+ expect(Array.isArray(res.body.pics)).to.equal(true)
+ expect(res.body.pics.length).to.equal(10)
+ let date = new Date(res.body.created).toString()
+ expect(date).to.equal(this.tempGallery.created.toString())
+ let tempPicsLength = this.tempPics.length
+ for (let i=0; i< res.body.pics.length; i++){
+ expect(res.body.pics[i]._id.toString()).to.equal(this.tempPics[tempPicsLength - 1 - i]._id.toString())
+ expect(res.body.pics[i].name).to.equal(this.tempPics[tempPicsLength - 1 - i].name)
+ expect(res.body.pics[i].desc).to.equal(this.tempPics[tempPicsLength - 1 - i].desc)
+ expect(res.body.pics[i].imageURI).to.equal(this.tempPics[tempPicsLength - 1 - i].imageURI)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with many pictures and ?itemcount=10&itemsort=dsc?itemoffset=1', function(){
+ before(done => mockManyPics.call(this, 100, done))
+ it('should return a gallery', done => {
+ request.get(`${url}/api/gallery/${this.tempGallery._id}?itemcount=10&itemsort=dsc&itemoffset=1`)
+ .set({
+ Authorization: `Bearer ${this.tempToken}`,
+ })
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.body.name).to.equal(exampleGallery.name)
+ expect(res.body.desc).to.equal(exampleGallery.desc)
+ expect(res.body.userID).to.equal(this.tempUser._id.toString())
+ expect(Array.isArray(res.body.pics)).to.equal(true)
+ expect(res.body.pics.length).to.equal(10)
+ let date = new Date(res.body.created).toString()
+ expect(date).to.equal(this.tempGallery.created.toString())
+ let tempPicsLength = this.tempPics.length
+ for (let i=0; i< res.body.pics.length; i++){
+ expect(res.body.pics[i]._id.toString()).to.equal(this.tempPics[tempPicsLength - 2 - i]._id.toString())
+ expect(res.body.pics[i].name).to.equal(this.tempPics[tempPicsLength - 2 - i].name)
+ expect(res.body.pics[i].desc).to.equal(this.tempPics[tempPicsLength - 2 - i].desc)
+ expect(res.body.pics[i].imageURI).to.equal(this.tempPics[tempPicsLength - 2 - i].imageURI)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with many pictures and ?itemcount=10&itemoffset=1', function(){
+ before(done => mockManyPics.call(this, 100, done))
+ it('should return a gallery', done => {
+ request.get(`${url}/api/gallery/${this.tempGallery._id}?itemcount=10&itemoffset=1`)
+ .set({
+ Authorization: `Bearer ${this.tempToken}`,
+ })
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.body.name).to.equal(exampleGallery.name)
+ expect(res.body.desc).to.equal(exampleGallery.desc)
+ expect(res.body.userID).to.equal(this.tempUser._id.toString())
+ expect(Array.isArray(res.body.pics)).to.equal(true)
+ expect(res.body.pics.length).to.equal(10)
+ let date = new Date(res.body.created).toString()
+ expect(date).to.equal(this.tempGallery.created.toString())
+ for (let i=0; i< res.body.pics.length; i++){
+ expect(res.body.pics[i]._id.toString()).to.equal(this.tempPics[i + 1]._id.toString())
+ expect(res.body.pics[i].name).to.equal(this.tempPics[i + 1].name)
+ expect(res.body.pics[i].desc).to.equal(this.tempPics[i + 1].desc)
+ expect(res.body.pics[i].imageURI).to.equal(this.tempPics[i + 1].imageURI)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with invalid token', function(){
+ before(done => mockGallery.call(this, done))
+ it('should respond with status 401', done => {
+ request.get(`${url}/api/gallery/${this.tempGallery._id}`)
+ .set({
+ Authorization: `Bearer ${this.tempToken}bad`,
+ })
+ .end((err, res) => {
+ expect(res.status).to.equal(401)
+ expect(res.text).to.equal('UnauthorizedError')
+ done()
+ })
+ })
+ })
+
+ describe('with invalid Bearer auth', function(){
+ before(done => mockGallery.call(this, done))
+ it('should respond with status 400', done => {
+ request.get(`${url}/api/gallery/${this.tempGallery._id}`)
+ .set({ Authorization: 'bad request' })
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+
+ describe('with no Authorization header', function(){
+ before(done => mockGallery.call(this, done))
+ it('should respond with status 400', done => {
+ request.get(`${url}/api/gallery/${this.tempGallery._id}`)
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+
+ describe('with invalid id', function(){
+ before(done => mockGallery.call(this, done))
+ it('should return a gallery', done => {
+ request.get(`${url}/api/gallery/${this.tempGallery._id}bad`)
+ .set({
+ Authorization: `Bearer ${this.tempToken}`,
+ })
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+
+ describe('with user whos been removed', function(){
+ before(done => mockGallery.call(this, done))
+ before(done => {
+ User.remove({})
+ .then(() => done())
+ .catch(done)
+ })
+
+ it('should respond with status 401', done => {
+ request.get(`${url}/api/gallery/${this.tempGallery._id}`)
+ .set({
+ Authorization: `Bearer ${this.tempToken}`,
+ })
+ .end((err, res) => {
+ expect(res.status).to.equal(401)
+ expect(res.text).to.equal('UnauthorizedError')
+ done()
+ })
+ })
+ })
+
+ describe('with wrong user', function(){
+ // mock user, password, token, and gallery
+ before(done => mockGallery.call(this, done))
+ // overwrite user, password, and token with new user
+ before(done => mockUser.call(this, done))
+
+ it('should respond with status 401', done => {
+ request.get(`${url}/api/gallery/${this.tempGallery._id}`)
+ .set({
+ Authorization: `Bearer ${this.tempToken}`,
+ })
+ .end((err, res) => {
+ expect(res.status).to.equal(401)
+ expect(res.text).to.equal('UnauthorizedError')
+ done()
+ })
+ })
+ })
+ })
+
+ describe('testing PUT /api/gallery/:galleryID', function(){
+ describe('update name ande desc', function(){
+ // mock user, password, token, and gallery
+ before(done => mockGallery.call(this, done))
+
+ it('should return a gallery', done => {
+ request.put(`${url}/api/gallery/${this.tempGallery._id}`)
+ .send({
+ name: 'hello',
+ desc: 'cool',
+ })
+ .set({
+ Authorization: `Bearer ${this.tempToken}`,
+ })
+ .end((err, res) => {
+ if(err) return done(err)
+ expect(res.status).to.equal(200)
+ expect(res.body.name).to.equal('hello')
+ expect(res.body.desc).to.equal('cool')
+ done()
+ })
+ })
+ })
+
+ describe('update name ande desc', function(){
+ // mock user, password, token, and gallery
+ before(done => mockGallery.call(this, done))
+ before(done => mockUser.call(this, done))
+
+ it('should return a gallery', done => {
+ request.put(`${url}/api/gallery/${this.tempGallery._id}`)
+ .send({
+ name: 'hello',
+ desc: 'cool',
+ })
+ .set({
+ Authorization: `Bearer ${this.tempToken}`,
+ })
+ .end((err, res) => {
+ expect(res.status).to.equal(401)
+ done()
+ })
+ })
+ })
+
+ describe('update name', function(){
+ // mock user, password, token, and gallery
+ before(done => mockGallery.call(this, done))
+
+ it('should return a gallery', done => {
+ request.put(`${url}/api/gallery/${this.tempGallery._id}`)
+ .send({
+ name: 'hello',
+ })
+ .set({
+ Authorization: `Bearer ${this.tempToken}`,
+ })
+ .end((err, res) => {
+ if (err) return done(err)
+ expect(res.status).to.equal(200)
+ expect(res.body.name).to.equal('hello')
+ expect(res.body.desc).to.equal(this.tempGallery.desc)
+ done()
+ })
+ })
+ })
+
+ describe('update desc', function(){
+ // mock user, password, token, and gallery
+ before(done => mockGallery.call(this, done))
+
+ it('should return a gallery', done => {
+ request.put(`${url}/api/gallery/${this.tempGallery._id}`)
+ .send({
+ desc: 'cool',
+ })
+ .set({
+ Authorization: `Bearer ${this.tempToken}`,
+ })
+ .end((err, res) => {
+ if (err) return done(err)
+ expect(res.status).to.equal(200)
+ expect(res.body.name).to.equal(this.tempGallery.name)
+ expect(res.body.desc).to.equal('cool')
+ done()
+ })
+ })
+ })
+
+ describe('with bad galeryID', function(){
+ // mock user, password, token, and gallery
+ before(done => mockGallery.call(this, done))
+
+ it('should return a gallery', done => {
+ request.put(`${url}/api/gallery/${this.tempGallery._id}bad`)
+ .send({
+ desc: 'cool',
+ })
+ .set({
+ Authorization: `Bearer ${this.tempToken}`,
+ })
+ .end((err, res) => {
+ expect(res.status).to.equal(404)
+ expect(res.text).to.equal('NotFoundError')
+ done()
+ })
+ })
+ })
+
+ describe('with bad token', function(){
+ // mock user, password, token, and gallery
+ before(done => mockGallery.call(this, done))
+
+ it('should respond with status 401', done => {
+ request.put(`${url}/api/gallery/${this.tempGallery._id}`)
+ .send({
+ desc: 'cool',
+ })
+ .set({
+ Authorization: `Bearer ${this.tempToken}bad`,
+ })
+ .end((err, res) => {
+ expect(res.status).to.equal(401)
+ expect(res.text).to.equal('UnauthorizedError')
+ done()
+ })
+ })
+ })
+
+ describe('witn no auth', function(){
+ // mock user, password, token, and gallery
+ before(done => mockGallery.call(this, done))
+
+ it('should respond with status 400', done => {
+ request.put(`${url}/api/gallery/${this.tempGallery._id}bad`)
+ .send({
+ desc: 'cool',
+ })
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+ })
+
+ describe('testing DELETE /api/gallery/:galleryID', function(){
+ describe('should respond with status 204', function(){
+ // mock user, password, token, and gallery
+ before(done => mockGallery.call(this, done))
+ it('should return a gallery', done => {
+ request.delete(`${url}/api/gallery/${this.tempGallery._id}`)
+ .set({
+ Authorization: `Bearer ${this.tempToken}`,
+ })
+ .end((err, res) => {
+ expect(res.status).to.equal(204)
+ done()
+ })
+ })
+ })
+
+ describe('with invalid galleryID', function(){
+ // mock user, password, token, and gallery
+ before(done => mockGallery.call(this, done))
+ it('should return a gallery', done => {
+ request.delete(`${url}/api/gallery/${this.tempGallery._id}bad`)
+ .set({ Authorization: `Bearer ${this.tempToken}` })
+ .end((err, res) => {
+ expect(res.status).to.equal(404)
+ expect(res.text).to.equal('NotFoundError')
+ done()
+ })
+ })
+ })
+
+ describe('with invalid galleryID', function(){
+ // mock user, password, token, and gallery
+ before(done => mockGallery.call(this, done))
+ before(done => mockUser.call(this, done))
+ it('should return a gallery', done => {
+ request.delete(`${url}/api/gallery/${this.tempGallery._id}`)
+ .set({ Authorization: `Bearer ${this.tempToken}` })
+ .end((err, res) => {
+ expect(res.status).to.equal(401)
+ done()
+ })
+ })
+ })
+
+ describe('with invalid token', function(){
+ // mock user, password, token, and gallery
+ before(done => mockGallery.call(this, done))
+ it('should respond with status 401', done => {
+ request.delete(`${url}/api/gallery/${this.tempGallery._id}`)
+ .set({
+ Authorization: `Bearer ${this.tempToken}bad`,
+ })
+ .end((err, res) => {
+ expect(res.status).to.equal(401)
+ expect(res.text).to.equal('UnauthorizedError')
+ done()
+ })
+ })
+ })
+
+ describe('witn no auth', function(){
+ // mock user, password, token, and gallery
+ before(done => mockGallery.call(this, done))
+ it('should respond with status 400', done => {
+ request.delete(`${url}/api/gallery/${this.tempGallery._id}`)
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+ })
+
+ describe('testing GET /api/gallery', function(){
+ describe('with valid request', function(){
+ before( done => mockManyGallerys.call(this, 100, done))
+ it('should respond with status 400', done => {
+ request.get(`${url}/api/gallery`)
+ .set({ Authorization: `Bearer ${this.tempToken}` })
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ expect(res.body.length).to.equal(50)
+ done()
+ })
+ })
+ })
+
+ describe('with ?pagesize=10', function(){
+ before( done => mockManyGallerys.call(this, 100, done))
+ it('should return 10 notes', done => {
+ request.get(`${url}/api/gallery?pagesize=5`)
+ .set({ Authorization: `Bearer ${this.tempToken}` })
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ expect(res.body.length).to.equal(5)
+ for (let i=0; i < res.body.length; i++){
+ expect(res.body[i].name).to.equal(this.tempGallerys[i].name)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with ?sort=dsc', function(){
+ before( done => mockManyGallerys.call(this, 100, done))
+ it('should return 10 notes', done => {
+ request.get(`${url}/api/gallery?sort=dsc`)
+ .set({ Authorization: `Bearer ${this.tempToken}` })
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ expect(res.body.length).to.equal(50)
+ for (let i=0; i < res.body.length; i++){
+ expect(res.body[i].name).to.equal(this.tempGallerys[this.tempGallerys.length - i - 1].name)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with ?sort=dsc?offset=3', function(){
+ before( done => mockManyGallerys.call(this, 100, done))
+ it('should return 10 notes', done => {
+ request.get(`${url}/api/gallery?sort=dsc&offset=3`)
+ .set({ Authorization: `Bearer ${this.tempToken}` })
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ expect(res.body.length).to.equal(50)
+ for (let i=0; i < res.body.length; i++){
+ let index = this.tempGallerys.length - i - 4
+ expect(res.body[i].name).to.equal(this.tempGallerys[index].name)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with offset=1', function(){
+ before( done => mockManyGallerys.call(this, 100, done))
+ it('should return 10 notes', done => {
+ request.get(`${url}/api/gallery?offset=1`)
+ .set({ Authorization: `Bearer ${this.tempToken}` })
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ expect(res.body.length).to.equal(50)
+ for (let i=0; i < res.body.length; i++){
+ expect(res.body[i].name).to.equal(this.tempGallerys[i + 1].name)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with ?page=2', function(){
+ before( done => mockManyGallerys.call(this, 100, done))
+ it('should return 10 notes', done => {
+ request.get(`${url}/api/gallery?page=2`)
+ .set({ Authorization: `Bearer ${this.tempToken}` })
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ expect(res.body.length).to.equal(50)
+ for (let i=0; i < res.body.length; i++){
+ expect(res.body[i].name).to.equal(this.tempGallerys[i + 50].name)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with ?page=3&?offset=1', function(){
+ before( done => mockManyGallerys.call(this, 150, done))
+ it('should return 10 notes', done => {
+ request.get(`${url}/api/gallery?page=3&offset=1`)
+ .set({ Authorization: `Bearer ${this.tempToken}` })
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ expect(res.body.length).to.equal(49)
+ for (let i=0; i < res.body.length; i++){
+ expect(res.body[i].name).to.equal(this.tempGallerys[i + 101].name)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with ?page=-1', function(){
+ before( done => mockManyGallerys.call(this, 150, done))
+ it('should return 10 notes', done => {
+ request.get(`${url}/api/gallery?page=-1`)
+ .set({ Authorization: `Bearer ${this.tempToken}` })
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ expect(res.body.length).to.equal(50)
+ for (let i=0; i < res.body.length; i++){
+ expect(res.body[i].name).to.equal(this.tempGallerys[i].name)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with ?pagesize=-1', function(){
+ before( done => mockManyGallerys.call(this, 50, done))
+ it('should return 10 notes', done => {
+ request.get(`${url}/api/gallery?pagesize=-1`)
+ .set({ Authorization: `Bearer ${this.tempToken}` })
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ expect(res.body.length).to.equal(1)
+ for (let i=0; i < res.body.length; i++){
+ expect(res.body[i].name).to.equal(this.tempGallerys[i].name)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with ?pagesize=300', function(){
+ before( done => mockManyGallerys.call(this, 300, done))
+ it('should return 10 notes', done => {
+ request.get(`${url}/api/gallery?pagesize=250`)
+ .set({ Authorization: `Bearer ${this.tempToken}` })
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ expect(res.body.length).to.equal(250)
+ for (let i=0; i < res.body.length; i++){
+ expect(res.body[i].name).to.equal(this.tempGallerys[i].name)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with invalid token', function(){
+ before( done => mockManyGallerys.call(this, 50, done))
+ it('should respond with status 401', done => {
+ request.get(`${url}/api/gallery`)
+ .set({ Authorization: `Bearer ${this.tempToken}bad` })
+ .end((err, res) => {
+ expect(res.status).to.equal(401)
+ expect(res.text).to.equal('UnauthorizedError')
+ done()
+ })
+ })
+ })
+
+ describe('with ?name=co', function(){
+ before( done => mockManyGallerys.call(this, 100, done))
+ it('should respond nodes with fuzzy match co', done => {
+ request.get(`${url}/api/gallery?name=co`)
+ .set({ Authorization: `Bearer ${this.tempToken}` })
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ console.log('matching notes', res.body.length)
+ let fuzzyName = fuzzyRegex('co')
+ for (let i=0; i < res.body.length; i++){
+ expect(res.body[i].name).to.match(fuzzyName)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with ?desc=co', function(){
+ before( done => mockManyGallerys.call(this, 100, done))
+ it('should respond nodes with fuzzy match co', done => {
+ request.get(`${url}/api/gallery?desc=co`)
+ .set({ Authorization: `Bearer ${this.tempToken}` })
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ console.log('matching notes', res.body.length)
+ let fuzzyName = fuzzyRegex('co')
+ for (let i=0; i < res.body.length; i++){
+ expect(res.body[i].desc).to.match(fuzzyName)
+ }
+ done()
+ })
+ })
+ })
+ })
+
+ describe('testing GET /api/public/gallery', function(){
+ describe('with valid request', function(){
+ let options = {
+ users: 4,
+ gallerys: 3,
+ pics: 4,
+ }
+
+ before( done => mockManyEverything.call(this, options, done))
+ it('should respond nodes with fuzzy match co', done => {
+ request.get(`${url}/api/public/gallery`)
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ done()
+ })
+ })
+ })
+
+ describe('with ?username=lu', function(){
+ let options = {
+ users: 30,
+ gallerys: 1,
+ pics: 1,
+ }
+
+ before( done => mockManyEverything.call(this, options, done))
+ it('should respond nodes with fuzzy match lu', done => {
+ request.get(`${url}/api/public/gallery?username=lu`)
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ let fuzzy = fuzzyRegex('lu')
+ console.log('matches found', res.body.length)
+ for (let i=0; i < res.body.length; i++){
+ expect(res.body[i].username).to.match(fuzzy)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with ?name=lu', function(){
+ let options = {
+ users: 5,
+ gallerys: 10,
+ pics: 1,
+ }
+
+ before( done => mockManyEverything.call(this, options, done))
+ it('should respond nodes with fuzzy match lu', done => {
+ request.get(`${url}/api/public/gallery?name=lu`)
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ let fuzzy = fuzzyRegex('lu')
+ console.log('matches found', res.body.length)
+ for (let i=0; i < res.body.length; i++){
+ expect(res.body[i].name).to.match(fuzzy)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with ?itemcount=4', function(){
+ let options = {
+ users: 2,
+ gallerys: 5,
+ pics: 10,
+ }
+
+ before( done => mockManyEverything.call(this, options, done))
+ it('each galery should have 4 pics', done => {
+ request.get(`${url}/api/public/gallery?itemcount=4`)
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ for (let i=0; i < res.body.length; i++){
+ expect(res.body[i].pics.length).to.equal(4)
+ }
+ done()
+ })
+ })
+ })
+
+ describe('with ?pagesize=4', function(){
+ let options = {
+ users: 2,
+ gallerys: 5,
+ pics: 10,
+ }
+
+ before( done => mockManyEverything.call(this, options, done))
+ it('show top 4 galerys with 10 pics', done => {
+ request.get(`${url}/api/public/gallery?pagesize=4`)
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ expect(res.body.length).to.equal(4)
+ for (let i=0; i < res.body.length; i++){
+ expect(res.body[i].pics.length).to.equal(10)
+ }
+ done()
+ })
+ })
+ })
+ })
+})
diff --git a/lab-raziyeh/test/lib/aws-mocks.js b/lab-raziyeh/test/lib/aws-mocks.js
new file mode 100644
index 0000000..5e8db65
--- /dev/null
+++ b/lab-raziyeh/test/lib/aws-mocks.js
@@ -0,0 +1,41 @@
+'use strict'
+
+const AWSMock = require('aws-sdk-mock')
+
+module.exports = exports = {}
+
+exports.uploadMock = {
+ ETag: '"5eefd06b5b384cc52f35a0c49414ea31"',
+ VersionId: '_F6K4BKgNMBKeTvrPEd27rl_4qPQl9Hy',
+ Location: 'https://slugram-assets.s3.amazonaws.com/cd52be0bc15dd9500e87d9afcf35c19e.png',
+ key: 'cd52be0bc15dd9500e87d9afcf35c19e.png',
+ Key: 'cd52be0bc15dd9500e87d9afcf35c19e.png',
+ Bucket: 'slugram-assets',
+}
+
+AWSMock.mock('S3', 'upload', function(params, callback){
+ if(params.ACL !== 'public-read')
+ return callback(new Error('ACL must be public read'))
+ if(params.Bucket !== process.env.AWS_BUCKET)
+ return callback(new Error('Bucket must be slugram-assets'))
+ if(!params.Key)
+ return callback(new Error('requres Key'))
+ if(!params.Body)
+ return callback(new Error('requires body'))
+ callback(null, exports.uploadMock)
+})
+
+exports.deleteMock = {
+ DeleteMarker: 'true',
+ VersionId: 'lv9XPH0r.UfGZERuv3u7WwxkIzwPKP2d',
+}
+
+AWSMock.mock('S3', 'deleteObject', function(params, callback){
+ if(params.Bucket !== process.env.AWS_BUCKET)
+ return callback(new Error('Bucket must be slugram-assets'))
+ if(!params.Key)
+ return callback(new Error('requres Key'))
+ callback(null, exports.deleteMock)
+})
+
+
diff --git a/lab-raziyeh/test/lib/clean-db.js b/lab-raziyeh/test/lib/clean-db.js
new file mode 100644
index 0000000..04e0a33
--- /dev/null
+++ b/lab-raziyeh/test/lib/clean-db.js
@@ -0,0 +1,18 @@
+'use strict'
+
+const debug = require('debug')('slugram:clean-db')
+
+const Pic = require('../../model/pic.js')
+const User = require('../../model/user.js')
+const Gallery = require('../../model/gallery.js')
+
+module.exports = function(done){
+ debug('clean up database')
+ Promise.all([
+ Pic.remove({}),
+ User.remove({}),
+ Gallery.remove({}),
+ ])
+ .then( () => done())
+ .catch(done)
+}
diff --git a/lab-raziyeh/test/lib/everything-mock.js b/lab-raziyeh/test/lib/everything-mock.js
new file mode 100644
index 0000000..20e607f
--- /dev/null
+++ b/lab-raziyeh/test/lib/everything-mock.js
@@ -0,0 +1,122 @@
+'use strict'
+
+const Promise = require('bluebird')
+const lorem = require('lorem-ipsum')
+const debug = require('debug')('slugram:gallery-mock-everything')
+
+const Pic = require('../../model/pic.js')
+const User = require('../../model/user.js')
+const Gallery = require('../../model/gallery.js')
+
+module.exports = function(options, done){
+ debug('mocking users, gallerys, and pics')
+ if(!checkOptions)
+ return done('bad options')
+
+ // make usercount
+ // make gallerycount for each user
+ // make pictures for each gallery
+ this.tempUserData = []
+ this.tempGallerys = []
+ this.tempPics = []
+
+ let makeUsers = []
+ for(var i=0; i {
+ this.tempUserData.push(userdata)
+ let makeUserGallerys = []
+ let userID = userdata.tempUser._id.toString()
+ let username = userdata.tempUser.username
+ for(var i=0; i {
+ return Promise.resolve(userGallerys)
+ .map(gallery => {
+ let makeGalleryPics = []
+ let userID = gallery.userID.toString()
+ let username = gallery.username
+ for(var i=0; i {
+ this.tempPics.push(pic)
+ let picID = pic._id.toString()
+ gallery.pics.push(picID)
+ return gallery.save()
+ })
+ .each(gallery => this.tempGallerys.push(gallery))
+ })
+ })
+ .then(() => done())
+ .catch(done)
+}
+
+function checkOptions(options){
+ if (!options.users)
+ return false
+ if (!options.gallerys)
+ return false
+ if (!options.pics)
+ return false
+ return true
+}
+
+function mockAUser(){
+ let username = lorem({count: 4, units: 'word'}).split(' ').join('-')
+ let password = lorem({count: 4, units: 'word'}).split(' ').join('-')
+ let email= lorem({count: 4, units: 'word'}).split(' ').join('-')
+ let exampleUser = {
+ username,
+ password,
+ email: `${email}@slug.slug`,
+ }
+ let tempPassword = password
+ let tempUser, tempToken
+ return new User(exampleUser)
+ .generatePasswordHash(tempPassword)
+ .then( user => {
+ tempUser = user
+ return user.generateToken()
+ })
+ .then( token => {
+ tempToken = token
+ return {
+ tempUser,
+ tempToken,
+ tempPassword,
+ }
+ })
+}
+
+function mockAGallery(userID, username){
+ let name = lorem({count: 2, units: 'word'})
+ let desc = lorem({count: 2, units: 'sentence'})
+ let exampleGallery = { name, desc , userID, username}
+ return new Gallery(exampleGallery).save()
+}
+
+function mockAPic(userID, username){
+ let name = lorem({count: 2, units: 'word'})
+ let desc = lorem({count: 2, units: 'sentence'})
+ let uri = lorem({count: 5, units: 'word'}).split(' ').join('-')
+ let objectKey = lorem({count: 5, units: 'word'}).split(' ').join('')
+ let imageURI = `https://${uri}/${objectKey}`
+ let examplePicData = {
+ name,
+ desc,
+ userID,
+ username,
+ imageURI,
+ objectKey,
+ created: new Date(),
+ }
+ return new Pic(examplePicData).save()
+}
diff --git a/lab-raziyeh/test/lib/gallery-mock.js b/lab-raziyeh/test/lib/gallery-mock.js
new file mode 100644
index 0000000..9202401
--- /dev/null
+++ b/lab-raziyeh/test/lib/gallery-mock.js
@@ -0,0 +1,26 @@
+'use strict'
+
+const debug = require('debug')('slugram:gallery-mock')
+const userMock = require('./user-mock.js')
+const Gallery = require('../../model/gallery.js')
+
+
+module.exports = function(done){
+ debug('create mock gallery')
+ let exampleGallery = {
+ name: 'beach adventure',
+ desc: 'not enough sun screan ouch',
+ }
+ userMock.call(this, err => {
+ if (err)
+ return done(err)
+ exampleGallery.userID = this.tempUser._id.toString()
+ exampleGallery.username = this.tempUser.username
+ new Gallery(exampleGallery).save()
+ .then( gallery => {
+ this.tempGallery = gallery
+ done()
+ })
+ .catch(done)
+ })
+}
diff --git a/lab-raziyeh/test/lib/mock-many-gallerys.js b/lab-raziyeh/test/lib/mock-many-gallerys.js
new file mode 100644
index 0000000..236945d
--- /dev/null
+++ b/lab-raziyeh/test/lib/mock-many-gallerys.js
@@ -0,0 +1,32 @@
+'use strict'
+
+const debug = require('debug')('slugram:gallery-mock')
+const userMock = require('./user-mock.js')
+const Gallery = require('../../model/gallery.js')
+const lorem = require('lorem-ipsum')
+
+module.exports = function(count, done){
+ debug(`mock ${count}gallerys`)
+ userMock.call(this, err => {
+ if (err) return done(err)
+ let galleryMocks = []
+ let userID = this.tempUser._id.toString()
+ let username = this.tempUser.username
+ for(var i=0; i {
+ this.tempGallerys = tempGallerys
+ done()
+ })
+ .catch(done)
+ })
+}
+
+function mockAGallery(userID, username){
+ let name = lorem({count: 2, units: 'word'})
+ let desc = lorem({count: 2, units: 'sentence'})
+ let exampleGallery = { name, desc , userID, username}
+ return new Gallery(exampleGallery).save()
+}
diff --git a/lab-raziyeh/test/lib/mock-many-pics.js b/lab-raziyeh/test/lib/mock-many-pics.js
new file mode 100644
index 0000000..4d380c3
--- /dev/null
+++ b/lab-raziyeh/test/lib/mock-many-pics.js
@@ -0,0 +1,54 @@
+
+'use strict'
+
+const Pic = require('../../model/pic.js')
+const debug = require('debug')('slugram:gallery-mock')
+const galleryMock = require('./gallery-mock.js')
+const lorem = require('lorem-ipsum')
+
+// create a uesr, token, pass, gallery
+// create a bunch of pics
+// add pic ids to gallery
+// save gallery
+// done
+module.exports = function(count, done){
+ debug(`mock ${count}gallerys`)
+ galleryMock.call(this, err => {
+ if (err) return done(err)
+ let picMocks = []
+ let userID = this.tempUser._id.toString()
+ let username = this.tempUser.username
+ for(var i=0; i {
+ tempPics.forEach(pic => {
+ let picID = pic._id.toString()
+ this.tempGallery.pics.push(picID)
+ })
+ this.tempPics = tempPics
+ return this.tempGallery.save()
+ })
+ .then(() => done())
+ .catch(done)
+ })
+}
+
+function mockAPic(userID, username){
+ let name = lorem({count: 2, units: 'word'})
+ let desc = lorem({count: 2, units: 'sentence'})
+ let uri = lorem({count: 5, units: 'word'}).split(' ').join('-')
+ let objectKey = lorem({count: 5, units: 'word'}).split(' ').join('')
+ let imageURI = `https://${uri}/${objectKey}`
+ let examplePicData = {
+ name,
+ desc,
+ userID,
+ username,
+ imageURI,
+ objectKey,
+ created: new Date(),
+ }
+ return new Pic(examplePicData).save()
+}
diff --git a/lab-raziyeh/test/lib/mock-many-users.js b/lab-raziyeh/test/lib/mock-many-users.js
new file mode 100644
index 0000000..198bb41
--- /dev/null
+++ b/lab-raziyeh/test/lib/mock-many-users.js
@@ -0,0 +1,50 @@
+'use strict'
+
+const debug = require('debug')('sulgram:mock-many-users')
+const User = require('../../model/user.js')
+const lorem = require('lorem-ipsum')
+
+module.exports = function(count, done){
+ debug(`creating ${count} users`)
+ let userMocks = []
+
+ for(var i=0; i {
+ this.tempUsers = tempUsers
+ done()
+ })
+ .catch(done)
+
+}
+
+function mockAUser(){
+ let username = lorem({count: 2, units: 'word'}).split(' ').join('-')
+ let password = lorem({count: 2, units: 'word'}).split(' ').join('-')
+ let email= lorem({count: 2, units: 'word'}).split(' ').join('-')
+ let exampleUser = {
+ username,
+ password,
+ email: `${email}@slug.slug`,
+ }
+ let tempPassword = password
+ let tempUser, tempToken
+ return new User(exampleUser)
+ .generatePasswordHash(exampleUser.password)
+ .then( user => user.save())
+ .then( user => {
+ tempUser = user
+ return user.generateToken()
+ })
+ .then( token => {
+ tempToken = token
+ return {
+ tempUser,
+ tempToken,
+ tempPassword,
+ }
+ })
+}
diff --git a/lab-raziyeh/test/lib/pic-mock.js b/lab-raziyeh/test/lib/pic-mock.js
new file mode 100644
index 0000000..aa96973
--- /dev/null
+++ b/lab-raziyeh/test/lib/pic-mock.js
@@ -0,0 +1,36 @@
+'use strict'
+
+// npm modules
+const debug = require('debug')('slugram:pic-mock')
+
+// app modules
+const Pic = require('../../model/pic.js')
+const awsMocks = require('./aws-mocks.js')
+const galleryMock = require('./gallery-mock.js')
+const lorem = require('lorem-ipsum')
+
+module.exports = function(done){
+ debug('creating mock pic')
+ let name = lorem({count: 2, units: 'word'})
+ let desc = lorem({count: 2, units: 'sentence'})
+ let examplePicData = {
+ name,
+ desc,
+ created: new Date(),
+ imageURI: awsMocks.uploadMock.Location,
+ objectKey: awsMocks.uploadMock.Key,
+ }
+
+ galleryMock.call(this, err => {
+ if (err) return done(err)
+ examplePicData.username = this.tempUser.username
+ examplePicData.userID = this.tempUser._id.toString()
+ examplePicData.galleryID = this.tempGallery._id.toString()
+ new Pic(examplePicData).save()
+ .then( pic => {
+ this.tempPic = pic
+ done()
+ })
+ .catch(done)
+ })
+}
diff --git a/lab-raziyeh/test/lib/server-ctrl.js b/lab-raziyeh/test/lib/server-ctrl.js
new file mode 100644
index 0000000..4aaf37f
--- /dev/null
+++ b/lab-raziyeh/test/lib/server-ctrl.js
@@ -0,0 +1,30 @@
+'use strict'
+
+const debug = require('debug')('slugram:server-ctrl')
+
+module.exports = exports = {}
+
+exports.serverUp = function(server, done){
+ if (!server.isRunning){
+ server.listen(process.env.PORT, () => {
+ server.isRunning = true
+ debug('server up')
+ done()
+ })
+ return
+ }
+ done()
+}
+
+exports.serverDown = function(server, done){
+ if(server.isRunning){
+ server.close(err => {
+ if (err) return done(err)
+ server.isRunning = false
+ debug('server down')
+ done()
+ })
+ return
+ }
+ done()
+}
diff --git a/lab-raziyeh/test/lib/test-env.js b/lab-raziyeh/test/lib/test-env.js
new file mode 100644
index 0000000..fb41f42
--- /dev/null
+++ b/lab-raziyeh/test/lib/test-env.js
@@ -0,0 +1,8 @@
+'use strict'
+process.env.MONGODB_URI = 'mongodb://localhost/slugtesting'
+process.env.PORT = 8888
+process.env.NODE_ENV = 'testing'
+process.env.APP_SECRET = 'lulwat fake secret for tests'
+process.env.AWS_ACCESS_KEY_ID = 'FAKEKEYFORAWS'
+process.env.AWS_SECRET_ACCESS_KEY = 'FAKEACESSKEY'
+process.env.AWS_BUCKET = 'slugram-assets-testing'
diff --git a/lab-raziyeh/test/lib/user-mock.js b/lab-raziyeh/test/lib/user-mock.js
new file mode 100644
index 0000000..e4e67a6
--- /dev/null
+++ b/lab-raziyeh/test/lib/user-mock.js
@@ -0,0 +1,30 @@
+'use strict'
+
+const debug = require('debug')('sulgram:user-mock')
+const User = require('../../model/user.js')
+const lorem = require('lorem-ipsum')
+
+module.exports = function(done){
+ debug('create mock user')
+ let username = lorem({count: 2, units: 'word'}).split(' ').join('-')
+ let password = lorem({count: 2, units: 'word'}).split(' ').join('-')
+ let email= lorem({count: 2, units: 'word'}).split(' ').join('-')
+ let exampleUser = {
+ username,
+ password,
+ email: `${email}@slug.slug`,
+ }
+ this.tempPassword = password
+ new User(exampleUser)
+ .generatePasswordHash(exampleUser.password)
+ .then( user => user.save())
+ .then( user => {
+ this.tempUser = user
+ return user.generateToken()
+ })
+ .then( token => {
+ this.tempToken = token
+ done()
+ })
+ .catch(done)
+}
diff --git a/lab-raziyeh/test/pic-router-test.js b/lab-raziyeh/test/pic-router-test.js
new file mode 100644
index 0000000..b410eb7
--- /dev/null
+++ b/lab-raziyeh/test/pic-router-test.js
@@ -0,0 +1,425 @@
+'use strict'
+
+// mock third party services
+require('./lib/test-env.js')
+const awsMocks = require('./lib/aws-mocks.js')
+
+// npm modules
+const expect = require('chai').expect
+const request = require('superagent')
+
+// app modules
+
+const picMock = require('./lib/pic-mock.js')
+const cleanDB = require('./lib/clean-db.js')
+const userMock = require('./lib/user-mock.js')
+let fuzzyRegex = require('../lib/fuzzy-regex.js')
+const serverCtrl = require('./lib/server-ctrl.js')
+const galleryMock = require('./lib/gallery-mock.js')
+const mockManyPics = require('./lib/mock-many-pics.js')
+const mockManyEverything = require('./lib/everything-mock.js')
+
+// module constants
+const server = require('../server.js')
+const url = `http://localhost:${process.env.PORT}`
+
+const examplePic = {
+ name: 'sunburn',
+ desc: 'owie no thank you',
+ file: `${__dirname}/data/shield.png`,
+}
+
+describe('testing pic-router', function(){
+ // start server before all tests
+ before( done => serverCtrl.serverUp(server, done))
+ // stop server before all tests
+ after(done => serverCtrl.serverDown(server, done))
+ // remove all models from database after every test
+ afterEach(done => cleanDB(done))
+
+ describe('testing post /api/gallery/:id/pic', function(){
+ describe('with valid token and data', function(){
+ before(done => galleryMock.call(this, done))
+ it('should return a pic', done => {
+ request.post(`${url}/api/gallery/${this.tempGallery._id}/pic`)
+ .set({Authorization: `Bearer ${this.tempToken}`})
+ .field('name', examplePic.name)
+ .field('desc', examplePic.desc)
+ .attach('file', examplePic.file)
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.status).to.equal(200)
+ expect(res.body.name).to.equal(examplePic.name)
+ expect(res.body.desc).to.equal(examplePic.desc)
+ expect(res.body.imageURI).to.equal(awsMocks.uploadMock.Location)
+ expect(res.body.objectKey).to.equal(awsMocks.uploadMock.Key)
+ done()
+ })
+ })
+ })
+
+ describe('with no name', function(){
+ before(done => galleryMock.call(this, done))
+ it('should respond with status 400', done => {
+ request.post(`${url}/api/gallery/${this.tempGallery._id}/pic`)
+ .set({Authorization: `Bearer ${this.tempToken}`})
+ .field('desc', examplePic.desc)
+ .attach('file', examplePic.file)
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+
+ describe('with no desc', function(){
+ before(done => galleryMock.call(this, done))
+ it('should respond with status 400', done => {
+ request.post(`${url}/api/gallery/${this.tempGallery._id}/pic`)
+ .set({Authorization: `Bearer ${this.tempToken}`})
+ .field('name', examplePic.name)
+ .attach('file', examplePic.file)
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+
+ describe('with no file', function(){
+ before(done => galleryMock.call(this, done))
+ it('should respond with status 400', done => {
+ request.post(`${url}/api/gallery/${this.tempGallery._id}/pic`)
+ .set({Authorization: `Bearer ${this.tempToken}`})
+ .field('desc', examplePic.desc)
+ .field('name', examplePic.name)
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+
+ describe('with invalid token', function(){
+ before(done => galleryMock.call(this, done))
+ it('should respond with status 401', done => {
+ request.post(`${url}/api/gallery/${this.tempGallery._id}/pic`)
+ .set({Authorization: `Bearer ${this.tempToken}bad`})
+ .field('desc', examplePic.desc)
+ .field('name', examplePic.name)
+ .attach('file', examplePic.file)
+ .end((err, res) => {
+ expect(res.status).to.equal(401)
+ expect(res.text).to.equal('UnauthorizedError')
+ done()
+ })
+ })
+ })
+
+ describe('with invalid galleryID', function(){
+ before(done => galleryMock.call(this, done))
+ it('should respond with status 404', done => {
+ request.post(`${url}/api/gallery/${this.tempGallery._id}bad/pic`)
+ .set({Authorization: `Bearer ${this.tempToken}`})
+ .field('desc', examplePic.desc)
+ .field('name', examplePic.name)
+ .attach('file', examplePic.file)
+ .end((err, res) => {
+ expect(res.status).to.equal(404)
+ expect(res.text).to.equal('NotFoundError')
+ done()
+ })
+ })
+ })
+ })
+
+ describe('testing DELETE /api/gallery/:gallryID/pic/:picID', function(){
+ describe('with valid token and ids', function(){
+ before(done => picMock.call(this, done))
+
+ it('should respond with status 204', done => {
+ request.delete(`${url}/api/gallery/${this.tempGallery._id}/pic/${this.tempPic._id}`)
+ .set({Authorization: `Bearer ${this.tempToken}`})
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.status).to.equal(204)
+ done()
+ })
+ })
+ })
+
+ describe('with invalid token', function(){
+ before(done => picMock.call(this, done))
+ it('should respond with status 401', done => {
+ request.delete(`${url}/api/gallery/${this.tempGallery._id}/pic/${this.tempPic._id}`)
+ .set({Authorization: `Bearer ${this.tempToken}bad`})
+ .end((err, res) => {
+ expect(res.status).to.equal(401)
+ expect(res.text).to.equal('UnauthorizedError')
+ done()
+ })
+ })
+ })
+
+ describe('should resond with 401', function(){
+ before(done => picMock.call(this, done))
+ before(done => userMock.call(this, done))
+
+ it('should respond with status 401', done => {
+ request.delete(`${url}/api/gallery/${this.tempGallery._id}/pic/${this.tempPic._id}`)
+ .set({Authorization: `Bearer ${this.tempToken}`})
+ .end((err, res) => {
+ expect(res.status).to.equal(401)
+ done()
+ })
+ })
+ })
+
+ describe('no auth header', function(){
+ before(done => picMock.call(this, done))
+ it('should respond with status 400', done => {
+ request.delete(`${url}/api/gallery/${this.tempGallery._id}/pic/${this.tempPic._id}`)
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+
+ describe('with no bearer auth', function(){
+ before(done => picMock.call(this, done))
+ it('should respond with status 400', done => {
+ request.delete(`${url}/api/gallery/${this.tempGallery._id}/pic/${this.tempPic._id}`)
+ .set({Authorization: 'lul this is bad'})
+ .end((err, res) => {
+ expect(res.status).to.equal(400)
+ expect(res.text).to.equal('BadRequestError')
+ done()
+ })
+ })
+ })
+
+ describe('with invalid galleryID', function(){
+ before(done => picMock.call(this, done))
+ it('should respond with status 404', done => {
+ request.delete(`${url}/api/gallery/${this.tempGallery._id}bad/pic/${this.tempPic._id}`)
+ .set({Authorization: `Bearer ${this.tempToken}`})
+ .end((err, res) => {
+ expect(res.status).to.equal(404)
+ expect(res.text).to.equal('NotFoundError')
+ done()
+ })
+ })
+ })
+
+ describe('with invalid picID', function(){
+ before(done => picMock.call(this, done))
+ it('should respond with status 404', done => {
+ request.delete(`${url}/api/gallery/${this.tempGallery._id}/pic/${this.tempPic._id}bad`)
+ .set({Authorization: `Bearer ${this.tempToken}`})
+ .end((err, res) => {
+ expect(res.status).to.equal(404)
+ expect(res.text).to.equal('NotFoundError')
+ done()
+ })
+ })
+ })
+ })
+
+ describe('testing GET /api/public/pic', function(){
+ describe('with valid request', function(){
+ before(done => mockManyPics.call(this, 100, done))
+ it ('should return an array of pics', done => {
+ request.get(`${url}/api/public/pic`)
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ expect(res.body.length).to.equal(50)
+ for(let i=0; i mockManyPics.call(this, 100, done))
+ it ('should return an array of pics', done => {
+ request.get(`${url}/api/public/pic?name=do`)
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ let fuzzy = fuzzyRegex('do')
+ for(let i=0; i mockManyPics.call(this, 50, done))
+ it ('should return an array of pics', done => {
+ request.get(`${url}/api/public/pic?desc=lorem`)
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ let fuzzy = fuzzyRegex('lorem')
+ for(let i=0; i mockManyPics.call(this, 50, done))
+ it ('should return an array of pics', done => {
+ request.get(`${url}/api/public/pic?desc=lorem%20ip`)
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ let fuzzy = fuzzyRegex('lorem ip')
+ for(let i=0; i mockManyPics.call(this, 100, done))
+ it ('should return an array of pics', done => {
+ request.get(`${url}/api/public/pic?desc=lorem&name=do`)
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ let fuzzyName = fuzzyRegex('do')
+ let fuzzyDesc = fuzzyRegex('lo')
+ for(let i=0; i mockManyEverything.call(this, options, done))
+ //before(function(done){
+ //this.timeout(5000)
+ //mockManyEverything.call(this, 20, function(err){
+ //if(err) return done(err)
+ //done()
+ //})
+ //})
+
+ it ('should return an array of pics', done => {
+ request.get(`${url}/api/public/pic?username=lop`)
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ let fuzzyuser = fuzzyRegex('lo')
+ console.log('pics in response', res.body.length)
+ for(let i=0; i mockManyPics.call(this, 100, done))
+ it ('should return an array of pics', done => {
+ request.get(`${url}/api/pic`)
+ .set({Authorization: `Bearer ${this.tempToken}`})
+ .end((err, res) => {
+ if (err)
+ return done(err)
+ expect(res.status).to.equal(200)
+ expect(Array.isArray(res.body)).to.equal(true)
+ for(let i=0; i mockManyPics.call(this, 100, done))
+ it ('should return an array of pics', done => {
+ request.get(`${url}/api/pic`)
+ .set({Authorization: `Bearer ${this.tempToken}bad`})
+ .end((err, res) => {
+ expect(res.status).to.equal(401)
+ done()
+ })
+ })
+ })
+
+
+ describe('with ?name=do', function(){
+ before(done => mockManyPics.call(this, 100, done))
+ it ('should return an array of pics', done => {
+ request.get(`${url}/api/pic?name=do`)
+ .set({Authorization: `Bearer ${this.tempToken}`})
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ let fuzzyName = fuzzyRegex('do')
+ for(let i=0; i mockManyPics.call(this, 10, done))
+ it ('should return an array of pics', done => {
+ request.get(`${url}/api/pic?desc=do`)
+ .set({Authorization: `Bearer ${this.tempToken}`})
+ .end((err, res) => {
+ expect(res.status).to.equal(200)
+ let fuzzyName = fuzzyRegex('do')
+ for(let i=0; i {
+ let params = {
+ Bucket: process.env.AWS_BUCKET,
+ Key: 'wat',
+ Body: 'lul',
+ ACL: 'public-read',
+ }
+
+ s3UploadPromise(params)
+ .then(data => {
+ let uploadMock = awsMocks.uploadMock
+ expect(data.ETag).to.equal(uploadMock.ETag)
+ expect(data.Location).to.equal(uploadMock.Location)
+ expect(data.Key).to.equal(uploadMock.Key)
+ done()
+ })
+ .catch(done)
+ })
+ })
+
+ describe('with no ACL', function(){
+ it('should return an error', done => {
+ let params = {
+ Bucket: process.env.AWS_BUCKET,
+ Key: 'wat',
+ Body: 'lul',
+ }
+
+ s3UploadPromise(params)
+ .then(done)
+ .catch(err => {
+ expect(err.message).to.equal('ACL must be public read')
+ done()
+ })
+ })
+ })
+
+ describe('with with no key', function(){
+ it('should return an aws response', done => {
+ let params = {
+ Bucket: process.env.AWS_BUCKET,
+ Body: 'lul',
+ ACL: 'public-read',
+ }
+
+ s3UploadPromise(params)
+ .then(done)
+ .catch(err => {
+ expect(err.message).to.equal('requres Key')
+ done()
+ })
+ })
+ })
+
+ describe('with with no body', function(){
+ it('should return an aws response', done => {
+ let params = {
+ Bucket: process.env.AWS_BUCKET,
+ Key: 'wat',
+ ACL: 'public-read',
+ }
+
+ s3UploadPromise(params)
+ .then(done)
+ .catch(err => {
+ expect(err.message).to.equal('requires body')
+ done()
+ })
+ })
+ })
+})
+
+
diff --git a/lab-raziyeh/webpack.config.js b/lab-raziyeh/webpack.config.js
new file mode 100644
index 0000000..a673069
--- /dev/null
+++ b/lab-raziyeh/webpack.config.js
@@ -0,0 +1,74 @@
+'use strict';
+
+require('dotenv').load({path: `${__dirname}/.client.env`});
+// if (!process.env.API_URL || !process.env.NODE_ENV || !process.env.TITLE){
+// process.exit(1);
+// }
+
+const webpack = require('webpack');
+const HTMLPlugin = require('html-webpack-plugin');
+const CleanPlugin = require('clean-webpack-plugin');
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
+
+const production = process.env.NODE_ENV === 'production';
+
+let plugins = [
+ new ExtractTextPlugin('bundle.css'),
+ new HTMLPlugin({ template: `${__dirname}/app/index.html` }),
+ new webpack.DefinePlugin({
+ __API_URL__: JSON.stringify(process.env.API_URL),
+ __GOOGLE_CLIENT_ID__: JSON.stringify(process.env.GOOGLE_CLIENT_ID),
+ __TITLE__: JSON.stringify(process.env.TITLE),
+ __DEBUG__: JSON.stringify(!production),
+ }),
+];
+
+if (production){
+ plugins = plugins.concat([
+ new webpack.optimize.UglifyJsPlugin({
+ mangle: true,
+ compress: {
+ warnings: false,
+ },
+ }),
+ new CleanPlugin(),
+ ]);
+}
+
+module.exports = {
+ entry: `${__dirname}/app/entry.js`,
+ devtool: production ? false : 'eval',
+ plugins,
+ output: {
+ path: 'build',
+ filename: 'bundle.js',
+ },
+ sassLoader: {
+ includePaths: [`${__dirname}/app/scss/lib`],
+ },
+ module: {
+ loaders: [
+ {
+ test: /\.js$/,
+ exclude: /node_modules/,
+ loader: 'babel',
+ },
+ {
+ test: /\.html$/,
+ loader: 'html',
+ },
+ {
+ test: /\.(woff|ttf|svg|eot).*/,
+ loader: 'url?limit=10000&name=font/[name].[ext]',
+ },
+ {
+ test: /\.(jpg|jpeg|bmp|tiff|gif|png)$/,
+ loader: 'url?limit=10000&name=image/[hash].[ext]',
+ },
+ {
+ test: /\.scss$/,
+ loader: ExtractTextPlugin.extract('style', 'css!resolve-url!sass?sourceMap'),
+ },
+ ],
+ },
+};