VYPR
Moderate severityNVD Advisory· Published Feb 20, 2024· Updated Apr 24, 2025

Decidim's devise_invitable gem vulnerable to circumvention of invitation token expiry period

CVE-2023-48220

Description

Decidim is a participatory democracy framework. Starting in version 0.4.rc3 and prior to version 2.0.9 of the devise_invitable gem, the invites feature allows users to accept the invitation for an unlimited amount of time through the password reset functionality. This issue creates vulnerable dependencies starting in version 0.0.1.alpha3 and prior to versions 0.26.9, 0.27.5, and 0.28.0 of the decidim, decidim-admin, and decidim-system gems. When using the password reset functionality, the devise_invitable gem always accepts the pending invitation if the user has been invited. The only check done is if the user has been invited but the code does not ensure that the pending invitation is still valid as defined by the invite_for expiry period. Decidim sets this configuration to 2.weeks so this configuration should be respected. The bug is in the devise_invitable gem and should be fixed there and the dependency should be upgraded in Decidim once the fix becomes available. devise_invitable to version 2.0.9 and above fix this issue. Versions 0.26.9, 0.27.5, and 0.28.0 of the decidim, decidim-admin, and decidim-system gems contain this fix. As a workaround, invitations can be cancelled directly from the database.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
decidimRubyGems
>= 0.0.1.alpha3, < 0.26.90.26.9
decidim-adminRubyGems
>= 0.0.1.alpha3, < 0.26.90.26.9
decidim-systemRubyGems
>= 0.0.1.alpha3, < 0.26.90.26.9
devise_invitableRubyGems
>= 0.4.rc3, < 2.0.92.0.9
decidimRubyGems
>= 0.27.0, < 0.27.50.27.5
decidim-adminRubyGems
>= 0.27.0, < 0.27.50.27.5
decidim-systemRubyGems
>= 0.27.0, < 0.27.50.27.5

Affected products

1

Patches

3
073e60e2e422

Add Organization admin engine

https://github.com/decidim/decidimOriol GualSep 26, 2016via ghsa
81 files changed · +2315 16
  • decidim-admin/app/assets/config/decidim_admin_manifest.js+2 0 added
    @@ -0,0 +1,2 @@
    +//= link_directory ../javascripts/decidim/admin.js
    +//= link_directory ../stylesheets/decidim/admin.css
    
  • decidim-admin/app/assets/images/decidim/admin/.keep+0 0 added
  • decidim-admin/app/assets/javascripts/decidim/admin.js+21 0 added
    @@ -0,0 +1,21 @@
    +// This is a manifest file that'll be compiled into application.js, which will include all the files
    +// listed below.
    +//
    +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
    +// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
    +//
    +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
    +// compiled file. JavaScript code in this file should be added after the last require_* statement.
    +//
    +// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
    +// about supported directives.
    +//
    +//= require jquery
    +//= require jquery.turbolinks
    +//= require jquery_ujs
    +//= require foundation
    +//= require_self
    +
    +$(function(){ $(document).foundation(); });
    +
    +//= require turbolinks
    
  • decidim-admin/app/assets/stylesheets/decidim/admin/_actions.scss+8 0 added
    @@ -0,0 +1,8 @@
    +.actions{
    +  text-align: right;
    +
    +  &.title{
    +    margin-bottom: $global-margin;
    +    font-size: 1.2em;
    +  }
    +}
    \ No newline at end of file
    
  • decidim-admin/app/assets/stylesheets/decidim/admin/_foundation_and_overrides.scss+52 0 added
    @@ -0,0 +1,52 @@
    +@charset "utf-8";
    +
    +@import "settings";
    +@import "foundation";
    +
    +// If you'd like to include motion-ui the foundation-rails gem comes prepackaged with it, uncomment the 3 @imports, if you are not using the gem you need to install the motion-ui sass package.
    +//
    +// @import 'motion-ui/motion-ui';
    +
    +// We include everything by default.  To slim your CSS, remove components you don't use.
    +
    +@include foundation-global-styles;
    +@include foundation-flex-grid;
    +@include foundation-typography;
    +@include foundation-button;
    +@include foundation-forms;
    +@include foundation-visibility-classes;
    +@include foundation-flex-classes;
    +@include foundation-accordion;
    +@include foundation-accordion-menu;
    +@include foundation-badge;
    +@include foundation-breadcrumbs;
    +@include foundation-button-group;
    +@include foundation-callout;
    +@include foundation-close-button;
    +@include foundation-drilldown-menu;
    +@include foundation-dropdown;
    +@include foundation-dropdown-menu;
    +@include foundation-flex-video;
    +@include foundation-label;
    +@include foundation-media-object;
    +@include foundation-menu;
    +@include foundation-menu-icon;
    +@include foundation-off-canvas;
    +@include foundation-orbit;
    +@include foundation-pagination;
    +@include foundation-progress-bar;
    +@include foundation-slider;
    +@include foundation-sticky;
    +@include foundation-reveal;
    +@include foundation-switch;
    +@include foundation-table;
    +@include foundation-tabs;
    +@include foundation-thumbnail;
    +@include foundation-title-bar;
    +@include foundation-tooltip;
    +@include foundation-top-bar;
    +
    +// If you'd like to include motion-ui the foundation-rails gem comes prepackaged with it, uncomment the 3 @imports, if you are not using the gem you need to install the motion-ui sass package.
    +//
    +// @include motion-ui-transitions;
    +// @include motion-ui-animations;
    
  • decidim-admin/app/assets/stylesheets/decidim/admin/_layout.scss+16 0 added
    @@ -0,0 +1,16 @@
    +.off-canvas, .sidebar, .off-canvas-wrapper{
    +  min-height: 100vh;
    +}
    +
    +.off-canvas{
    +  color: #fff;
    +}
    +
    +.main-content{
    +  padding: $global-padding 0;
    +  min-height: 100vh;
    +}
    +
    +.page-title{
    +  margin-bottom: $global-margin;
    +}
    \ No newline at end of file
    
  • decidim-admin/app/assets/stylesheets/decidim/admin/_login.scss+36 0 added
    @@ -0,0 +1,36 @@
    +body.login{
    +  background: $light-gray;
    +  position: relative;
    +
    +  .login-form-wrapper{
    +    @include grid-row;
    +    @include flex;
    +
    +    height: 100vh;
    +
    +    .login-form{
    +      @include grid-column(12);
    +      @include grid-column-position(center);
    +
    +      @include breakpoint(medium){
    +        @include grid-column(8);
    +        @include grid-column-position(center);
    +      }
    +
    +      @include breakpoint(large){
    +        @include grid-column(6);
    +        @include grid-column-position(center);
    +      }
    +    }
    +
    +    @include breakpoint(medium){
    +      @include flex-align(center, middle);
    +      transform: translateY(-8%);
    +    }
    +
    +    .login-form-inner{
    +      background-color: $white;
    +      padding: $global-padding;
    +    }
    +  }
    +}
    \ No newline at end of file
    
  • decidim-admin/app/assets/stylesheets/decidim/admin.scss+8 0 added
    @@ -0,0 +1,8 @@
    +@import 'https://fonts.googleapis.com/css?family=Montserrat:400,700';
    +
    +@import "admin/foundation_and_overrides";
    +@import "admin/layout";
    +@import "admin/login";
    +@import "admin/sidebar";
    +@import "admin/tables";
    +@import "admin/actions";
    
  • decidim-admin/app/assets/stylesheets/decidim/admin/_settings.scss+566 0 added
    @@ -0,0 +1,566 @@
    +//  Foundation for Sites Settings
    +//  -----------------------------
    +//
    +//  Table of Contents:
    +//
    +//   1. Global
    +//   2. Breakpoints
    +//   3. The Grid
    +//   4. Base Typography
    +//   5. Typography Helpers
    +//   6. Abide
    +//   7. Accordion
    +//   8. Accordion Menu
    +//   9. Badge
    +//  10. Breadcrumbs
    +//  11. Button
    +//  12. Button Group
    +//  13. Callout
    +//  14. Close Button
    +//  15. Drilldown
    +//  16. Dropdown
    +//  17. Dropdown Menu
    +//  18. Flex Video
    +//  19. Forms
    +//  20. Label
    +//  21. Media Object
    +//  22. Menu
    +//  23. Meter
    +//  24. Off-canvas
    +//  25. Orbit
    +//  26. Pagination
    +//  27. Progress Bar
    +//  28. Reveal
    +//  29. Slider
    +//  30. Switch
    +//  31. Table
    +//  32. Tabs
    +//  33. Thumbnail
    +//  34. Title Bar
    +//  35. Tooltip
    +//  36. Top Bar
    +
    +@import "util/util";
    +
    +// 1. Global
    +// ---------
    +
    +// $global-font-size: 100%;
    +// $global-width: rem-calc(1200);
    +// $global-lineheight: 1.5;
    +$foundation-palette: (
    +  primary: #2199e8,
    +  secondary: #777,
    +  success: #3adb76,
    +  warning: #ffae00,
    +  alert: #ec5840,
    +);
    +$light-gray: #e6e6e6;
    +// $medium-gray: #cacaca;
    +// $dark-gray: #8a8a8a;
    +$black: #232323;
    +// $white: #fefefe;
    +// $body-background: $white;
    +// $body-font-color: $black;
    +$body-font-family: 'Montserrat', sans-serif;
    +// $body-antialiased: true;
    +// $global-margin: 1rem;
    +// $global-padding: 1rem;
    +// $global-weight-normal: normal;
    +// $global-weight-bold: bold;
    +// $global-radius: 0;
    +// $global-text-direction: ltr;
    +$global-flexbox: true;
    +// $print-transparent-backgrounds: true;
    +
    +@include add-foundation-colors;
    +
    +// 2. Breakpoints
    +// --------------
    +
    +// $breakpoints: (
    +//   small: 0,
    +//   medium: 640px,
    +//   large: 1024px,
    +//   xlarge: 1200px,
    +//   xxlarge: 1440px,
    +// );
    +// $breakpoint-classes: (small medium large);
    +
    +// 3. The Grid
    +// -----------
    +
    +// $grid-row-width: $global-width;
    +// $grid-column-count: 12;
    +// $grid-column-gutter: (
    +//   small: 20px,
    +//   medium: 30px,
    +// );
    +// $grid-column-align-edge: true;
    +// $block-grid-max: 8;
    +
    +// 4. Base Typography
    +// ------------------
    +
    +// $header-font-family: $body-font-family;
    +// $header-font-weight: $global-weight-normal;
    +// $header-font-style: normal;
    +// $font-family-monospace: Consolas, 'Liberation Mono', Courier, monospace;
    +// $header-sizes: (
    +//   small: (
    +//     'h1': 24,
    +//     'h2': 20,
    +//     'h3': 19,
    +//     'h4': 18,
    +//     'h5': 17,
    +//     'h6': 16,
    +//   ),
    +//   medium: (
    +//     'h1': 48,
    +//     'h2': 40,
    +//     'h3': 31,
    +//     'h4': 25,
    +//     'h5': 20,
    +//     'h6': 16,
    +//   ),
    +// );
    +// $header-color: inherit;
    +// $header-lineheight: 1.4;
    +// $header-margin-bottom: 0.5rem;
    +// $header-text-rendering: optimizeLegibility;
    +// $small-font-size: 80%;
    +// $header-small-font-color: $medium-gray;
    +// $paragraph-lineheight: 1.6;
    +// $paragraph-margin-bottom: 1rem;
    +// $paragraph-text-rendering: optimizeLegibility;
    +// $code-color: $black;
    +// $code-font-family: $font-family-monospace;
    +// $code-font-weight: $global-weight-normal;
    +// $code-background: $light-gray;
    +// $code-border: 1px solid $medium-gray;
    +// $code-padding: rem-calc(2 5 1);
    +// $anchor-color: $primary-color;
    +// $anchor-color-hover: scale-color($anchor-color, $lightness: -14%);
    +// $anchor-text-decoration: none;
    +// $anchor-text-decoration-hover: none;
    +// $hr-width: $global-width;
    +// $hr-border: 1px solid $medium-gray;
    +// $hr-margin: rem-calc(20) auto;
    +// $list-lineheight: $paragraph-lineheight;
    +// $list-margin-bottom: $paragraph-margin-bottom;
    +// $list-style-type: disc;
    +// $list-style-position: outside;
    +// $list-side-margin: 1.25rem;
    +// $list-nested-side-margin: 1.25rem;
    +// $defnlist-margin-bottom: 1rem;
    +// $defnlist-term-weight: $global-weight-bold;
    +// $defnlist-term-margin-bottom: 0.3rem;
    +// $blockquote-color: $dark-gray;
    +// $blockquote-padding: rem-calc(9 20 0 19);
    +// $blockquote-border: 1px solid $medium-gray;
    +// $cite-font-size: rem-calc(13);
    +// $cite-color: $dark-gray;
    +// $keystroke-font: $font-family-monospace;
    +// $keystroke-color: $black;
    +// $keystroke-background: $light-gray;
    +// $keystroke-padding: rem-calc(2 4 0);
    +// $keystroke-radius: $global-radius;
    +// $abbr-underline: 1px dotted $black;
    +
    +// 5. Typography Helpers
    +// ---------------------
    +
    +// $lead-font-size: $global-font-size * 1.25;
    +// $lead-lineheight: 1.6;
    +// $subheader-lineheight: 1.4;
    +// $subheader-color: $dark-gray;
    +// $subheader-font-weight: $global-weight-normal;
    +// $subheader-margin-t#23262Cop: 0.2rem;
    +// $subheader-margin-bottom: 0.5rem;
    +// $stat-font-size: 2.5rem;
    +
    +// 6. Abide
    +// --------
    +
    +// $abide-inputs: true;
    +// $abide-labels: true;
    +// $input-background-invalid: map-get($foundation-palette, alert);
    +// $form-label-color-invalid: map-get($foundation-palette, alert);
    +// $input-error-color: map-get($foundation-palette, alert);
    +// $input-error-font-size: rem-calc(12);
    +// $input-error-font-weight: $global-weight-bold;
    +
    +// 7. Accordion
    +// ------------
    +
    +// $accordion-background: $white;
    +// $accordion-plusminus: true;
    +// $accordion-item-color: foreground($accordion-background, $primary-color);
    +// $accordion-item-background-hover: $light-gray;
    +// $accordion-item-padding: 1.25rem 1rem;
    +// $accordion-content-background: $white;
    +// $accordion-content-border: 1px solid $light-gray;
    +// $accordion-content-color: foreground($accordion-content-background, $body-font-color);
    +// $accordion-content-padding: 1rem;
    +
    +// 8. Accordion Menu
    +// -----------------
    +
    +// $accordionmenu-arrows: true;
    +// $accordionmenu-arrow-color: $primary-color;
    +
    +// 9. Badge
    +// --------
    +
    +// $badge-background: $primary-color;
    +// $badge-color: foreground($badge-background);
    +// $badge-padding: 0.3em;
    +// $badge-minwidth: 2.1em;
    +// $badge-font-size: 0.6rem;
    +
    +// 10. Breadcrumbs
    +// ---------------
    +
    +// $breadcrumbs-margin: 0 0 $global-margin 0;
    +// $breadcrumbs-item-font-size: rem-calc(11);
    +// $breadcrumbs-item-color: $primary-color;
    +// $breadcrumbs-item-color-current: $black;
    +// $breadcrumbs-item-color-disabled: $medium-gray;
    +// $breadcrumbs-item-margin: 0.75rem;
    +// $breadcrumbs-item-uppercase: true;
    +// $breadcrumbs-item-slash: true;
    +
    +// 11. Button
    +// ----------
    +
    +// $button-padding: 0.85em 1em;
    +$button-margin: 0;
    +// $button-fill: solid;
    +// $button-background: $primary-color;
    +// $button-background-hover: scale-color($button-background, $lightness: -15%);
    +// $button-color: $white;
    +// $button-color-alt: $black;
    +// $button-radius: $global-radius;
    +// $button-sizes: (
    +//   tiny: 0.6rem,
    +//   small: 0.75rem,
    +//   default: 0.9rem,
    +//   large: 1.25rem,
    +// );
    +// $button-opacity-disabled: 0.25;
    +
    +// 12. Button Group
    +// ----------------
    +
    +// $buttongroup-margin: 1rem;
    +// $buttongroup-spacing: 1px;
    +// $buttongroup-child-selector: ".button";
    +// $buttongroup-expand-max: 6;
    +
    +// 13. Callout
    +// -----------
    +
    +// $callout-background: $white;
    +// $callout-background-fade: 85%;
    +// $callout-border: 1px solid rgba($black, 0.25);
    +// $callout-margin: 0 0 1rem 0;
    +// $callout-padding: 1rem;
    +// $callout-font-color: $body-font-color;
    +// $callout-font-color-alt: $body-background;
    +// $callout-radius: $global-radius;
    +// $callout-link-tint: 30%;
    +
    +// 14. Close Button
    +// ----------------
    +
    +// $closebutton-position: right top;
    +// $closebutton-offset-horizontal: 1rem;
    +// $closebutton-offset-vertical: 0.5rem;
    +// $closebutton-size: 2em;
    +// $closebutton-lineheight: 1;
    +// $closebutton-color: $dark-gray;
    +// $closebutton-color-hover: $black;
    +
    +// 15. Drilldown
    +// -------------
    +
    +// $drilldown-transition: transform 0.15s linear;
    +// $drilldown-arrows: true;
    +// $drilldown-arrow-color: $primary-color;
    +// $drilldown-background: $white;
    +
    +// 16. Dropdown
    +// ------------
    +
    +// $dropdown-padding: 1rem;
    +// $dropdown-border: 1px solid $medium-gray;
    +// $dropdown-font-size: 1rem;
    +// $dropdown-width: 300px;
    +// $dropdown-radius: $global-radius;
    +// $dropdown-sizes: (
    +//   tiny: 100px,
    +//   small: 200px,
    +//   large: 400px,
    +// );
    +
    +// 17. Dropdown Menu
    +// -----------------
    +
    +// $dropdownmenu-arrows: true;
    +// $dropdownmenu-arrow-color: $anchor-color;
    +// $dropdownmenu-min-width: 200px;
    +// $dropdownmenu-background: $white;
    +// $dropdownmenu-border: 1px solid $medium-gray;
    +
    +// 18. Flex Video
    +// --------------
    +
    +// $flexvideo-margin-bottom: rem-calc(16);
    +// $flexvideo-ratio: 4 by 3;
    +// $flexvideo-ratio-widescreen: 16 by 9;
    +
    +// 19. Forms
    +// ---------
    +
    +// $fieldset-border: 1px solid $medium-gray;
    +// $fieldset-padding: rem-calc(20);
    +// $fieldset-margin: rem-calc(18 0);
    +// $legend-padding: rem-calc(0 3);
    +// $form-spacing: rem-calc(16);
    +// $helptext-color: $black;
    +// $helptext-font-size: rem-calc(13);
    +// $helptext-font-style: italic;
    +// $input-prefix-color: $black;
    +// $input-prefix-background: $light-gray;
    +// $input-prefix-border: 1px solid $medium-gray;
    +// $input-prefix-padding: 1rem;
    +// $form-label-color: $black;
    +// $form-label-font-size: rem-calc(14);
    +// $form-label-font-weight: $global-weight-normal;
    +// $form-label-line-height: 1.8;
    +// $select-background: $white;
    +// $select-triangle-color: $dark-gray;
    +// $select-radius: $global-radius;
    +// $input-color: $black;
    +// $input-placeholder-color: $medium-gray;
    +// $input-font-family: inherit;
    +// $input-font-size: rem-calc(16);
    +// $input-background: $white;
    +// $input-background-focus: $white;
    +// $input-background-disabled: $light-gray;
    +// $input-border: 1px solid $medium-gray;
    +// $input-border-focus: 1px solid $dark-gray;
    +// $input-shadow: inset 0 1px 2px rgba($black, 0.1);
    +// $input-shadow-focus: 0 0 5px $medium-gray;
    +// $input-cursor-disabled: not-allowed;
    +// $input-transition: box-shadow 0.5s, border-color 0.25s ease-in-out;
    +// $input-number-spinners: true;
    +// $input-radius: $global-radius;
    +
    +// 20. Label
    +// ---------
    +
    +// $label-background: $primary-color;
    +// $label-color: foreground($label-background);
    +// $label-font-size: 0.8rem;
    +// $label-padding: 0.33333rem 0.5rem;
    +// $label-radius: $global-radius;
    +
    +// 21. Media Object
    +// ----------------
    +
    +// $mediaobject-margin-bottom: $global-margin;
    +// $mediaobject-section-padding: $global-padding;
    +// $mediaobject-image-width-stacked: 100%;
    +
    +// 22. Menu
    +// --------
    +
    +// $menu-margin: 0;
    +// $menu-margin-nested: 1rem;
    +// $menu-item-padding: 0.7rem 1rem;
    +// $menu-item-color-active: $white;
    +// $menu-item-background-active: map-get($foundation-palette, primary);
    +// $menu-icon-spacing: 0.25rem;
    +
    +// 23. Meter
    +// ---------
    +
    +// $meter-height: 1rem;
    +// $meter-radius: $global-radius;
    +// $meter-background: $medium-gray;
    +// $meter-fill-good: $success-color;
    +// $meter-fill-medium: $warning-color;
    +// $meter-fill-bad: $alert-color;
    +
    +// 24. Off-canvas
    +// --------------
    +
    +// $offcanvas-size: 250px;
    +$offcanvas-background: $black;
    +// $offcanvas-zindex: -1;
    +// $offcanvas-transition-length: 0.5s;
    +// $offcanvas-transition-timing: ease;
    +// $offcanvas-fixed-reveal: true;
    +// $offcanvas-exit-background: rgba($white, 0.25);
    +// $maincontent-class: "off-canvas-content";
    +$maincontent-shadow: none;
    +
    +// 25. Orbit
    +// ---------
    +
    +// $orbit-bullet-background: $medium-gray;
    +// $orbit-bullet-background-active: $dark-gray;
    +// $orbit-bullet-diameter: 1.2rem;
    +// $orbit-bullet-margin: 0.1rem;
    +// $orbit-bullet-margin-top: 0.8rem;
    +// $orbit-bullet-margin-bottom: 0.8rem;
    +// $orbit-caption-background: rgba($black, 0.5);
    +// $orbit-caption-padding: 1rem;
    +// $orbit-control-background-hover: rgba($black, 0.5);
    +// $orbit-control-padding: 1rem;
    +// $orbit-control-zindex: 10;
    +
    +// 26. Pagination
    +// --------------
    +
    +// $pagination-font-size: rem-calc(14);
    +// $pagination-margin-bottom: $global-margin;
    +// $pagination-item-color: $black;
    +// $pagination-item-padding: rem-calc(3 10);
    +// $pagination-item-spacing: rem-calc(1);
    +// $pagination-radius: $global-radius;
    +// $pagination-item-background-hover: $light-gray;
    +// $pagination-item-background-current: $primary-color;
    +// $pagination-item-color-current: foreground($pagination-item-background-current);
    +// $pagination-item-color-disabled: $medium-gray;
    +// $pagination-ellipsis-color: $black;
    +// $pagination-mobile-items: false;
    +// $pagination-arrows: true;
    +
    +// 27. Progress Bar
    +// ----------------
    +
    +// $progress-height: 1rem;
    +// $progress-background: $medium-gray;
    +// $progress-margin-bottom: $global-margin;
    +// $progress-meter-background: $primary-color;
    +// $progress-radius: $global-radius;
    +
    +// 28. Reveal
    +// ----------
    +
    +// $reveal-background: $white;
    +// $reveal-width: 600px;
    +// $reveal-max-width: $global-width;
    +// $reveal-padding: $global-padding;
    +// $reveal-border: 1px solid $medium-gray;
    +// $reveal-radius: $global-radius;
    +// $reveal-zindex: 1005;
    +// $reveal-overlay-background: rgba($black, 0.45);
    +
    +// 29. Slider
    +// ----------
    +
    +// $slider-width-vertical: 0.5rem;
    +// $slider-transition: all 0.2s ease-in-out;
    +// $slider-height: 0.5rem;
    +// $slider-background: $light-gray;
    +// $slider-fill-background: $medium-gray;
    +// $slider-handle-height: 1.4rem;
    +// $slider-handle-width: 1.4rem;
    +// $slider-handle-background: $primary-color;
    +// $slider-opacity-disabled: 0.25;
    +// $slider-radius: $global-radius;
    +
    +// 30. Switch
    +// ----------
    +
    +// $switch-background: $medium-gray;
    +// $switch-background-active: $primary-color;
    +// $switch-height: 2rem;
    +// $switch-height-tiny: 1.5rem;
    +// $switch-height-small: 1.75rem;
    +// $switch-height-large: 2.5rem;
    +// $switch-radius: $global-radius;
    +// $switch-margin: $global-margin;
    +// $switch-paddle-background: $white;
    +// $switch-paddle-offset: 0.25rem;
    +// $switch-paddle-radius: $global-radius;
    +// $switch-paddle-transition: all 0.25s ease-out;
    +
    +// 31. Table
    +// ---------
    +
    +// $table-background: $white;
    +// $table-color-scale: 5%;
    +// $table-border: 1px solid smart-scale($table-background, $table-color-scale);
    +// $table-padding: rem-calc(8 10 10);
    +// $table-hover-scale: 2%;
    +// $table-row-hover: darken($table-background, $table-hover-scale);
    +// $table-row-stripe-hover: darken($table-background, $table-color-scale + $table-hover-scale);
    +// $table-striped-background: smart-scale($table-background, $table-color-scale);
    +// $table-stripe: even;
    +// $table-head-background: smart-scale($table-background, $table-color-scale / 2);
    +// $table-foot-background: smart-scale($table-background, $table-color-scale);
    +// $table-head-font-color: $body-font-color;
    +// $show-header-for-stacked: false;
    +
    +// 32. Tabs
    +// --------
    +
    +// $tab-margin: 0;
    +// $tab-background: $white;
    +// $tab-background-active: $light-gray;
    +// $tab-item-font-size: rem-calc(12);
    +// $tab-item-background-hover: $white;
    +// $tab-item-padding: 1.25rem 1.5rem;
    +// $tab-expand-max: 6;
    +// $tab-content-background: $white;
    +// $tab-content-border: $light-gray;
    +// $tab-content-color: foreground($tab-background, $primary-color);
    +// $tab-content-padding: 1rem;
    +
    +// 33. Thumbnail
    +// -------------
    +
    +// $thumbnail-border: solid 4px $white;
    +// $thumbnail-margin-bottom: $global-margin;
    +// $thumbnail-shadow: 0 0 0 1px rgba($black, 0.2);
    +// $thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5);
    +// $thumbnail-transition: box-shadow 200ms ease-out;
    +// $thumbnail-radius: $global-radius;
    +
    +// 34. Title Bar
    +// -------------
    +
    +$titlebar-background: map-get($foundation-palette, primary);
    +// $titlebar-color: $white;
    +// $titlebar-padding: 0.5rem;
    +// $titlebar-text-font-weight: bold;
    +// $titlebar-icon-color: $white;
    +// $titlebar-icon-color-hover: $medium-gray;
    +// $titlebar-icon-spacing: 0.25rem;
    +
    +// 35. Tooltip
    +// -----------
    +
    +// $has-tip-font-weight: $global-weight-bold;
    +// $has-tip-border-bottom: dotted 1px $dark-gray;
    +// $tooltip-background-color: $black;
    +// $tooltip-color: $white;
    +// $tooltip-padding: 0.75rem;
    +// $tooltip-font-size: $small-font-size;
    +// $tooltip-pip-width: 0.75rem;
    +// $tooltip-pip-height: $tooltip-pip-width * 0.866;
    +// $tooltip-radius: $global-radius;
    +
    +// 36. Top Bar
    +// -----------
    +
    +// $topbar-padding: 0.5rem;
    +// $topbar-background: $light-gray;
    +// $topbar-submenu-background: $topbar-background;
    +// $topbar-title-spacing: 1rem;
    +// $topbar-input-width: 200px;
    +// $topbar-unstack-breakpoint: medium;
    
  • decidim-admin/app/assets/stylesheets/decidim/admin/_sidebar.scss+73 0 added
    @@ -0,0 +1,73 @@
    +@mixin menu-title{
    +  text-transform: uppercase;
    +  letter-spacing: 5px;
    +}
    +
    +.title-bar-title{
    +  @include menu-title;
    +}
    +
    +.sidebar{
    +  color: $light-gray;
    +
    +  .title{
    +    background-color: black;
    +    background-color: map-get($foundation-palette, primary);
    +
    +    h1{
    +      margin: 0;
    +      padding: 0;
    +      font-size: rem-calc(18);
    +      font-weight: bold;
    +    }
    +
    +    a{
    +      display: block;
    +      color: inherit;
    +      padding: rem-calc(20) $global-padding;
    +      @include menu-title;
    +    }
    +
    +    @include show-for(large);
    +  }
    +
    +  .main-menu{
    +    border-top: 1px solid black;
    +    border-bottom: 1px solid black;
    +    a{
    +      padding: $global-padding;
    +      display: block;
    +      color: $light-gray;
    +      text-transform: uppercase;
    +      font-weight: bold;
    +      font-size: 0.9em;
    +      border-left: 2px solid transparent;
    +
    +      transition: all 0.3s;
    +
    +      &.active {
    +        border-color: map-get($foundation-palette, primary);
    +        &, &:hover{
    +          background-color: #101010;
    +        }
    +      }
    +
    +      &:hover {
    +        background-color: #1a1a1a;
    +      }
    +    }
    +  }
    +
    +  .session-box {
    +    padding: $global-padding 0;
    +
    +    @include grid-row;
    +
    +    .admin-email {
    +      @include grid-column(8);
    +      text-overflow: ellipsis;
    +      overflow: hidden;
    +    }
    +    .sign-out{ @include grid-column(4); }
    +  }
    +}
    \ No newline at end of file
    
  • decidim-admin/app/assets/stylesheets/decidim/admin/_tables.scss+5 0 added
    @@ -0,0 +1,5 @@
    +table{
    +  td.actions, th.actions{
    +    text-align: right;
    +  }
    +}
    \ No newline at end of file
    
  • decidim-admin/app/constraints/admin/organization_dashboard_constraint.rb+38 0 added
    @@ -0,0 +1,38 @@
    +# frozen_string_literal: true
    +module Admin
    +  # A Rails routes constraint to only allow access to an Organization admin to
    +  # the organization dashboard.
    +  class OrganizationDashboardConstraint
    +    # Initializes the contraint.
    +    #
    +    # request [Rack::Request]
    +    def initialize(request)
    +      @request = request
    +    end
    +
    +    # Checks if the user can access the organization dashboard.
    +    #
    +    # Returns boolean.
    +    def matches?
    +      admin? && belongs_to_organization?
    +    end
    +
    +    private
    +
    +    attr_reader :request
    +
    +    def admin?
    +      user.admin?
    +    end
    +
    +    def belongs_to_organization?
    +      user.organization == request.env["decidim.current_organization"]
    +    end
    +
    +    def user
    +      return unless request.env["warden"].authenticate!(scope: "user")
    +
    +      @user ||= request.env["warden"].user("user")
    +    end
    +  end
    +end
    
  • decidim-admin/app/controllers/decidim/admin/application_controller.rb+9 0 added
    @@ -0,0 +1,9 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module Admin
    +    # The main application controller that inherits from Rails.
    +    class ApplicationController < ActionController::Base
    +      protect_from_forgery with: :exception, prepend: true
    +    end
    +  end
    +end
    
  • decidim-admin/app/controllers/decidim/admin/dashboard_controller.rb+9 0 added
    @@ -0,0 +1,9 @@
    +# frozen_string_literal: true
    +require_dependency "decidim/admin/application_controller"
    +
    +module Decidim
    +  module Admin
    +    class DashboardController < ApplicationController
    +    end
    +  end
    +end
    
  • decidim-admin/app/helpers/decidim/admin/application_helper.rb+12 0 added
    @@ -0,0 +1,12 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module Admin
    +    # Custom helpers, scoped to the admin panel.
    +    #
    +    module ApplicationHelper
    +      def title
    +        "Decidim"
    +      end
    +    end
    +  end
    +end
    
  • decidim-admin/app/jobs/decidim/admin/application_job.rb+9 0 added
    @@ -0,0 +1,9 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module Admin
    +    # Custom ApplicationJob scoped to the admin panel.
    +    #
    +    class ApplicationJob < ActiveJob::Base
    +    end
    +  end
    +end
    
  • decidim-admin/app/mailers/decidim/admin/application_mailer.rb+11 0 added
    @@ -0,0 +1,11 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module Admin
    +    # Custom application mailer, scoped to the admin mailer.
    +    #
    +    class ApplicationMailer < ActionMailer::Base
    +      default from: "from@example.com"
    +      layout "mailer"
    +    end
    +  end
    +end
    
  • decidim-admin/app/models/decidim/admin/application_record.rb+10 0 added
    @@ -0,0 +1,10 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module Admin
    +    # Custom ApplicationRecord scoped to the Admin panel.
    +    #
    +    class ApplicationRecord < ActiveRecord::Base
    +      self.abstract_class = true
    +    end
    +  end
    +end
    
  • decidim-admin/app/views/decidim/admin/dashboard/show.html.erb+3 0 added
    @@ -0,0 +1,3 @@
    +<% content_for :title do %>
    +  <h2><%= t("decidim.admin.titles.dashboard") %></h2>
    +<% end %>
    
  • decidim-admin/app/views/decidim/admin/devise/mailers/password_change.html.erb+3 0 added
    @@ -0,0 +1,3 @@
    +<p>Hello <%= @resource.email %>!</p>
    +
    +<p>We're contacting you to notify you that your password has been changed.</p>
    
  • decidim-admin/app/views/decidim/admin/devise/mailers/reset_password_instructions.html.erb+8 0 added
    @@ -0,0 +1,8 @@
    +<p>Hello <%= @resource.email %>!</p>
    +
    +<p>Someone has requested a link to change your password. You can do this through the link below.</p>
    +
    +<p><%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %></p>
    +
    +<p>If you didn't request this, please ignore this email.</p>
    +<p>Your password won't change until you access the link above and create a new one.</p>
    
  • decidim-admin/app/views/layouts/decidim/admin/application.html.erb+40 0 added
    @@ -0,0 +1,40 @@
    +<!DOCTYPE html>
    +<html>
    +<head>
    +<title>Decidim - admin</title>
    +<%= render partial: 'layouts/decidim/admin/header' %>
    +</head>
    +
    +<body>
    +  <div class="off-canvas-wrapper">
    +    <div class="off-canvas-wrapper-inner" data-off-canvas-wrapper>
    +      <div class="off-canvas position-left reveal-for-large" id="my-info" data-off-canvas data-position="left">
    +        <div class="sidebar">
    +          <%= render partial: 'layouts/decidim/admin/sidebar' %>
    +        </div>
    +      </div>
    +
    +      <div class="off-canvas-content" data-off-canvas-content>
    +        <div class="title-bar hide-for-large">
    +          <div class="title-bar-left">
    +            <button class="menu-icon" type="button" data-open="my-info"></button>
    +            <span class="title-bar-title"><%= title %></span>
    +          </div>
    +        </div>
    +        <div class="row main-content">
    +          <div class="small-12 column">
    +            <% if content_for?(:title) %>
    +              <section class="page-title">
    +                <%= content_for :title %>
    +              </section>
    +            <% end %>
    +
    +            <%= display_flash_messages %>
    +            <%= yield %>
    +          </div>
    +        </div>
    +      </div>
    +    </div>
    +  </div>
    +</body>
    +</html>
    
  • decidim-admin/app/views/layouts/decidim/admin/_header.html.erb+4 0 added
    @@ -0,0 +1,4 @@
    +<meta name="viewport" content="width=device-width, initial-scale=1">
    +<%= csrf_meta_tags %>
    +<%= stylesheet_link_tag    'decidim/admin', media: 'all', 'data-turbolinks-track': 'reload' %>
    +<%= javascript_include_tag 'decidim/admin', 'data-turbolinks-track': 'reload' %>
    
  • decidim-admin/app/views/layouts/decidim/admin/login.html.erb+19 0 added
    @@ -0,0 +1,19 @@
    +<!DOCTYPE html>
    +<html>
    +  <head>
    +    <title>Decidim - Login</title>
    +    <%= render partial: 'layouts/decidim/admin/header' %>
    +  </head>
    +
    +  <body class="login">
    +    <div class="login-form-wrapper">
    +      <div class="login-form">
    +        <h1>Decidim</h1>
    +        <%= display_flash_messages %>
    +        <div class="login-form-inner">
    +          <%= yield %>
    +        </div>
    +      </div>
    +    </div>
    +  </body>
    +</html>
    
  • decidim-admin/app/views/layouts/decidim/admin/_login_items.html.erb+8 0 added
    @@ -0,0 +1,8 @@
    +<div class="session-box">
    +  <div class="admin-email">
    +    <%= current_user.email %>
    +  </div>
    +  <div class="sign-out">
    +    <%= link_to('Logout', decidim.destroy_user_session_path, method: :delete) %>
    +  </div>
    +</div>
    
  • decidim-admin/app/views/layouts/decidim/admin/_sidebar.html.erb+13 0 added
    @@ -0,0 +1,13 @@
    +<div class="title">
    +  <h1>
    +    <%= link_to root_path do %>
    +      <%= title %>
    +    <% end %>
    +  </h1>
    +</div>
    +
    +<nav class="main-menu">
    +  <%= active_link_to t("menu.dashboard", scope: "decidim.admin"), root_path, active: :exact %>
    +</nav>
    +
    +<%= render partial: 'layouts/decidim/admin/login_items' %>
    
  • decidim-admin/bin/rails+14 0 added
    @@ -0,0 +1,14 @@
    +#!/usr/bin/env ruby
    +# frozen_string_literal: true
    +# This command will automatically be run when you run "rails" with Rails gems
    +# installed from the root of your application.
    +
    +ENGINE_ROOT = File.expand_path("../..", __FILE__)
    +ENGINE_PATH = File.expand_path("../../lib/decidim/admin/engine", __FILE__)
    +
    +# Set up gems listed in the Gemfile.
    +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
    +require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
    +
    +require "rails/all"
    +require "rails/engine/commands"
    
  • decidim-admin/config/i18n-tasks.yml+120 0 added
    @@ -0,0 +1,120 @@
    +# i18n-tasks finds and manages missing and unused translations: https://github.com/glebm/i18n-tasks
    +
    +# The "main" locale.
    +base_locale: en
    +## All available locales are inferred from the data by default. Alternatively, specify them explicitly:
    +# locales: [es, fr]
    +## Reporting locale, default: en. Available: en, ru.
    +# internal_locale: en
    +
    +# Read and write translations.
    +data:
    +  ## Translations are read from the file system. Supported format: YAML, JSON.
    +  ## Provide a custom adapter:
    +  # adapter: I18n::Tasks::Data::FileSystem
    +
    +  # Locale files or `File.find` patterns where translations are read from:
    +  read:
    +    ## Default:
    +    # - config/locales/%{locale}.yml
    +    ## More files:
    +    # - config/locales/**/*.%{locale}.yml
    +    ## Another gem (replace %#= with %=):
    +    # - "<%#= %x[bundle show vagrant].chomp %>/templates/locales/%{locale}.yml"
    +
    +  # Locale files to write new keys to, based on a list of key pattern => file rules. Matched from top to bottom:
    +  # `i18n-tasks normalize -p` will force move the keys according to these rules
    +  write:
    +    ## For example, write devise and simple form keys to their respective files:
    +    # - ['{devise, simple_form}.*', 'config/locales/\1.%{locale}.yml']
    +    ## Catch-all default:
    +    # - config/locales/%{locale}.yml
    +
    +  ## Specify the router (see Readme for details). Valid values: conservative_router, pattern_router, or a custom class.
    +  # router: convervative_router
    +
    +  yaml:
    +    write:
    +      # do not wrap lines at 80 characters
    +      line_width: -1
    +
    +  ## Pretty-print JSON:
    +  # json:
    +  #   write:
    +  #     indent: '  '
    +  #     space: ' '
    +  #     object_nl: "\n"
    +  #     array_nl: "\n"
    +
    +# Find translate calls
    +search:
    +  ## Paths or `File.find` patterns to search in:
    +  # paths:
    +  #  - app/
    +
    +  ## Root directories for relative keys resolution.
    +  # relative_roots:
    +  #   - app/controllers
    +  #   - app/helpers
    +  #   - app/mailers
    +  #   - app/presenters
    +  #   - app/views
    +
    +  ## Files or `File.fnmatch` patterns to exclude from search. Some files are always excluded regardless of this setting:
    +  ##   %w(*.jpg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less *.yml *.json)
    +  exclude:
    +    - app/assets/images
    +    - app/assets/fonts
    +
    +  ## Alternatively, the only files or `File.fnmatch patterns` to search in `paths`:
    +  ## If specified, this settings takes priority over `exclude`, but `exclude` still applies.
    +  # only: ["*.rb", "*.html.slim"]
    +
    +  ## If `strict` is `false`, guess usages such as t("categories.#{category}.title"). The default is `true`.
    +  # strict: true
    +
    +  ## Multiple scanners can be used. Their results are merged.
    +  ## The options specified above are passed down to each scanner. Per-scanner options can be specified as well.
    +  ## See this example of a custom scanner: https://github.com/glebm/i18n-tasks/wiki/A-custom-scanner-example
    +
    +## Google Translate
    +# translation:
    +#   # Get an API key and set billing info at https://code.google.com/apis/console to use Google Translate
    +#   api_key: "AbC-dEf5"
    +
    +## Do not consider these keys missing:
    +# ignore_missing:
    +# - 'errors.messages.{accepted,blank,invalid,too_short,too_long}'
    +# - '{devise,simple_form}.*'
    +
    +## Consider these keys used:
    +# ignore_unused:
    +# - 'activerecord.attributes.*'
    +# - '{devise,kaminari,will_paginate}.*'
    +# - 'simple_form.{yes,no}'
    +# - 'simple_form.{placeholders,hints,labels}.*'
    +# - 'simple_form.{error_notification,required}.:'
    +
    +## Exclude these keys from the `i18n-tasks eq-base' report:
    +# ignore_eq_base:
    +#   all:
    +#     - common.ok
    +#   fr,es:
    +#     - common.brand
    +
    +## Ignore these keys completely:
    +# ignore:
    +#  - kaminari.*
    +
    +## Sometimes, it isn't possible for i18n-tasks to match the key correctly,
    +## e.g. in case of a relative key defined in a helper method.
    +## In these cases you can use the built-in PatternMapper to map patterns to keys, e.g.:
    +#
    +# <%#= I18n::Tasks.add_scanner 'I18n::Tasks::Scanners::PatternMapper',
    +#        only: %w(*.html.haml *.html.slim),
    +#        patterns: [['= title\b', '.page_title']] %>
    +#
    +# The PatternMapper can also match key literals via a special %{key} interpolation, e.g.:
    +#
    +# <%#= I18n::Tasks.add_scanner 'I18n::Tasks::Scanners::PatternMapper',
    +#        patterns: [['\bSpree\.t[( ]\s*%{key}', 'spree.%{key}']] %>
    
  • decidim-admin/config/locales/en.yml+8 0 added
    @@ -0,0 +1,8 @@
    +---
    +en:
    +  decidim:
    +    admin:
    +      menu:
    +        dashboard: Dashboard
    +      titles:
    +        dashboard: Dashboard
    
  • decidim-admin/config/routes.rb+4 0 added
    @@ -0,0 +1,4 @@
    +# frozen_string_literal: true
    +Decidim::Admin::Engine.routes.draw do
    +  root to: "dashboard#show", constraints: ->(request) { Admin::OrganizationDashboardConstraint.new(request).matches? }
    +end
    
  • decidim-admin/decidim-admin.gemspec+44 0 added
    @@ -0,0 +1,44 @@
    +# frozen_string_literal: true
    +$LOAD_PATH.push File.expand_path("../lib", __FILE__)
    +
    +# Maintain your gem's version:
    +require_relative "../decidim-core/lib/decidim/core/version"
    +
    +# Describe your gem and declare its dependencies:
    +Gem::Specification.new do |s|
    +  s.name        = "decidim-admin"
    +  s.version     = Decidim.version
    +  s.authors     = ["Josep Jaume Rey Peroy", "Marc Riera Casals", "Oriol Gual Oliva"]
    +  s.email       = ["josepjaume@gmail.com", "mrc2407@gmail.com", "oriolgual@gmail.com"]
    +  s.homepage    = ""
    +  s.summary     = "Organization administration"
    +  s.description = "Organization administration to manage a single organization."
    +  s.license     = "MIT"
    +
    +  s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
    +
    +  s.add_dependency "decidim-core"
    +  s.add_dependency "rails", Decidim.rails_version
    +  s.add_dependency "devise", "~> 4.2"
    +  s.add_dependency "rectify", "~> 0.6"
    +  s.add_dependency "devise_invitable", "~> 1.7.0"
    +  s.add_dependency "foundation-rails", "~> 6.2.3.0"
    +  s.add_dependency "sass-rails", "~> 5.0.0"
    +  s.add_dependency "jquery-rails", "~> 4.0"
    +  s.add_dependency "turbolinks", Decidim.rails_version
    +  s.add_dependency "jquery-turbolinks", "~> 2.1.0"
    +  s.add_dependency "jbuilder", "~> 2.5"
    +  s.add_dependency "foundation_rails_helper", "~> 2.0.0"
    +  s.add_dependency "active_link_to", "~> 1.0.0"
    +
    +  s.add_development_dependency "factory_girl_rails"
    +  s.add_development_dependency "database_cleaner", "~> 1.5.0"
    +  s.add_development_dependency "capybara", "~> 2.4"
    +  s.add_development_dependency "rspec-rails", "~> 3.5"
    +  s.add_development_dependency "byebug"
    +  s.add_development_dependency "wisper-rspec"
    +  s.add_development_dependency "pg"
    +  s.add_development_dependency "listen"
    +  s.add_development_dependency "launchy"
    +  s.add_development_dependency "i18n-tasks", "~> 0.9.5"
    +end
    
  • decidim-admin/Gemfile+17 0 added
    @@ -0,0 +1,17 @@
    +eval(File.read(File.dirname(__FILE__) + "/../common_gemfile.rb"))
    +
    +# Declare your gem's dependencies in decidim-admin.gemspec.
    +# Bundler will treat runtime dependencies like base dependencies, and
    +# development dependencies will be added by default to the :development group.
    +gemspec
    +
    +# Declare any dependencies that are still in development here instead of in
    +# your gemspec. These might include edge Rails or gems from your path or
    +# Git. Remember to move these dependencies to your gemspec before releasing
    +# your gem to rubygems.org.
    +
    +# To use a debugger
    +# gem 'byebug', group: [:development, :test]
    +
    +gemspec path: "../decidim-core"
    +gemspec path: ".."
    
  • decidim-admin/Gemfile.lock+351 0 added
    @@ -0,0 +1,351 @@
    +GIT
    +  remote: https://github.com/sgruhier/foundation_rails_helper
    +  revision: d8819b3a5f9d82251aac7559e0d07505c9777717
    +  specs:
    +    foundation_rails_helper (2.0.0)
    +      actionpack (>= 4.1)
    +      activemodel (>= 4.1)
    +      activesupport (>= 4.1)
    +      railties (>= 4.1)
    +      tzinfo (~> 1.2, >= 1.2.2)
    +
    +PATH
    +  remote: ../decidim-core
    +  specs:
    +    decidim-core (0.0.1.alpha2)
    +      active_link_to (~> 1.0.0)
    +      devise (~> 4.2)
    +      foundation-rails (~> 6.2.3.0)
    +      foundation_rails_helper (~> 2.0.0)
    +      jbuilder (~> 2.5)
    +      jquery-rails (~> 4.0)
    +      jquery-turbolinks (~> 2.1.0)
    +      rails (~> 5.0.0)
    +      rectify (~> 0.6)
    +      sass-rails (~> 5.0.0)
    +      turbolinks (~> 5.0.0)
    +
    +PATH
    +  remote: ..
    +  specs:
    +    decidim (0.0.1.alpha2)
    +      decidim-admin (= 0.0.1.alpha2)
    +      decidim-core (= 0.0.1.alpha2)
    +      decidim-system (= 0.0.1.alpha2)
    +      rails (~> 5.0.0)
    +    decidim-system (0.0.1.alpha2)
    +      active_link_to (~> 1.0.0)
    +      decidim-core
    +      devise (~> 4.2)
    +      devise_invitable (~> 1.7.0)
    +      foundation-rails (~> 6.2.3.0)
    +      foundation_rails_helper (~> 2.0.0)
    +      jbuilder (~> 2.5)
    +      jquery-rails (~> 4.0)
    +      jquery-turbolinks (~> 2.1.0)
    +      rails (~> 5.0.0)
    +      rectify (~> 0.6)
    +      sass-rails (~> 5.0.0)
    +      turbolinks (~> 5.0.0)
    +
    +PATH
    +  remote: .
    +  specs:
    +    decidim-admin (0.0.1.alpha2)
    +      active_link_to (~> 1.0.0)
    +      decidim-core
    +      devise (~> 4.2)
    +      devise_invitable (~> 1.7.0)
    +      foundation-rails (~> 6.2.3.0)
    +      foundation_rails_helper (~> 2.0.0)
    +      jbuilder (~> 2.5)
    +      jquery-rails (~> 4.0)
    +      jquery-turbolinks (~> 2.1.0)
    +      rails (~> 5.0.0)
    +      rectify (~> 0.6)
    +      sass-rails (~> 5.0.0)
    +      turbolinks (~> 5.0.0)
    +
    +GEM
    +  remote: https://rubygems.org/
    +  specs:
    +    actioncable (5.0.0.1)
    +      actionpack (= 5.0.0.1)
    +      nio4r (~> 1.2)
    +      websocket-driver (~> 0.6.1)
    +    actionmailer (5.0.0.1)
    +      actionpack (= 5.0.0.1)
    +      actionview (= 5.0.0.1)
    +      activejob (= 5.0.0.1)
    +      mail (~> 2.5, >= 2.5.4)
    +      rails-dom-testing (~> 2.0)
    +    actionpack (5.0.0.1)
    +      actionview (= 5.0.0.1)
    +      activesupport (= 5.0.0.1)
    +      rack (~> 2.0)
    +      rack-test (~> 0.6.3)
    +      rails-dom-testing (~> 2.0)
    +      rails-html-sanitizer (~> 1.0, >= 1.0.2)
    +    actionview (5.0.0.1)
    +      activesupport (= 5.0.0.1)
    +      builder (~> 3.1)
    +      erubis (~> 2.7.0)
    +      rails-dom-testing (~> 2.0)
    +      rails-html-sanitizer (~> 1.0, >= 1.0.2)
    +    active_link_to (1.0.3)
    +      actionpack
    +    activejob (5.0.0.1)
    +      activesupport (= 5.0.0.1)
    +      globalid (>= 0.3.6)
    +    activemodel (5.0.0.1)
    +      activesupport (= 5.0.0.1)
    +    activerecord (5.0.0.1)
    +      activemodel (= 5.0.0.1)
    +      activesupport (= 5.0.0.1)
    +      arel (~> 7.0)
    +    activesupport (5.0.0.1)
    +      concurrent-ruby (~> 1.0, >= 1.0.2)
    +      i18n (~> 0.7)
    +      minitest (~> 5.1)
    +      tzinfo (~> 1.1)
    +    addressable (2.4.0)
    +    arel (7.1.2)
    +    ast (2.3.0)
    +    axiom-types (0.1.1)
    +      descendants_tracker (~> 0.0.4)
    +      ice_nine (~> 0.11.0)
    +      thread_safe (~> 0.3, >= 0.3.1)
    +    babel-source (5.8.35)
    +    babel-transpiler (0.7.0)
    +      babel-source (>= 4.0, < 6)
    +      execjs (~> 2.0)
    +    bcrypt (3.1.11)
    +    builder (3.2.2)
    +    byebug (9.0.5)
    +    capybara (2.9.1)
    +      addressable
    +      mime-types (>= 1.16)
    +      nokogiri (>= 1.3.3)
    +      rack (>= 1.0.0)
    +      rack-test (>= 0.5.4)
    +      xpath (~> 2.0)
    +    coercible (1.0.0)
    +      descendants_tracker (~> 0.0.1)
    +    concurrent-ruby (1.0.2)
    +    database_cleaner (1.5.3)
    +    descendants_tracker (0.0.4)
    +      thread_safe (~> 0.3, >= 0.3.1)
    +    devise (4.2.0)
    +      bcrypt (~> 3.0)
    +      orm_adapter (~> 0.1)
    +      railties (>= 4.1.0, < 5.1)
    +      responders
    +      warden (~> 1.2.3)
    +    devise_invitable (1.7.0)
    +      actionmailer (>= 4.0.0)
    +      devise (>= 4.0.0)
    +    diff-lcs (1.2.5)
    +    easy_translate (0.5.0)
    +      json
    +      thread
    +      thread_safe
    +    equalizer (0.0.11)
    +    erubis (2.7.0)
    +    execjs (2.7.0)
    +    factory_girl (4.7.0)
    +      activesupport (>= 3.0.0)
    +    factory_girl_rails (4.7.0)
    +      factory_girl (~> 4.7.0)
    +      railties (>= 3.0.0)
    +    ffi (1.9.14)
    +    foundation-rails (6.2.3.0)
    +      railties (>= 3.1.0)
    +      sass (>= 3.3.0, < 3.5)
    +      sprockets-es6 (>= 0.9.0)
    +    globalid (0.3.7)
    +      activesupport (>= 4.1.0)
    +    highline (1.7.8)
    +    i18n (0.7.0)
    +    i18n-tasks (0.9.5)
    +      activesupport (>= 4.0.2)
    +      ast (>= 2.1.0)
    +      easy_translate (>= 0.5.0)
    +      erubis
    +      highline (>= 1.7.3)
    +      i18n
    +      parser (>= 2.2.3.0)
    +      term-ansicolor (>= 1.3.2)
    +      terminal-table (>= 1.5.1)
    +    ice_nine (0.11.2)
    +    jbuilder (2.6.0)
    +      activesupport (>= 3.0.0, < 5.1)
    +      multi_json (~> 1.2)
    +    jquery-rails (4.2.1)
    +      rails-dom-testing (>= 1, < 3)
    +      railties (>= 4.2.0)
    +      thor (>= 0.14, < 2.0)
    +    jquery-turbolinks (2.1.0)
    +      railties (>= 3.1.0)
    +      turbolinks
    +    json (2.0.2)
    +    launchy (2.4.3)
    +      addressable (~> 2.3)
    +    listen (3.1.5)
    +      rb-fsevent (~> 0.9, >= 0.9.4)
    +      rb-inotify (~> 0.9, >= 0.9.7)
    +      ruby_dep (~> 1.2)
    +    loofah (2.0.3)
    +      nokogiri (>= 1.5.9)
    +    mail (2.6.4)
    +      mime-types (>= 1.16, < 4)
    +    method_source (0.8.2)
    +    mime-types (3.1)
    +      mime-types-data (~> 3.2015)
    +    mime-types-data (3.2016.0521)
    +    mini_portile2 (2.1.0)
    +    minitest (5.9.1)
    +    multi_json (1.12.1)
    +    nio4r (1.2.1)
    +    nokogiri (1.6.8)
    +      mini_portile2 (~> 2.1.0)
    +      pkg-config (~> 1.1.7)
    +    orm_adapter (0.5.0)
    +    parser (2.3.1.4)
    +      ast (~> 2.2)
    +    pg (0.19.0)
    +    pkg-config (1.1.7)
    +    rack (2.0.1)
    +    rack-test (0.6.3)
    +      rack (>= 1.0)
    +    rails (5.0.0.1)
    +      actioncable (= 5.0.0.1)
    +      actionmailer (= 5.0.0.1)
    +      actionpack (= 5.0.0.1)
    +      actionview (= 5.0.0.1)
    +      activejob (= 5.0.0.1)
    +      activemodel (= 5.0.0.1)
    +      activerecord (= 5.0.0.1)
    +      activesupport (= 5.0.0.1)
    +      bundler (>= 1.3.0, < 2.0)
    +      railties (= 5.0.0.1)
    +      sprockets-rails (>= 2.0.0)
    +    rails-dom-testing (2.0.1)
    +      activesupport (>= 4.2.0, < 6.0)
    +      nokogiri (~> 1.6.0)
    +    rails-html-sanitizer (1.0.3)
    +      loofah (~> 2.0)
    +    railties (5.0.0.1)
    +      actionpack (= 5.0.0.1)
    +      activesupport (= 5.0.0.1)
    +      method_source
    +      rake (>= 0.8.7)
    +      thor (>= 0.18.1, < 2.0)
    +    rake (11.3.0)
    +    rb-fsevent (0.9.7)
    +    rb-inotify (0.9.7)
    +      ffi (>= 0.5.0)
    +    rectify (0.6.1)
    +      activemodel (>= 4.1.0)
    +      activerecord (>= 4.1.0)
    +      activesupport (>= 4.1.0)
    +      virtus (~> 1.0.5)
    +      wisper (>= 1.6.1)
    +    responders (2.3.0)
    +      railties (>= 4.2.0, < 5.1)
    +    rspec (3.5.0)
    +      rspec-core (~> 3.5.0)
    +      rspec-expectations (~> 3.5.0)
    +      rspec-mocks (~> 3.5.0)
    +    rspec-core (3.5.3)
    +      rspec-support (~> 3.5.0)
    +    rspec-expectations (3.5.0)
    +      diff-lcs (>= 1.2.0, < 2.0)
    +      rspec-support (~> 3.5.0)
    +    rspec-mocks (3.5.0)
    +      diff-lcs (>= 1.2.0, < 2.0)
    +      rspec-support (~> 3.5.0)
    +    rspec-rails (3.5.2)
    +      actionpack (>= 3.0)
    +      activesupport (>= 3.0)
    +      railties (>= 3.0)
    +      rspec-core (~> 3.5.0)
    +      rspec-expectations (~> 3.5.0)
    +      rspec-mocks (~> 3.5.0)
    +      rspec-support (~> 3.5.0)
    +    rspec-support (3.5.0)
    +    ruby_dep (1.4.0)
    +    sass (3.4.22)
    +    sass-rails (5.0.6)
    +      railties (>= 4.0.0, < 6)
    +      sass (~> 3.1)
    +      sprockets (>= 2.8, < 4.0)
    +      sprockets-rails (>= 2.0, < 4.0)
    +      tilt (>= 1.1, < 3)
    +    sprockets (3.7.0)
    +      concurrent-ruby (~> 1.0)
    +      rack (> 1, < 3)
    +    sprockets-es6 (0.9.2)
    +      babel-source (>= 5.8.11)
    +      babel-transpiler
    +      sprockets (>= 3.0.0)
    +    sprockets-rails (3.2.0)
    +      actionpack (>= 4.0)
    +      activesupport (>= 4.0)
    +      sprockets (>= 3.0.0)
    +    term-ansicolor (1.3.2)
    +      tins (~> 1.0)
    +    terminal-table (1.7.3)
    +      unicode-display_width (~> 1.1.1)
    +    thor (0.19.1)
    +    thread (0.2.2)
    +    thread_safe (0.3.5)
    +    tilt (2.0.5)
    +    tins (1.12.0)
    +    turbolinks (5.0.1)
    +      turbolinks-source (~> 5)
    +    turbolinks-source (5.0.0)
    +    tzinfo (1.2.2)
    +      thread_safe (~> 0.1)
    +    unicode-display_width (1.1.1)
    +    virtus (1.0.5)
    +      axiom-types (~> 0.1)
    +      coercible (~> 1.0)
    +      descendants_tracker (~> 0.0, >= 0.0.3)
    +      equalizer (~> 0.0, >= 0.0.9)
    +    warden (1.2.6)
    +      rack (>= 1.0)
    +    websocket-driver (0.6.4)
    +      websocket-extensions (>= 0.1.0)
    +    websocket-extensions (0.1.2)
    +    wisper (1.6.1)
    +    wisper-rspec (0.0.2)
    +    xpath (2.0.0)
    +      nokogiri (~> 1.3)
    +
    +PLATFORMS
    +  ruby
    +
    +DEPENDENCIES
    +  bundler (~> 1.12)
    +  byebug
    +  capybara (~> 2.4)
    +  database_cleaner (~> 1.5.0)
    +  decidim!
    +  decidim-admin!
    +  decidim-core!
    +  factory_girl_rails
    +  foundation_rails_helper!
    +  i18n-tasks (~> 0.9.5)
    +  launchy
    +  listen
    +  pg
    +  rake (~> 11.0)
    +  rspec (~> 3.0)
    +  rspec-rails (~> 3.5)
    +  wisper-rspec
    +
    +RUBY VERSION
    +   ruby 2.3.1p112
    +
    +BUNDLED WITH
    +   1.13.1
    
  • decidim-admin/lib/decidim/admin/engine.rb+30 0 added
    @@ -0,0 +1,30 @@
    +# frozen_string_literal: true
    +require "rails"
    +require "active_support/all"
    +
    +require "decidim/core"
    +require "devise"
    +require "jquery-rails"
    +require "sass-rails"
    +require "turbolinks"
    +require "jquery-turbolinks"
    +require "foundation-rails"
    +require "foundation_rails_helper"
    +require "jbuilder"
    +require "rectify"
    +
    +module Decidim
    +  module Admin
    +    # Decidim's core Rails Engine.
    +    class Engine < ::Rails::Engine
    +      isolate_namespace Decidim::Admin
    +
    +      config.to_prepare do
    +        Rails.application.config.assets.precompile += %w(
    +          decidim/admin.js
    +          decidim/admin.css
    +        )
    +      end
    +    end
    +  end
    +end
    
  • decidim-admin/lib/decidim/admin.rb+12 0 added
    @@ -0,0 +1,12 @@
    +# frozen_string_literal: true
    +require "decidim/admin/engine"
    +
    +module Decidim
    +  # This module contains all the logic related to a admin-wide
    +  # administration panel. The scope of the domain is to be able
    +  # to manage Organizations (tenants), as well as have a bird's
    +  # eye view of the whole admin.
    +  #
    +  module Admin
    +  end
    +end
    
  • decidim-admin/lib/generators/decidim/admin/dummy_generator.rb+60 0 added
    @@ -0,0 +1,60 @@
    +# frozen_string_literal: true
    +require "rails/generators"
    +require_relative "../../../../lib/generators/decidim/app_generator"
    +
    +module Decidim
    +  module Admin
    +    module Generators
    +      # Generates a dummy test Rails app for the given library folder. It uses
    +      # the `AppGenerator` class to actually generate the Rails app, so this
    +      # generator only sets the path and some flags.
    +      #
    +      # The Rails app will be installed with some flags to disable git, tests,
    +      # Gemfile and other options. Refer to the `create_dummy_app` method to see
    +      # all the flags passed to the `AppGenerator` class, which is the one that
    +      # actually generates the Rails app.
    +      #
    +      # Remember that, for how generators work, actions are executed based on the
    +      # definition order of the public methods.
    +      class DummyGenerator < Rails::Generators::Base
    +        desc "Generate dummy app for testing purposes"
    +
    +        class_option :lib_name, type: :string,
    +                                desc: "The library where the dummy app will be installed"
    +
    +        class_option :migrate, type: :boolean, default: false,
    +                               desc: "Run migrations after installing decidim"
    +
    +        def cleanup
    +          remove_directory_if_exists(dummy_path)
    +        end
    +
    +        def create_dummy_app
    +          Decidim::Generators::AppGenerator.start [
    +            dummy_path,
    +            "--skip_gemfile",
    +            "--skip-bundle",
    +            "--skip-git",
    +            "--skip-keeps",
    +            "--skip-test",
    +            "--migrate=#{options[:migrate]}"
    +          ]
    +        end
    +
    +        private
    +
    +        def dummy_path
    +          ENV["DUMMY_PATH"] || "spec/#{short_lib_name}_dummy"
    +        end
    +
    +        def remove_directory_if_exists(path)
    +          remove_dir(path) if File.directory?(path)
    +        end
    +
    +        def short_lib_name
    +          options[:lib_name].split("/").last
    +        end
    +      end
    +    end
    +  end
    +end
    
  • decidim-admin/lib/tasks/decidim/admin_tasks.rake+5 0 added
    @@ -0,0 +1,5 @@
    +# frozen_string_literal: true
    +# desc "Explaining what the task does"
    +# task :decidim_admin do
    +#   # Task goes here
    +# end
    
  • decidim-admin/Rakefile+35 0 added
    @@ -0,0 +1,35 @@
    +# frozen_string_literal: true
    +begin
    +  require "bundler/setup"
    +rescue LoadError
    +  puts "You must `gem install bundler` and `bundle install` to run rake tasks"
    +end
    +
    +require_relative "../decidim-core/lib/decidim/testing_support/common_rake"
    +require "rdoc/task"
    +
    +RDoc::Task.new(:rdoc) do |rdoc|
    +  rdoc.rdoc_dir = "rdoc"
    +  rdoc.title    = "Decidim::Admin"
    +  rdoc.options << "--line-numbers"
    +  rdoc.rdoc_files.include("README.md")
    +  rdoc.rdoc_files.include("lib/**/*.rb")
    +end
    +
    +load "rails/tasks/statistics.rake"
    +
    +require "bundler/gem_tasks"
    +
    +task default: :test
    +
    +desc "Generates a dummy app for testing"
    +task :test_app do
    +  ENV["LIB_NAME"] = "decidim/admin"
    +  Rake::Task["common:test_app"].invoke
    +end
    +
    +task test: :test_app do
    +  Dir.chdir(File.dirname(__FILE__).to_s) do
    +    sh "rspec spec"
    +  end
    +end
    
  • decidim-admin/README.md+28 0 added
    @@ -0,0 +1,28 @@
    +# Decidim::Admin
    +Short description and motivation.
    +
    +## Usage
    +How to use my plugin.
    +
    +## Installation
    +Add this line to your application's Gemfile:
    +
    +```ruby
    +gem 'decidim-admin'
    +```
    +
    +And then execute:
    +```bash
    +$ bundle
    +```
    +
    +Or install it yourself as:
    +```bash
    +$ gem install decidim-admin
    +```
    +
    +## Contributing
    +Contribution directions go here.
    +
    +## License
    +The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
    
  • decidim-admin/spec/factories.rb+1 0 added
    @@ -0,0 +1 @@
    +require_relative "../../decidim-core/spec/factories"
    
  • decidim-admin/spec/features/admin_access_control.rb+40 0 added
    @@ -0,0 +1,40 @@
    +# frozen_string_literal: true
    +require "spec_helper"
    +
    +describe "Admin dashboard access control", type: :feature do
    +  let(:organization) { create(:organization) }
    +
    +  before do
    +    login_as user, scope: :user
    +    switch_to_host(organization.host)
    +  end
    +
    +  context "a regular user" do
    +    let(:user) { create(:user, organization: organization) }
    +
    +    it "can't access the admin dashboard" do
    +      visit decidim_admin.root_path
    +      expect(page.status_code).to eq(404)
    +    end
    +  end
    +
    +  context "an organization admin" do
    +    let(:user) { create(:user, :admin, organization: organization) }
    +
    +    it "can access the admin dashboard" do
    +      visit decidim_admin.root_path
    +      expect(page.status_code).to eq(200)
    +      expect(page).to have_content("Dashboard")
    +    end
    +  end
    +
    +  context "an admin from another organization" do
    +    let(:other_organization) { create(:organization) }
    +    let(:user) { create(:user, :admin, organization: other_organization) }
    +
    +    it "can't access the admin dashboard" do
    +      visit decidim_admin.root_path
    +      expect(page.status_code).to eq(404)
    +    end
    +  end
    +end
    
  • decidim-admin/spec/features/admin_invite_spec.rb+38 0 added
    @@ -0,0 +1,38 @@
    +# frozen_string_literal: true
    +require "spec_helper"
    +
    +describe "Admin invite", type: :feature do
    +  let(:form) do
    +    Decidim::System::RegisterOrganizationForm.new(params)
    +  end
    +
    +  let(:params) do
    +    {
    +      name: "Gotham City",
    +      host: "decide.lvh.me",
    +      organization_admin_email: "f.laguardia@gotham.gov"
    +    }
    +  end
    +
    +  before(:each) do
    +    Decidim::System::RegisterOrganization.new(form).call
    +    switch_to_host("decide.lvh.me")
    +  end
    +
    +  describe "Accept an invitation" do
    +    let(:email_link) do
    +      last_delivery = ActionMailer::Base.deliveries.last.body.encoded
    +      URI.extract(last_delivery).last
    +    end
    +
    +    it "asks for a password and redirects to the organization dashboard" do
    +      visit email_link
    +
    +      fill_in :user_password, with: "123456"
    +      fill_in :user_password_confirmation, with: "123456"
    +      find("*[type=submit]").click
    +
    +      expect(page).to have_content("Dashboard")
    +    end
    +  end
    +end
    
  • decidim-admin/spec/i18n_spec.rb+18 0 added
    @@ -0,0 +1,18 @@
    +# frozen_string_literal: true
    +require 'i18n/tasks'
    +
    +RSpec.describe 'I18n' do
    +  let(:i18n) { I18n::Tasks::BaseTask.new }
    +  let(:missing_keys) { i18n.missing_keys }
    +  let(:unused_keys) { i18n.unused_keys }
    +
    +  it 'does not have missing keys' do
    +    expect(missing_keys).to be_empty,
    +      "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them"
    +  end
    +
    +  it 'does not have unused keys' do
    +    expect(unused_keys).to be_empty,
    +      "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them"
    +  end
    +end
    
  • decidim-admin/spec/spec_helper.rb+39 0 added
    @@ -0,0 +1,39 @@
    +# frozen_string_literal: true
    +ENV["RAILS_ENV"] ||= "test"
    +
    +if ENV["CI"]
    +  require "simplecov"
    +  SimpleCov.start
    +
    +  require "codecov"
    +  SimpleCov.formatter = SimpleCov::Formatter::Codecov
    +end
    +
    +begin
    +  require File.expand_path("../admin_dummy/config/environment", __FILE__)
    +rescue LoadError
    +  puts "Could not load dummy application. Please ensure you have run `bundle exec rake test_app`"
    +  exit
    +end
    +
    +require "rspec/rails"
    +require "factory_girl_rails"
    +require "database_cleaner"
    +require "byebug"
    +
    +# Requires supporting files with custom matchers and macros, etc,
    +# in ./support/ and its subdirectories.
    +Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
    +
    +RSpec.configure do |config|
    +  config.color = true
    +  config.fail_fast = ENV["FAIL_FAST"] || false
    +  config.infer_spec_type_from_file_location!
    +  config.mock_with :rspec
    +  config.raise_errors_for_deprecations!
    +
    +  # If you're not using ActiveRecord, or you'd prefer not to run each of your
    +  # examples within a transaction, comment the following line or assign false
    +  # instead of true.
    +  config.use_transactional_fixtures = false
    +end
    
  • decidim-admin/spec/support/action_mailer.rb+5 0 added
    @@ -0,0 +1,5 @@
    +RSpec.configure do |config|
    +  config.before(:each) do
    +    ActionMailer::Base.deliveries.clear
    +  end
    +end
    
  • decidim-admin/spec/support/capybara.rb+17 0 added
    @@ -0,0 +1,17 @@
    +def switch_to_host(host = "lvh.me")
    +  Capybara.app_host = "http://#{host}"
    +end
    +
    +def switch_to_default_host
    +  switch_to_host nil
    +end
    +
    +Capybara.configure do |config|
    +  config.always_include_port = true
    +end
    +
    +RSpec.configure do |config|
    +  config.before :each, type: :feature do
    +    switch_to_default_host
    +  end
    +end
    
  • decidim-admin/spec/support/database_cleaner.rb+12 0 added
    @@ -0,0 +1,12 @@
    +RSpec.configure do |config|
    +  config.before(:suite) do
    +    DatabaseCleaner.strategy = :transaction
    +    DatabaseCleaner.clean_with(:truncation)
    +  end
    +
    +  config.around(:each) do |example|
    +    DatabaseCleaner.cleaning do
    +      example.run
    +    end
    +  end
    +end
    
  • decidim-admin/spec/support/factory_girl.rb+3 0 added
    @@ -0,0 +1,3 @@
    +RSpec.configure do |config|
    +  config.include FactoryGirl::Syntax::Methods
    +end
    
  • decidim-admin/spec/support/warden.rb+7 0 added
    @@ -0,0 +1,7 @@
    +RSpec.configure do |config|
    +  config.include Warden::Test::Helpers, type: :feature
    +
    +  config.after :each, type: :feature do
    +    Warden.test_reset!
    +  end
    +end
    
  • decidim-admin/spec/support/wisper.rb+5 0 added
    @@ -0,0 +1,5 @@
    +require 'wisper/rspec/matchers'
    +
    +RSpec::configure do |config|
    +  config.include(Wisper::RSpec::BroadcastMatcher)
    +end
    
  • decidim-core/app/controllers/decidim/devise/invitations_controller.rb+20 0 added
    @@ -0,0 +1,20 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module Devise
    +    # This controller customizes the behaviour of Devise::Invitiable.
    +    class InvitationsController < ::Devise::InvitationsController
    +      # We don't users to create invitations, so we just redirect them to the
    +      # homepage.
    +      def authenticate_inviter!
    +        redirect_to root_path
    +      end
    +
    +      # Overwrite the method that returns the path after a user accepts an
    +      # invitation. Using the param `invite_redirect` we can redirect the user
    +      # to a custom path after it has accepted the invitation.
    +      def after_accept_path_for(resource)
    +        params[:invite_redirect] || after_sign_in_path_for(resource)
    +      end
    +    end
    +  end
    +end
    
  • decidim-core/app/controllers/decidim/devise/passwords_controller.rb+8 0 added
    @@ -0,0 +1,8 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module Devise
    +    # A controller so admins can recover their passwords.
    +    class PasswordsController < Devise::PasswordsController
    +    end
    +  end
    +end
    
  • decidim-core/app/controllers/decidim/devise/sessions_controller.rb+8 0 added
    @@ -0,0 +1,8 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module Devise
    +    # A controller so admins can log-in and use their backend.
    +    class SessionsController < ::Devise::SessionsController
    +    end
    +  end
    +end
    
  • decidim-core/app/mailers/decidim/decidim_devise_mailer.rb+11 0 added
    @@ -0,0 +1,11 @@
    +# frozen_string_literal: true
    +module Decidim
    +  # A custom mailer for Devise so we can tweak the invitation instructions for
    +  # each role.
    +  class DecidimDeviseMailer < Devise::Mailer
    +    def invitation_instructions(record, token, opts = {})
    +      @token = token
    +      devise_mail(record, record.invitation_instructions || :invitation_instructions, opts)
    +    end
    +  end
    +end
    
  • decidim-core/app/middleware/decidim/current_organization.rb+28 0 added
    @@ -0,0 +1,28 @@
    +# frozen_string_literal: true
    +module Decidim
    +  # A middleware that enhances the request with the current organization based
    +  # on the hostname.
    +  class CurrentOrganization
    +    # Initializes the Rack Middleware.
    +    #
    +    # app - The Rack application
    +    def initialize(app)
    +      @app = app
    +    end
    +
    +    # Main entry point for a Rack Middleware.
    +    #
    +    # env - A Hash.
    +    def call(env)
    +      env["decidim.current_organization"] = detect_current_organization(env)
    +      @app.call(env)
    +    end
    +
    +    private
    +
    +    def detect_current_organization(env)
    +      host = Rack::Request.new(env).host.downcase
    +      Decidim::Organization.where(host: host).first
    +    end
    +  end
    +end
    
  • decidim-core/app/models/decidim/user.rb+16 2 modified
    @@ -1,16 +1,24 @@
     # frozen_string_literal: true
    +require_dependency "devise/models/decidim_validatable"
    +
     module Decidim
       # A User is a citizen that wants to join the platform to participate.
       class User < ApplicationRecord
         devise :invitable, :database_authenticatable, :registerable,
    -           :recoverable, :rememberable, :trackable, :validatable
    +           :recoverable, :rememberable, :trackable, :decidim_validatable
     
         belongs_to :organization, foreign_key: "decidim_organization_id", class_name: Decidim::Organization
     
         ROLES = %w(admin moderator official).freeze
     
         validates :organization, presence: true
    -    validates :roles, inclusion: { in: ROLES }, allow_blank: true
    +    validate :all_roles_are_valid
    +
    +    # Public: Allows customizing the invitation instruction email content when
    +    # inviting a user.
    +    #
    +    # Returns a String.
    +    attr_accessor :invitation_instructions
     
         # Checks if the user is an admin in the organization it belongs to. This
         # should probably be deleted and add a system to authorize actions.
    @@ -19,5 +27,11 @@ class User < ApplicationRecord
         def admin?
           roles.include?("admin")
         end
    +
    +    private
    +
    +    def all_roles_are_valid
    +      errors.add(:roles, :invalid) unless roles.all? { |role| ROLES.include?(role) }
    +    end
       end
     end
    
  • decidim-core/app/views/decidim/devise/invitations/edit.html.erb+16 0 added
    @@ -0,0 +1,16 @@
    +<h2><%= t 'devise.invitations.edit.header' %></h2>
    +
    +<%= form_for resource, as: resource_name, url: invitation_path(resource_name, invite_redirect: params[:invite_redirect]), html: { method: :put } do |f| %>
    +  <%= devise_error_messages! %>
    +  <%= f.hidden_field :invitation_token %>
    +
    +  <% if f.object.class.require_password_on_accepting %>
    +  <p><%= f.label :password %><br />
    +  <%= f.password_field :password %></p>
    +
    +  <p><%= f.label :password_confirmation %><br />
    +  <%= f.password_field :password_confirmation %></p>
    +  <% end %>
    +
    +  <p><%= f.submit t("devise.invitations.edit.submit_button") %></p>
    +<% end %>
    
  • decidim-core/app/views/decidim/devise/passwords/edit.html.erb+23 0 added
    @@ -0,0 +1,23 @@
    +<h2>Change your password</h2>
    +
    +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
    +  <%= devise_error_messages! %>
    +  <%= f.hidden_field :reset_password_token %>
    +
    +  <div class="field">
    +    <% if @minimum_password_length %>
    +      <em>(<%= @minimum_password_length %> characters minimum)</em><br />
    +    <% end %>
    +    <%= f.password_field :password, autofocus: true, autocomplete: "off" %>
    +  </div>
    +
    +  <div class="field">
    +    <%= f.password_field :password_confirmation, autocomplete: "off" %>
    +  </div>
    +
    +  <div class="actions">
    +    <%= f.submit "Change my password" %>
    +  </div>
    +<% end %>
    +
    +<%= render "decidim/devise/shared/links" %>
    
  • decidim-core/app/views/decidim/devise/passwords/new.html.erb+15 0 added
    @@ -0,0 +1,15 @@
    +<h2>Forgot your password?</h2>
    +
    +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
    +  <%= devise_error_messages! %>
    +
    +  <div class="field">
    +    <%= f.email_field :email, autofocus: true %>
    +  </div>
    +
    +  <div class="actions">
    +    <%= f.submit "Send me reset password instructions" %>
    +  </div>
    +<% end %>
    +
    +<%= render "decidim/devise/shared/links" %>
    
  • decidim-core/app/views/decidim/devise/sessions/new.html.erb+23 0 added
    @@ -0,0 +1,23 @@
    +<h2>Log in</h2>
    +
    +<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
    +  <div class="field">
    +    <%= f.email_field :email, autofocus: true %>
    +  </div>
    +
    +  <div class="field">
    +    <%= f.password_field :password, autocomplete: "off" %>
    +  </div>
    +
    +  <% if devise_mapping.rememberable? -%>
    +    <div class="field">
    +      <%= f.check_box :remember_me %>
    +    </div>
    +  <% end -%>
    +
    +  <div class="actions">
    +    <%= f.submit "Log in" %>
    +  </div>
    +<% end %>
    +
    +<%= render "decidim/devise/shared/links" %>
    
  • decidim-core/app/views/decidim/devise/shared/_links.html.erb+25 0 added
    @@ -0,0 +1,25 @@
    +<%- if controller_name != 'sessions' %>
    +  <%= link_to "Log in", new_session_path(resource_name) %><br />
    +<% end -%>
    +
    +<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
    +  <%= link_to "Sign up", new_registration_path(resource_name) %><br />
    +<% end -%>
    +
    +<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
    +  <%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
    +<% end -%>
    +
    +<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
    +  <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
    +<% end -%>
    +
    +<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
    +  <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
    +<% end -%>
    +
    +<%- if devise_mapping.omniauthable? %>
    +  <%- resource_class.omniauth_providers.each do |provider| %>
    +    <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %><br />
    +  <% end -%>
    +<% end -%>
    
  • decidim-core/app/views/users/mailer/organization_admin_invitation_instructions.html.erb+1 2 renamed
    @@ -1,8 +1,7 @@
     <p><%= t("devise.mailer.invitation_instructions.hello", email: @resource.email) %></p>
     
     <p><%= t("devise.mailer.invitation_instructions.someone_invited_you", application: Decidim.config.application_name) %></p>
    -
    -<p><%= link_to t("devise.mailer.invitation_instructions.accept"), accept_invitation_url(@resource, :invitation_token => @token) %></p>
    +<p><%= link_to t("devise.mailer.invitation_instructions.accept"), accept_invitation_url(@resource, invitation_token: @token, invite_redirect: decidim_admin.root_path, host: @resource.organization.host) %></p>
     
     <% if @resource.invitation_due_at %>
       <p><%= t("devise.mailer.invitation_instructions.accept_until", due_date: l(@resource.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format')) %></p>
    
  • decidim-core/app/views/users/mailer/organization_admin_invitation_instructions.text.erb+1 1 renamed
    @@ -2,7 +2,7 @@
     
     <%= t("devise.mailer.invitation_instructions.someone_invited_you", application: Decidim.config.application_name) %>
     
    -<%= accept_invitation_url(@resource, :invitation_token => @token) %>
    +<%= accept_invitation_url(@resource, invitation_token: @token, invite_redirect: decidim_admin.root_path, host: @resource.organization.host) %>
     
     <% if @resource.invitation_due_at %>
       <%= t("devise.mailer.invitation_instructions.accept_until", due_date: l(@resource.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format')) %>
    
  • decidim-core/config/initializers/devise.rb+2 2 modified
    @@ -19,7 +19,7 @@
       config.mailer_sender = "please-change-me-at-config-initializers-devise@example.com"
     
       # Configure the class responsible to send e-mails.
    -  # config.mailer = 'Devise::Mailer'
    +  config.mailer = "Decidim::DecidimDeviseMailer"
     
       # Configure the parent class responsible to send e-mails.
       config.parent_mailer = "Decidim::ApplicationMailer"
    @@ -165,7 +165,7 @@
       # Auto-login after the user accepts the invite. If this is false,
       # the user will need to manually log in after accepting the invite.
       # Default: true
    -  # config.allow_insecure_sign_in_after_accept = false
    +  config.allow_insecure_sign_in_after_accept = true
     
       # ==> Configuration for :confirmable
       # A period that the user is allowed to access the website even without
    
  • decidim-core/Gemfile+1 0 modified
    @@ -15,4 +15,5 @@ gemspec
     # gem 'byebug', group: [:development, :test]
     
     gemspec path: "../decidim-system"
    +gemspec path: "../decidim-admin"
     gemspec path: ".."
    
  • decidim-core/Gemfile.lock+20 0 modified
    @@ -9,6 +9,24 @@ GIT
           railties (>= 4.1)
           tzinfo (~> 1.2, >= 1.2.2)
     
    +PATH
    +  remote: ../decidim-admin
    +  specs:
    +    decidim-admin (0.0.1.alpha2)
    +      active_link_to (~> 1.0.0)
    +      decidim-core
    +      devise (~> 4.2)
    +      devise_invitable (~> 1.7.0)
    +      foundation-rails (~> 6.2.3.0)
    +      foundation_rails_helper (~> 2.0.0)
    +      jbuilder (~> 2.5)
    +      jquery-rails (~> 4.0)
    +      jquery-turbolinks (~> 2.1.0)
    +      rails (~> 5.0.0)
    +      rectify (~> 0.6)
    +      sass-rails (~> 5.0.0)
    +      turbolinks (~> 5.0.0)
    +
     PATH
       remote: ../decidim-system
       specs:
    @@ -31,6 +49,7 @@ PATH
       remote: ..
       specs:
         decidim (0.0.1.alpha2)
    +      decidim-admin (= 0.0.1.alpha2)
           decidim-core (= 0.0.1.alpha2)
           decidim-system (= 0.0.1.alpha2)
           rails (~> 5.0.0)
    @@ -314,6 +333,7 @@ DEPENDENCIES
       capybara (~> 2.4)
       database_cleaner (~> 1.5.0)
       decidim!
    +  decidim-admin!
       decidim-core!
       decidim-system!
       factory_girl_rails
    
  • decidim-core/lib/decidim/core/engine.rb+4 0 modified
    @@ -26,6 +26,10 @@ class Engine < ::Rails::Engine
               helper Decidim::LayoutHelper
             end
           end
    +
    +      initializer "decidim.middleware" do |app|
    +        app.config.middleware.use Decidim::CurrentOrganization
    +      end
         end
       end
     end
    
  • decidim-core/lib/devise/models/decidim_validatable.rb+67 0 added
    @@ -0,0 +1,67 @@
    +# frozen_string_literal: true
    +module Devise
    +  module Models
    +    # Validatable creates all needed validations for a user email and password.
    +    # Automatically validate if the email is present, unique and its format is
    +    # valid. Also tests presence of password, confirmation and length.
    +    #
    +    # == Options
    +    #
    +    # Validatable adds the following options to devise_for:
    +    #
    +    #   * +email_regexp+: the regular expression used to validate e-mails;
    +    #   * +password_length+: a range expressing password length. Defaults to 8..72.
    +    #
    +    module DecidimValidatable
    +      # All validations used by this module.
    +      VALIDATIONS = [:validates_presence_of, :validates_uniqueness_of, :validates_format_of,
    +                     :validates_confirmation_of, :validates_length_of].freeze
    +
    +      def self.required_fields(_klass)
    +        []
    +      end
    +
    +      def self.included(base)
    +        base.extend ClassMethods
    +        assert_validations_api!(base)
    +
    +        base.class_eval do
    +          validates_presence_of   :email, if: :email_required?
    +          validates_uniqueness_of :email, allow_blank: true, if: :email_changed?, scope: :organization
    +          validates_format_of     :email, with: email_regexp, allow_blank: true, if: :email_changed?
    +
    +          validates_presence_of     :password, if: :password_required?
    +          validates_confirmation_of :password, if: :password_required?
    +          validates_length_of       :password, within: password_length, allow_blank: true
    +        end
    +      end
    +
    +      def self.assert_validations_api!(base) #:nodoc:
    +        unavailable_validations = VALIDATIONS.select { |v| !base.respond_to?(v) }
    +
    +        unless unavailable_validations.empty?
    +          raise "Could not use :validatable module since #{base} does not respond " \
    +                "to the following methods: #{unavailable_validations.to_sentence}."
    +        end
    +      end
    +
    +      protected
    +
    +      # Checks whether a password is needed or not. For validations only.
    +      # Passwords are always required if it's a new record, or if the password
    +      # or confirmation are being set somewhere.
    +      def password_required?
    +        !persisted? || !password.nil? || !password_confirmation.nil?
    +      end
    +
    +      def email_required?
    +        true
    +      end
    +
    +      # Methods to be injected at class level.
    +      module ClassMethods
    +        Devise::Models.config(self, :email_regexp, :password_length)
    +      end
    +    end
    +  end
    +end
    
  • decidim-core/lib/generators/decidim/install_generator.rb+5 2 modified
    @@ -18,6 +18,7 @@ class InstallGenerator < Rails::Generators::Base
     
           def install
             route "mount Decidim::System::Engine => '/system'"
    +        route "mount Decidim::Admin::Engine => '/admin'"
             route "mount Decidim::Core::Engine => '/'"
           end
     
    @@ -75,8 +76,10 @@ def test_mail_host
           private
     
           def prepare_database
    -        rake "db:environment:set RAILS_ENV=development"
    -        rake "db:drop:all"
    +        unless ENV["CI"]
    +          rake "db:environment:set RAILS_ENV=development"
    +          rake "db:drop:all"
    +        end
             rake "db:create"
             rake "db:migrate"
             rake "db:test:prepare"
    
  • decidim-core/spec/middleware/decidim/current_organization_spec.rb+30 0 added
    @@ -0,0 +1,30 @@
    +require "spec_helper"
    +
    +module Decidim
    +  describe CurrentOrganization do
    +    let(:app) { ->(env) { [200, env, "app"] } }
    +    let(:env) { Rack::MockRequest.env_for("https://#{host}", {}) }
    +    let(:host) { "city.domain.org" }
    +    let(:middleware) { described_class.new(app) }
    +
    +    context "when an organization exists for the current host" do
    +      let!(:organization) { create(:organization, host: host) }
    +
    +      it "sets the organization" do
    +        _code, new_env = middleware.call(env)
    +
    +        expect(new_env["decidim.current_organization"]).to eq(organization)
    +      end
    +    end
    +
    +    context "when no orgazanization exists for the current host" do
    +      let!(:organization) { create(:organization, host: 'fake.host.com') }
    +
    +      it "doesn't set the organization" do
    +        _code, new_env = middleware.call(env)
    +
    +        expect(new_env["decidim.current_organization"]).to be_nil
    +      end
    +    end
    +  end
    +end
    
  • decidim-core/spec/models/decidim/user_spec.rb+31 0 modified
    @@ -8,6 +8,37 @@
         expect(user).to be_valid
       end
     
    +  context "with roles" do
    +    let(:user) { build(:user, :admin) }
    +
    +    it "is still valid" do
    +      expect(user).to be_valid
    +    end
    +
    +    context "with an invalid role" do
    +      let(:user) { build(:user, roles: ["foo"]) }
    +
    +      it "is not valid" do
    +        expect(user).to_not be_valid
    +      end
    +    end
    +  end
    +
    +  describe "validation scopes" do
    +    context "when a user with the same email exists in another organization" do
    +      let(:email) { "foo@bar.com" }
    +      let(:user) { build(:user, email: email) }
    +
    +      before do
    +        create(:user, email: email)
    +      end
    +
    +      it "is valid" do
    +        expect(user).to be_valid
    +      end
    +    end
    +  end
    +
       describe "admin?" do
         context "when the user is an admin" do
           before do
    
  • decidim.gemspec+1 0 modified
    @@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
     
       spec.add_dependency "decidim-core", Decidim.version
       spec.add_dependency "decidim-system", Decidim.version
    +  spec.add_dependency "decidim-admin", Decidim.version
       spec.add_dependency "rails", Decidim.rails_version
     
       spec.add_development_dependency "bundler", "~> 1.12"
    
  • decidim-system/app/commands/decidim/system/register_organization.rb+3 1 modified
    @@ -44,7 +44,9 @@ def invite_admin(organization)
               email: form.organization_admin_email,
               organization: organization,
               roles: ["admin"]
    -        )
    +        ) do |invitable|
    +          invitable.invitation_instructions = :organization_admin_invitation_instructions
    +        end
           end
         end
       end
    
  • decidim-system/app/helpers/decidim/system/application_helper.rb+0 4 modified
    @@ -7,10 +7,6 @@ module ApplicationHelper
           def title
             "Decidim"
           end
    -
    -      def field_name(model, field)
    -        st "models.#{model}.fields.#{field}"
    -      end
         end
       end
     end
    
  • decidim-system/Gemfile.lock+15 0 modified
    @@ -29,9 +29,24 @@ PATH
       remote: ..
       specs:
         decidim (0.0.1.alpha2)
    +      decidim-admin (= 0.0.1.alpha2)
           decidim-core (= 0.0.1.alpha2)
           decidim-system (= 0.0.1.alpha2)
           rails (~> 5.0.0)
    +    decidim-admin (0.0.1.alpha2)
    +      active_link_to (~> 1.0.0)
    +      decidim-core
    +      devise (~> 4.2)
    +      devise_invitable (~> 1.7.0)
    +      foundation-rails (~> 6.2.3.0)
    +      foundation_rails_helper (~> 2.0.0)
    +      jbuilder (~> 2.5)
    +      jquery-rails (~> 4.0)
    +      jquery-turbolinks (~> 2.1.0)
    +      rails (~> 5.0.0)
    +      rectify (~> 0.6)
    +      sass-rails (~> 5.0.0)
    +      turbolinks (~> 5.0.0)
     
     PATH
       remote: .
    
  • decidim-system/spec/commands/decidim/system/register_organization_spec.rb+7 0 modified
    @@ -41,6 +41,13 @@ module System
                 expect(admin).to be_admin
                 expect(admin).to be_created_by_invite
               end
    +
    +          it "sends a custom email" do
    +            expect { command.call }.to change { ActionMailer::Base.deliveries.count }.by(1)
    +
    +            last_delivery_body = ActionMailer::Base.deliveries.last.body.encoded
    +            expect(last_delivery_body).to include(URI.encode_www_form(["/admin"]))
    +          end
             end
     
             context "when the form is invalid" do
    
  • decidim-system/spec/support/action_mailer.rb+5 0 added
    @@ -0,0 +1,5 @@
    +RSpec.configure do |config|
    +  config.before(:each) do
    +    ActionMailer::Base.deliveries.clear
    +  end
    +end
    
  • Gemfile+2 0 modified
    @@ -8,5 +8,7 @@ gem "codecov", require: false, group: :test
     
     gemspec path: "."
     gemspec path: "decidim-core"
    +gemspec path: "decidim-system"
    +gemspec path: "decidim-admin"
     
     gem "rubocop"
    
  • Gemfile.lock+29 1 modified
    @@ -13,10 +13,15 @@ PATH
       remote: .
       specs:
         decidim (0.0.1.alpha2)
    +      decidim-admin (= 0.0.1.alpha2)
           decidim-core (= 0.0.1.alpha2)
           decidim-system (= 0.0.1.alpha2)
           rails (~> 5.0.0)
    -    decidim-system (0.0.1.alpha2)
    +
    +PATH
    +  remote: decidim-admin
    +  specs:
    +    decidim-admin (0.0.1.alpha2)
           active_link_to (~> 1.0.0)
           decidim-core
           devise (~> 4.2)
    @@ -47,6 +52,24 @@ PATH
           sass-rails (~> 5.0.0)
           turbolinks (~> 5.0.0)
     
    +PATH
    +  remote: decidim-system
    +  specs:
    +    decidim-system (0.0.1.alpha2)
    +      active_link_to (~> 1.0.0)
    +      decidim-core
    +      devise (~> 4.2)
    +      devise_invitable (~> 1.7.0)
    +      foundation-rails (~> 6.2.3.0)
    +      foundation_rails_helper (~> 2.0.0)
    +      jbuilder (~> 2.5)
    +      jquery-rails (~> 4.0)
    +      jquery-turbolinks (~> 2.1.0)
    +      rails (~> 5.0.0)
    +      rectify (~> 0.6)
    +      sass-rails (~> 5.0.0)
    +      turbolinks (~> 5.0.0)
    +
     GEM
       remote: https://rubygems.org/
       specs:
    @@ -174,6 +197,8 @@ GEM
           railties (>= 3.1.0)
           turbolinks
         json (2.0.2)
    +    launchy (2.4.3)
    +      addressable (~> 2.3)
         listen (3.0.8)
           rb-fsevent (~> 0.9, >= 0.9.4)
           rb-inotify (~> 0.9, >= 0.9.7)
    @@ -332,10 +357,13 @@ DEPENDENCIES
       codecov
       database_cleaner (~> 1.5.0)
       decidim!
    +  decidim-admin!
       decidim-core!
    +  decidim-system!
       factory_girl_rails
       foundation_rails_helper!
       i18n-tasks (~> 0.9.5)
    +  launchy
       listen
       pg
       rake (~> 11.0)
    
  • lib/decidim.rb+1 0 modified
    @@ -1,6 +1,7 @@
     # frozen_string_literal: true
     require "decidim/core"
     require "decidim/system"
    +require "decidim/admin"
     
     # Module declaration.
     module Decidim
    
  • Rakefile+1 1 modified
    @@ -8,7 +8,7 @@ rescue LoadError
       raise "Could not find decidim/testing_support/common_rake. You need to run this command using Bundler."
     end
     
    -DECIDIM_GEMS = %w(core system).freeze
    +DECIDIM_GEMS = %w(core system admin).freeze
     
     RSpec::Core::RakeTask.new(:spec)
     
    
b12800717a68

System admin (#9)

https://github.com/decidim/decidimOriol GualSep 23, 2016via ghsa
126 files changed · +3552 237
  • common_gemfile.rb+3 18 modified
    @@ -1,24 +1,9 @@
     # frozen_string_literal: true
     source "https://rubygems.org"
     
    -ruby '2.3.1'
    +ruby "2.3.1"
     
     gem "pg"
    +gem "listen"
     
    -# Rails 5 deps
    -gem "sass-rails", "~> 5.0"
    -gem "uglifier", ">= 1.3.0"
    -gem "coffee-rails", "~> 4.2"
    -gem "jquery-rails"
    -gem "turbolinks", "~> 5"
    -gem "jbuilder", "~> 2.5"
    -
    -group :development do
    -  gem "byebug"
    -  gem "listen"
    -end
    -
    -group :test do
    -  gem "capybara", "~> 2.4"
    -  gem "rspec-rails", "~> 3.5"
    -end
    +gem "foundation_rails_helper", github: "sgruhier/foundation_rails_helper"
    
  • decidim-core/app/assets/javascripts/decidim.js+0 1 modified
    @@ -10,7 +10,6 @@
     // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
     // about supported directives.
     //
    -//= require_tree .
     //= require foundation
     
     $(function(){ $(document).foundation(); });
    
  • decidim-core/app/assets/stylesheets/decidim.scss+0 1 modified
    @@ -10,7 +10,6 @@
      * files in this directory. Styles in this file should be added after the last require_* statement.
      * It is generally better to create a new file per style scope.
      *
    - *= require_tree .
      *= require_self
      */
     
    
  • decidim-core/app/models/decidim/organization.rb+9 0 added
    @@ -0,0 +1,9 @@
    +# frozen_string_literal: true
    +module Decidim
    +  # Organizations are one of the main models of Decidim. In a single Decidim
    +  # installation we can find many organizations and each of them can start
    +  # their own participatory processes.
    +  class Organization < ApplicationRecord
    +    validates :name, :host, uniqueness: true
    +  end
    +end
    
  • decidim-core/app/models/decidim/user.rb+16 3 modified
    @@ -2,9 +2,22 @@
     module Decidim
       # A User is a citizen that wants to join the platform to participate.
       class User < ApplicationRecord
    -    # Include default devise modules. Others available are:
    -    # :confirmable, :lockable, :timeoutable and :omniauthable
    -    devise :database_authenticatable, :registerable,
    +    devise :invitable, :database_authenticatable, :registerable,
                :recoverable, :rememberable, :trackable, :validatable
    +
    +    belongs_to :organization, foreign_key: "decidim_organization_id", class_name: Decidim::Organization
    +
    +    ROLES = %w(admin moderator official).freeze
    +
    +    validates :organization, presence: true
    +    validates :roles, inclusion: { in: ROLES }, allow_blank: true
    +
    +    # Checks if the user is an admin in the organization it belongs to. This
    +    # should probably be deleted and add a system to authorize actions.
    +    #
    +    # Returns Booolean.
    +    def admin?
    +      roles.include?("admin")
    +    end
       end
     end
    
  • decidim-core/app/views/layouts/decidim/_header.html.erb+1 0 modified
    @@ -1,2 +1,3 @@
     <h1>Decidim</h1>
    +<%= display_flash_messages %>
     <hr />
    
  • decidim-core/app/views/users/mailer/invitation_instructions.html.erb+11 0 added
    @@ -0,0 +1,11 @@
    +<p><%= t("devise.mailer.invitation_instructions.hello", email: @resource.email) %></p>
    +
    +<p><%= t("devise.mailer.invitation_instructions.someone_invited_you", application: Decidim.config.application_name) %></p>
    +
    +<p><%= link_to t("devise.mailer.invitation_instructions.accept"), accept_invitation_url(@resource, :invitation_token => @token) %></p>
    +
    +<% if @resource.invitation_due_at %>
    +  <p><%= t("devise.mailer.invitation_instructions.accept_until", due_date: l(@resource.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format')) %></p>
    +<% end %>
    +
    +<p><%= t("devise.mailer.invitation_instructions.ignore").html_safe %></p>
    
  • decidim-core/app/views/users/mailer/invitation_instructions.text.erb+11 0 added
    @@ -0,0 +1,11 @@
    +<%= t("devise.mailer.invitation_instructions.hello", email: @resource.email) %>
    +
    +<%= t("devise.mailer.invitation_instructions.someone_invited_you", application: Decidim.config.application_name) %>
    +
    +<%= accept_invitation_url(@resource, :invitation_token => @token) %>
    +
    +<% if @resource.invitation_due_at %>
    +  <%= t("devise.mailer.invitation_instructions.accept_until", due_date: l(@resource.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format')) %>
    +<% end %>
    +
    +<%= strip_tags t("devise.mailer.invitation_instructions.ignore") %>
    
  • decidim-core/bin/rails+1 1 modified
    @@ -4,7 +4,7 @@
     # installed from the root of your application.
     
     ENGINE_ROOT = File.expand_path("../..", __FILE__)
    -ENGINE_PATH = File.expand_path("../../lib/decidim/engine", __FILE__)
    +ENGINE_PATH = File.expand_path("../../lib/decidim/core/engine", __FILE__)
     
     # Set up gems listed in the Gemfile.
     ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
    
  • decidim-core/config/i18n-tasks.yml+120 0 added
    @@ -0,0 +1,120 @@
    +# i18n-tasks finds and manages missing and unused translations: https://github.com/glebm/i18n-tasks
    +
    +# The "main" locale.
    +base_locale: en
    +## All available locales are inferred from the data by default. Alternatively, specify them explicitly:
    +# locales: [es, fr]
    +## Reporting locale, default: en. Available: en, ru.
    +# internal_locale: en
    +
    +# Read and write translations.
    +data:
    +  ## Translations are read from the file system. Supported format: YAML, JSON.
    +  ## Provide a custom adapter:
    +  # adapter: I18n::Tasks::Data::FileSystem
    +
    +  # Locale files or `File.find` patterns where translations are read from:
    +  read:
    +    ## Default:
    +    # - config/locales/%{locale}.yml
    +    ## More files:
    +    # - config/locales/**/*.%{locale}.yml
    +    ## Another gem (replace %#= with %=):
    +    # - "<%#= %x[bundle show vagrant].chomp %>/templates/locales/%{locale}.yml"
    +
    +  # Locale files to write new keys to, based on a list of key pattern => file rules. Matched from top to bottom:
    +  # `i18n-tasks normalize -p` will force move the keys according to these rules
    +  write:
    +    ## For example, write devise and simple form keys to their respective files:
    +    # - ['{devise, simple_form}.*', 'config/locales/\1.%{locale}.yml']
    +    ## Catch-all default:
    +    # - config/locales/%{locale}.yml
    +
    +  ## Specify the router (see Readme for details). Valid values: conservative_router, pattern_router, or a custom class.
    +  # router: convervative_router
    +
    +  yaml:
    +    write:
    +      # do not wrap lines at 80 characters
    +      line_width: -1
    +
    +  ## Pretty-print JSON:
    +  # json:
    +  #   write:
    +  #     indent: '  '
    +  #     space: ' '
    +  #     object_nl: "\n"
    +  #     array_nl: "\n"
    +
    +# Find translate calls
    +search:
    +  ## Paths or `File.find` patterns to search in:
    +  # paths:
    +  #  - app/
    +
    +  ## Root directories for relative keys resolution.
    +  # relative_roots:
    +  #   - app/controllers
    +  #   - app/helpers
    +  #   - app/mailers
    +  #   - app/presenters
    +  #   - app/views
    +
    +  ## Files or `File.fnmatch` patterns to exclude from search. Some files are always excluded regardless of this setting:
    +  ##   %w(*.jpg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less *.yml *.json)
    +  exclude:
    +    - app/assets/images
    +    - app/assets/fonts
    +
    +  ## Alternatively, the only files or `File.fnmatch patterns` to search in `paths`:
    +  ## If specified, this settings takes priority over `exclude`, but `exclude` still applies.
    +  # only: ["*.rb", "*.html.slim"]
    +
    +  ## If `strict` is `false`, guess usages such as t("categories.#{category}.title"). The default is `true`.
    +  # strict: true
    +
    +  ## Multiple scanners can be used. Their results are merged.
    +  ## The options specified above are passed down to each scanner. Per-scanner options can be specified as well.
    +  ## See this example of a custom scanner: https://github.com/glebm/i18n-tasks/wiki/A-custom-scanner-example
    +
    +## Google Translate
    +# translation:
    +#   # Get an API key and set billing info at https://code.google.com/apis/console to use Google Translate
    +#   api_key: "AbC-dEf5"
    +
    +## Do not consider these keys missing:
    +ignore_missing:
    +- '{devise,simple_form}.*'
    +
    +## Consider these keys used:
    +ignore_unused:
    +  - '{time.formats}.*'
    +# - 'activerecord.attributes.*'
    +# - '{devise,kaminari,will_paginate}.*'
    +# - 'simple_form.{yes,no}'
    +# - 'simple_form.{placeholders,hints,labels}.*'
    +# - 'simple_form.{error_notification,required}.:'
    +
    +## Exclude these keys from the `i18n-tasks eq-base' report:
    +# ignore_eq_base:
    +#   all:
    +#     - common.ok
    +#   fr,es:
    +#     - common.brand
    +
    +## Ignore these keys completely:
    +# ignore:
    +#  - kaminari.*
    +
    +## Sometimes, it isn't possible for i18n-tasks to match the key correctly,
    +## e.g. in case of a relative key defined in a helper method.
    +## In these cases you can use the built-in PatternMapper to map patterns to keys, e.g.:
    +#
    +# <%#= I18n::Tasks.add_scanner 'I18n::Tasks::Scanners::PatternMapper',
    +#        only: %w(*.html.haml *.html.slim),
    +#        patterns: [['= title\b', '.page_title']] %>
    +#
    +# The PatternMapper can also match key literals via a special %{key} interpolation, e.g.:
    +#
    +# <%#= I18n::Tasks.add_scanner 'I18n::Tasks::Scanners::PatternMapper',
    +#        patterns: [['\bSpree\.t[( ]\s*%{key}', 'spree.%{key}']] %>
    
  • decidim-core/config/initializers/devise.rb+56 6 modified
    @@ -1,6 +1,9 @@
     # frozen_string_literal: true
     # Use this hook to configure devise mailer, warden hooks and so forth.
     # Many of these configuration options can be set straight in your model.
    +
    +require "decidim/devise_failure_app"
    +
     Devise.setup do |config|
       # The secret key used by Devise. Devise uses this key to generate
       # random tokens. Changing this key will render invalid all existing
    @@ -116,6 +119,54 @@
       # Send a notification email when the user's password is changed
       # config.send_password_change_notification = false
     
    +  # ==> Configuration for :invitable
    +  # The period the generated invitation token is valid, after
    +  # this period, the invited resource won't be able to accept the invitation.
    +  # When invite_for is 0 (the default), the invitation won't expire.
    +  config.invite_for = 2.weeks
    +
    +  # Number of invitations users can send.
    +  # - If invitation_limit is nil, there is no limit for invitations, users can
    +  # send unlimited invitations, invitation_limit column is not used.
    +  # - If invitation_limit is 0, users can't send invitations by default.
    +  # - If invitation_limit n > 0, users can send n invitations.
    +  # You can change invitation_limit column for some users so they can send more
    +  # or less invitations, even with global invitation_limit = 0
    +  # Default: nil
    +  # config.invitation_limit = 5
    +
    +  # The key to be used to check existing users when sending an invitation
    +  # and the regexp used to test it when validate_on_invite is not set.
    +  # config.invite_key = {:email => /\A[^@]+@[^@]+\z/}
    +  # config.invite_key = {:email => /\A[^@]+@[^@]+\z/, :username => nil}
    +
    +  # Flag that force a record to be valid before being actually invited
    +  # Default: false
    +  # config.validate_on_invite = true
    +
    +  # Resend invitation if user with invited status is invited again
    +  # Default: true
    +  # config.resend_invitation = false
    +
    +  # The class name of the inviting model. If this is nil,
    +  # the #invited_by association is declared to be polymorphic.
    +  # Default: nil
    +  # config.invited_by_class_name = 'User'
    +
    +  # The foreign key to the inviting model (if invited_by_class_name is set)
    +  # Default: :invited_by_id
    +  # config.invited_by_foreign_key = :invited_by_id
    +
    +  # The column name used for counter_cache column. If this is nil,
    +  # the #invited_by association is declared without counter_cache.
    +  # Default: nil
    +  # config.invited_by_counter_cache = :invitations_count
    +
    +  # Auto-login after the user accepts the invite. If this is false,
    +  # the user will need to manually log in after accepting the invite.
    +  # Default: true
    +  # config.allow_insecure_sign_in_after_accept = false
    +
       # ==> Configuration for :confirmable
       # A period that the user is allowed to access the website even without
       # confirming their account. For instance, if set to 2.days, the user will be
    @@ -223,7 +274,7 @@
       # Turn scoped views on. Before rendering "sessions/new", it will first check for
       # "users/sessions/new". It's turned off by default because it's slower if you
       # are using only default views.
    -  # config.scoped_views = false
    +  config.scoped_views = true
     
       # Configure the default scope given to Warden. By default it's the first
       # devise role declared in your routes (usually :user).
    @@ -256,10 +307,9 @@
       # If you want to use other strategies, that are not supported by Devise, or
       # change the failure app, you can configure them inside the config.warden block.
       #
    -  # config.warden do |manager|
    -  #   manager.intercept_401 = false
    -  #   manager.default_strategies(scope: :user).unshift :some_external_strategy
    -  # end
    +  config.warden do |manager|
    +    manager.failure_app = Decidim::DeviseFailureApp
    +  end
     
       # ==> Mountable engine configurations
       # When using Devise inside an engine, let's call it `MyEngine`, and this engine
    @@ -269,7 +319,7 @@
       #     mount MyEngine, at: '/my_engine'
       #
       # The router that invoked `devise_for`, in the example above, would be:
    -  config.router_name = :decidim
    +  # config.router_name = :decidim
       #
       # When using OmniAuth, Devise cannot automatically set OmniAuth path,
       # so you need to do it manually. For the users scope, it would be:
    
  • decidim-core/config/locales/devise.en.yml+0 62 removed
    @@ -1,62 +0,0 @@
    -# Additional translations at https://github.com/plataformatec/devise/wiki/I18n
    -
    -en:
    -  devise:
    -    confirmations:
    -      confirmed: "Your email address has been successfully confirmed."
    -      send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes."
    -      send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
    -    failure:
    -      already_authenticated: "You are already signed in."
    -      inactive: "Your account is not activated yet."
    -      invalid: "Invalid %{authentication_keys} or password."
    -      locked: "Your account is locked."
    -      last_attempt: "You have one more attempt before your account is locked."
    -      not_found_in_database: "Invalid %{authentication_keys} or password."
    -      timeout: "Your session expired. Please sign in again to continue."
    -      unauthenticated: "You need to sign in or sign up before continuing."
    -      unconfirmed: "You have to confirm your email address before continuing."
    -    mailer:
    -      confirmation_instructions:
    -        subject: "Confirmation instructions"
    -      reset_password_instructions:
    -        subject: "Reset password instructions"
    -      unlock_instructions:
    -        subject: "Unlock instructions"
    -      password_change:
    -        subject: "Password Changed"
    -    omniauth_callbacks:
    -      failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
    -      success: "Successfully authenticated from %{kind} account."
    -    passwords:
    -      no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
    -      send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
    -      send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
    -      updated: "Your password has been changed successfully. You are now signed in."
    -      updated_not_active: "Your password has been changed successfully."
    -    registrations:
    -      destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon."
    -      signed_up: "Welcome! You have signed up successfully."
    -      signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
    -      signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
    -      signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
    -      update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address."
    -      updated: "Your account has been updated successfully."
    -    sessions:
    -      signed_in: "Signed in successfully."
    -      signed_out: "Signed out successfully."
    -      already_signed_out: "Signed out successfully."
    -    unlocks:
    -      send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes."
    -      send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes."
    -      unlocked: "Your account has been unlocked successfully. Please sign in to continue."
    -  errors:
    -    messages:
    -      already_confirmed: "was already confirmed, please try signing in"
    -      confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
    -      expired: "has expired, please request a new one"
    -      not_found: "not found"
    -      not_locked: "was not locked"
    -      not_saved:
    -        one: "1 error prohibited this %{resource} from being saved:"
    -        other: "%{count} errors prohibited this %{resource} from being saved:"
    
  • decidim-core/config/locales/en.yml+18 0 added
    @@ -0,0 +1,18 @@
    +---
    +en:
    +  devise:
    +    mailer:
    +      invitation_instructions:
    +        accept: Accept invitation
    +        accept_until: This invitation will be due in %{due_date}.
    +        hello: Hello %{email}
    +        ignore: |-
    +          If you don't want to accept the invitation, please ignore this email.<br />
    +          Your account won't be created until you access the link above and set your password.
    +        someone_invited_you: Someone has invited you to %{application}, you can accept it through the link below.
    +  time:
    +    formats:
    +      devise:
    +        mailer:
    +          invitation_instructions:
    +            accept_until_format: "%B %d, %Y %I:%M %p"
    
  • decidim-core/config/routes.rb+2 1 modified
    @@ -1,5 +1,6 @@
     # frozen_string_literal: true
    +
     Decidim::Core::Engine.routes.draw do
    -  devise_for :users, class_name: "Decidim::User", module: :devise
    +  devise_for :users, class_name: "Decidim::User", module: :'decidim/devise', router_name: :decidim
       root to: "home#show"
     end
    
  • decidim-core/db/migrate/20160919104837_create_decidim_organizations.rb+13 0 added
    @@ -0,0 +1,13 @@
    +class CreateDecidimOrganizations < ActiveRecord::Migration[5.0]
    +  def change
    +    create_table :decidim_organizations do |t|
    +      t.string :name, null: false
    +      t.string :host, null: false
    +
    +      t.timestamps
    +    end
    +
    +    add_index :decidim_organizations, :name, unique: true
    +    add_index :decidim_organizations, :host, unique: true
    +  end
    +end
    
  • decidim-core/db/migrate/20160920140207_devise_invitable_add_to_decidim_users.rb+23 0 added
    @@ -0,0 +1,23 @@
    +class DeviseInvitableAddToDecidimUsers < ActiveRecord::Migration
    +  def up
    +    change_table :decidim_users do |t|
    +      t.string     :invitation_token
    +      t.datetime   :invitation_created_at
    +      t.datetime   :invitation_sent_at
    +      t.datetime   :invitation_accepted_at
    +      t.integer    :invitation_limit
    +      t.references :invited_by, polymorphic: true
    +      t.integer    :invitations_count, default: 0
    +      t.index      :invitations_count
    +      t.index      :invitation_token, unique: true # for invitable
    +      t.index      :invited_by_id
    +    end
    +  end
    +
    +  def down
    +    change_table :decidim_users do |t|
    +      t.remove_references :invited_by, polymorphic: true
    +      t.remove :invitations_count, :invitation_limit, :invitation_sent_at, :invitation_accepted_at, :invitation_token, :invitation_created_at
    +    end
    +  end
    +end
    
  • decidim-core/db/migrate/20160920141039_user_belongs_to_organization.rb+5 0 added
    @@ -0,0 +1,5 @@
    +class UserBelongsToOrganization < ActiveRecord::Migration[5.0]
    +  def change
    +    add_reference :decidim_users, :decidim_organization, index: true, foreign_key: true
    +  end
    +end
    
  • decidim-core/db/migrate/20160920141151_user_has_roles.rb+5 0 added
    @@ -0,0 +1,5 @@
    +class UserHasRoles < ActiveRecord::Migration[5.0]
    +  def change
    +    add_column :decidim_users, :roles, :string, array: true, default: []
    +  end
    +end
    
  • decidim-core/decidim-core.gemspec+16 1 modified
    @@ -18,7 +18,22 @@ Gem::Specification.new do |s|
       s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
     
       s.add_dependency "rails", Decidim.rails_version
    -  s.add_dependency "devise"
    +  s.add_dependency "devise", "~> 4.2"
    +  s.add_dependency "rectify", "~> 0.6"
       s.add_dependency "foundation-rails", "~> 6.2.3.0"
       s.add_dependency "sass-rails", "~> 5.0.0"
    +  s.add_dependency "jquery-rails", "~> 4.0"
    +  s.add_dependency "turbolinks", Decidim.rails_version
    +  s.add_dependency "jquery-turbolinks", "~> 2.1.0"
    +  s.add_dependency "jbuilder", "~> 2.5"
    +  s.add_dependency "foundation_rails_helper", "~> 2.0.0"
    +  s.add_dependency "active_link_to", "~> 1.0.0"
    +
    +  s.add_development_dependency "factory_girl_rails"
    +  s.add_development_dependency "database_cleaner", "~> 1.5.0"
    +  s.add_development_dependency "capybara", "~> 2.4"
    +  s.add_development_dependency "rspec-rails", "~> 3.5"
    +  s.add_development_dependency "byebug"
    +  s.add_development_dependency "wisper-rspec"
    +  s.add_development_dependency "i18n-tasks", "~> 0.9.5"
     end
    
  • decidim-core/Gemfile+3 0 modified
    @@ -13,3 +13,6 @@ gemspec
     
     # To use a debugger
     # gem 'byebug', group: [:development, :test]
    +
    +gemspec path: "../decidim-system"
    +gemspec path: ".."
    
  • decidim-core/Gemfile.lock+126 17 modified
    @@ -1,11 +1,55 @@
    +GIT
    +  remote: git://github.com/sgruhier/foundation_rails_helper.git
    +  revision: 809f127b76747b53fb199ff4e5d3f06aa3d35f97
    +  specs:
    +    foundation_rails_helper (2.0.0)
    +      actionpack (>= 4.1)
    +      activemodel (>= 4.1)
    +      activesupport (>= 4.1)
    +      railties (>= 4.1)
    +      tzinfo (~> 1.2, >= 1.2.2)
    +
    +PATH
    +  remote: ../decidim-system
    +  specs:
    +    decidim-system (0.0.1.alpha2)
    +      active_link_to (~> 1.0.0)
    +      decidim-core
    +      devise (~> 4.2)
    +      devise_invitable (~> 1.7.0)
    +      foundation-rails (~> 6.2.3.0)
    +      foundation_rails_helper (~> 2.0.0)
    +      jbuilder (~> 2.5)
    +      jquery-rails (~> 4.0)
    +      jquery-turbolinks (~> 2.1.0)
    +      rails (~> 5.0.0)
    +      rectify (~> 0.6)
    +      sass-rails (~> 5.0.0)
    +      turbolinks (~> 5.0.0)
    +
    +PATH
    +  remote: ..
    +  specs:
    +    decidim (0.0.1.alpha2)
    +      decidim-core (= 0.0.1.alpha2)
    +      decidim-system (= 0.0.1.alpha2)
    +      rails (~> 5.0.0)
    +
     PATH
       remote: .
       specs:
         decidim-core (0.0.1.alpha2)
    -      devise
    +      active_link_to (~> 1.0.0)
    +      devise (~> 4.2)
           foundation-rails (~> 6.2.3.0)
    +      foundation_rails_helper (~> 2.0.0)
    +      jbuilder (~> 2.5)
    +      jquery-rails (~> 4.0)
    +      jquery-turbolinks (~> 2.1.0)
           rails (~> 5.0.0)
    +      rectify (~> 0.6)
           sass-rails (~> 5.0.0)
    +      turbolinks (~> 5.0.0)
     
     GEM
       remote: https://rubygems.org/
    @@ -33,6 +77,8 @@ GEM
           erubis (~> 2.7.0)
           rails-dom-testing (~> 2.0)
           rails-html-sanitizer (~> 1.0, >= 1.0.2)
    +    active_link_to (1.0.3)
    +      actionpack
         activejob (5.0.0.1)
           activesupport (= 5.0.0.1)
           globalid (>= 0.3.6)
    @@ -49,6 +95,11 @@ GEM
           tzinfo (~> 1.1)
         addressable (2.4.0)
         arel (7.1.2)
    +    ast (2.3.0)
    +    axiom-types (0.1.1)
    +      descendants_tracker (~> 0.0.4)
    +      ice_nine (~> 0.11.0)
    +      thread_safe (~> 0.3, >= 0.3.1)
         babel-source (5.8.35)
         babel-transpiler (0.7.0)
           babel-source (>= 4.0, < 6)
    @@ -63,38 +114,67 @@ GEM
           rack (>= 1.0.0)
           rack-test (>= 0.5.4)
           xpath (~> 2.0)
    -    coffee-rails (4.2.1)
    -      coffee-script (>= 2.2.0)
    -      railties (>= 4.0.0, < 5.2.x)
    -    coffee-script (2.4.1)
    -      coffee-script-source
    -      execjs
    -    coffee-script-source (1.10.0)
    +    coercible (1.0.0)
    +      descendants_tracker (~> 0.0.1)
         concurrent-ruby (1.0.2)
    +    database_cleaner (1.5.3)
    +    descendants_tracker (0.0.4)
    +      thread_safe (~> 0.3, >= 0.3.1)
         devise (4.2.0)
           bcrypt (~> 3.0)
           orm_adapter (~> 0.1)
           railties (>= 4.1.0, < 5.1)
           responders
           warden (~> 1.2.3)
    +    devise_invitable (1.7.0)
    +      actionmailer (>= 4.0.0)
    +      devise (>= 4.0.0)
         diff-lcs (1.2.5)
    +    easy_translate (0.5.0)
    +      json
    +      thread
    +      thread_safe
    +    equalizer (0.0.11)
         erubis (2.7.0)
         execjs (2.7.0)
    +    factory_girl (4.7.0)
    +      activesupport (>= 3.0.0)
    +    factory_girl_rails (4.7.0)
    +      factory_girl (~> 4.7.0)
    +      railties (>= 3.0.0)
         ffi (1.9.14)
         foundation-rails (6.2.3.0)
           railties (>= 3.1.0)
           sass (>= 3.3.0, < 3.5)
           sprockets-es6 (>= 0.9.0)
         globalid (0.3.7)
           activesupport (>= 4.1.0)
    +    highline (1.7.8)
         i18n (0.7.0)
    +    i18n-tasks (0.9.5)
    +      activesupport (>= 4.0.2)
    +      ast (>= 2.1.0)
    +      easy_translate (>= 0.5.0)
    +      erubis
    +      highline (>= 1.7.3)
    +      i18n
    +      parser (>= 2.2.3.0)
    +      term-ansicolor (>= 1.3.2)
    +      terminal-table (>= 1.5.1)
    +    ice_nine (0.11.2)
         jbuilder (2.6.0)
           activesupport (>= 3.0.0, < 5.1)
           multi_json (~> 1.2)
         jquery-rails (4.2.1)
           rails-dom-testing (>= 1, < 3)
           railties (>= 4.2.0)
           thor (>= 0.14, < 2.0)
    +    jquery-turbolinks (2.1.0)
    +      railties (>= 3.1.0)
    +      turbolinks
    +    json (2.0.2)
    +    launchy (2.4.3)
    +      addressable (~> 2.3)
         listen (3.0.8)
           rb-fsevent (~> 0.9, >= 0.9.4)
           rb-inotify (~> 0.9, >= 0.9.7)
    @@ -114,6 +194,8 @@ GEM
           mini_portile2 (~> 2.1.0)
           pkg-config (~> 1.1.7)
         orm_adapter (0.5.0)
    +    parser (2.3.1.4)
    +      ast (~> 2.2)
         pg (0.18.4)
         pkg-config (1.1.7)
         rack (2.0.1)
    @@ -146,8 +228,18 @@ GEM
         rb-fsevent (0.9.7)
         rb-inotify (0.9.7)
           ffi (>= 0.5.0)
    +    rectify (0.6.1)
    +      activemodel (>= 4.1.0)
    +      activerecord (>= 4.1.0)
    +      activesupport (>= 4.1.0)
    +      virtus (~> 1.0.5)
    +      wisper (>= 1.6.1)
         responders (2.3.0)
           railties (>= 4.2.0, < 5.1)
    +    rspec (3.5.0)
    +      rspec-core (~> 3.5.0)
    +      rspec-expectations (~> 3.5.0)
    +      rspec-mocks (~> 3.5.0)
         rspec-core (3.5.2)
           rspec-support (~> 3.5.0)
         rspec-expectations (3.5.0)
    @@ -179,44 +271,61 @@ GEM
           babel-source (>= 5.8.11)
           babel-transpiler
           sprockets (>= 3.0.0)
    -    sprockets-rails (3.1.1)
    +    sprockets-rails (3.2.0)
           actionpack (>= 4.0)
           activesupport (>= 4.0)
           sprockets (>= 3.0.0)
    +    term-ansicolor (1.3.2)
    +      tins (~> 1.0)
    +    terminal-table (1.7.3)
    +      unicode-display_width (~> 1.1.1)
         thor (0.19.1)
    +    thread (0.2.2)
         thread_safe (0.3.5)
         tilt (2.0.5)
    +    tins (1.12.0)
         turbolinks (5.0.1)
           turbolinks-source (~> 5)
         turbolinks-source (5.0.0)
         tzinfo (1.2.2)
           thread_safe (~> 0.1)
    -    uglifier (3.0.2)
    -      execjs (>= 0.3.0, < 3)
    +    unicode-display_width (1.1.1)
    +    virtus (1.0.5)
    +      axiom-types (~> 0.1)
    +      coercible (~> 1.0)
    +      descendants_tracker (~> 0.0, >= 0.0.3)
    +      equalizer (~> 0.0, >= 0.0.9)
         warden (1.2.6)
           rack (>= 1.0)
         websocket-driver (0.6.4)
           websocket-extensions (>= 0.1.0)
         websocket-extensions (0.1.2)
    +    wisper (1.6.1)
    +    wisper-rspec (0.0.2)
         xpath (2.0.0)
           nokogiri (~> 1.3)
     
     PLATFORMS
       ruby
     
     DEPENDENCIES
    +  bundler (~> 1.12)
       byebug
       capybara (~> 2.4)
    -  coffee-rails (~> 4.2)
    +  database_cleaner (~> 1.5.0)
    +  decidim!
       decidim-core!
    -  jbuilder (~> 2.5)
    -  jquery-rails
    +  decidim-system!
    +  factory_girl_rails
    +  foundation_rails_helper!
    +  i18n-tasks (~> 0.9.5)
    +  launchy
       listen
       pg
    +  rake (~> 11.0)
    +  rspec (~> 3.0)
       rspec-rails (~> 3.5)
    -  sass-rails (~> 5.0)
    -  turbolinks (~> 5)
    -  uglifier (>= 1.3.0)
    +  wisper-rspec
     
     RUBY VERSION
        ruby 2.3.1p112
    
  • decidim-core/lib/decidim/core/engine.rb+12 0 modified
    @@ -2,6 +2,18 @@
     require "rails"
     require "active_support/all"
     
    +require "devise"
    +require "devise_invitable"
    +require "jquery-rails"
    +require "sass-rails"
    +require "turbolinks"
    +require "jquery-turbolinks"
    +require "foundation-rails"
    +require "foundation_rails_helper"
    +require "jbuilder"
    +require "active_link_to"
    +require "rectify"
    +
     module Decidim
       module Core
         # Decidim's core Rails Engine.
    
  • decidim-core/lib/decidim/core.rb+0 3 modified
    @@ -1,9 +1,6 @@
     # frozen_string_literal: true
     require "decidim/core/engine"
     require "decidim/core/version"
    -require "devise"
    -
    -require "foundation-rails"
     
     # Decidim configuration.
     module Decidim
    
  • decidim-core/lib/decidim/devise_failure_app.rb+36 0 added
    @@ -0,0 +1,36 @@
    +# encoding: utf-8
    +# frozen_string_literal: true
    +
    +module Decidim
    +  # We've provided a custom class in order to be able to deactivate the
    +  # script_name hack that doesn't seem to be affecting us (it is actually
    +  # introducing a bug).
    +  class DeviseFailureApp < Devise::FailureApp
    +    def scope_url
    +      opts  = {}
    +
    +      # Initialize script_name with nil to prevent infinite loops in
    +      # authenticated mounted engines in rails 4.2 and 5.0
    +
    +      # The line below is what we commented LOL ^^
    +      # opts[:script_name] = nil
    +
    +      route = route(scope)
    +
    +      opts[:format] = request_format unless skip_format?
    +
    +      opts[:script_name] = relative_url_root if relative_url_root?
    +
    +      router_name = Devise.mappings[scope].router_name || Devise.available_router_name
    +      context = send(router_name)
    +
    +      if context.respond_to?(route)
    +        context.send(route, opts)
    +      elsif respond_to?(:root_url)
    +        root_url(opts)
    +      else
    +        "/"
    +      end
    +    end
    +  end
    +end
    
  • decidim-core/lib/generators/decidim/install_generator.rb+12 2 modified
    @@ -17,6 +17,7 @@ class InstallGenerator < Rails::Generators::Base
                                  desc: "Run migrations after installing decidim"
     
           def install
    +        route "mount Decidim::System::Engine => '/system'"
             route "mount Decidim::Core::Engine => '/'"
           end
     
    @@ -60,16 +61,25 @@ def append_assets
             append_file "app/assets/javascripts/application.js", "//= require decidim"
             inject_into_file "app/assets/stylesheets/application.css",
                              before: "*= require_tree ." do
    -          "*= require decidim\n "
    +          "*= require decidim\n"
    +        end
    +      end
    +
    +      def test_mail_host
    +        inject_into_file "config/environments/test.rb",
    +                         after: "config.action_mailer.delivery_method = :test" do
    +          "\n  config.action_mailer.default_url_options = { host: \"test.citizencorp.com\" }"
             end
           end
     
           private
     
           def prepare_database
    -        rake "db:drop"
    +        rake "db:environment:set RAILS_ENV=development"
    +        rake "db:drop:all"
             rake "db:create"
             rake "db:migrate"
    +        rake "db:test:prepare"
           end
         end
       end
    
  • decidim-core/lib/tasks/factory_girl.rake+16 0 added
    @@ -0,0 +1,16 @@
    +# frozen_string_literal: true
    +namespace :factory_girl do
    +  desc "Verify that all FactoryGirl factories are valid"
    +  task lint: :environment do
    +    if Rails.env.test?
    +      begin
    +        DatabaseCleaner.start
    +        FactoryGirl.lint
    +      ensure
    +        DatabaseCleaner.clean
    +      end
    +    else
    +      system("bundle exec rake factory_girl:lint RAILS_ENV='test'")
    +    end
    +  end
    +end
    
  • decidim-core/Rakefile+9 1 modified
    @@ -10,7 +10,7 @@ require "rdoc/task"
     
     RDoc::Task.new(:rdoc) do |rdoc|
       rdoc.rdoc_dir = "rdoc"
    -  rdoc.title    = "Decidim"
    +  rdoc.title    = "Decidim::Core"
       rdoc.options << "--line-numbers"
       rdoc.rdoc_files.include("README.md")
       rdoc.rdoc_files.include("lib/**/*.rb")
    @@ -20,8 +20,16 @@ load "rails/tasks/statistics.rake"
     
     require "bundler/gem_tasks"
     
    +task default: :test
    +
     desc "Generates a dummy app for testing"
     task :test_app do
       ENV["LIB_NAME"] = "decidim/core"
       Rake::Task["common:test_app"].invoke
     end
    +
    +task test: :test_app do
    +  Dir.chdir(File.dirname(__FILE__).to_s) do
    +    sh "rspec spec"
    +  end
    +end
    
  • decidim-core/spec/factories.rb+25 0 added
    @@ -0,0 +1,25 @@
    +FactoryGirl.define do
    +  factory :organization, class: Decidim::Organization do
    +    sequence(:name) { |n| "Citizen Corp ##{n}" }
    +    sequence(:host) { |n| "#{n}.citizen.corp" }
    +  end
    +
    +  factory :user, class: Decidim::User do
    +    sequence(:email)      { |n| "user#{n}@citizen.corp" }
    +    password              "password1234"
    +    password_confirmation "password1234"
    +    organization
    +
    +    trait :admin do
    +      roles ["admin"]
    +    end
    +
    +    trait :moderator do
    +      roles ["moderator"]
    +    end
    +
    +    trait :official do
    +      roles ["official"]
    +    end
    +  end
    +end
    
  • decidim-core/spec/i18n_spec.rb+18 0 added
    @@ -0,0 +1,18 @@
    +# frozen_string_literal: true
    +require 'i18n/tasks'
    +
    +RSpec.describe 'I18n' do
    +  let(:i18n) { I18n::Tasks::BaseTask.new }
    +  let(:missing_keys) { i18n.missing_keys }
    +  let(:unused_keys) { i18n.unused_keys }
    +
    +  it 'does not have missing keys' do
    +    expect(missing_keys).to be_empty,
    +      "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them"
    +  end
    +
    +  it 'does not have unused keys' do
    +    expect(unused_keys).to be_empty,
    +      "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them"
    +  end
    +end
    
  • decidim-core/spec/models/decidim/organization_spec.rb+10 0 added
    @@ -0,0 +1,10 @@
    +# frozen_string_literal: true
    +require "spec_helper"
    +
    +describe "Organization", :db do
    +  let(:organization) { create(:organization) }
    +
    +  it "is valid" do
    +    expect(organization).to be_valid
    +  end
    +end
    
  • decidim-core/spec/models/decidim/user_spec.rb+28 0 added
    @@ -0,0 +1,28 @@
    +# frozen_string_literal: true
    +require "spec_helper"
    +
    +describe "User", :db do
    +  let(:user) { build(:user) }
    +
    +  it "is valid" do
    +    expect(user).to be_valid
    +  end
    +
    +  describe "admin?" do
    +    context "when the user is an admin" do
    +      before do
    +        user.roles = ["admin"]
    +      end
    +
    +      it { expect(user.admin?).to eq true }
    +    end
    +
    +    context "when the user is not an admin" do
    +      before do
    +        user.roles = []
    +      end
    +
    +      it { expect(user.admin?).to eq false }
    +    end
    +  end
    +end
    
  • decidim-core/spec/spec_helper.rb+3 0 modified
    @@ -17,6 +17,9 @@
     end
     
     require "rspec/rails"
    +require "factory_girl_rails"
    +require "database_cleaner"
    +require "byebug"
     
     # Requires supporting files with custom matchers and macros, etc,
     # in ./support/ and its subdirectories.
    
  • decidim-core/spec/support/database_cleaner.rb+12 0 added
    @@ -0,0 +1,12 @@
    +RSpec.configure do |config|
    +  config.before(:suite) do
    +    DatabaseCleaner.strategy = :transaction
    +    DatabaseCleaner.clean_with(:truncation)
    +  end
    +
    +  config.around(:each) do |example|
    +    DatabaseCleaner.cleaning do
    +      example.run
    +    end
    +  end
    +end
    
  • decidim-core/spec/support/factory_girl.rb+3 0 added
    @@ -0,0 +1,3 @@
    +RSpec.configure do |config|
    +  config.include FactoryGirl::Syntax::Methods
    +end
    
  • decidim-core/test/controllers/decidim/home_controller_test.rb+0 12 removed
    @@ -1,12 +0,0 @@
    -# frozen_string_literal: true
    -require "test_helper"
    -
    -module Decidim
    -  class HomeControllerTest < ActionDispatch::IntegrationTest
    -    include Engine.routes.url_helpers
    -
    -    # test "the truth" do
    -    #   assert true
    -    # end
    -  end
    -end
    
  • decidim-core/test/fixtures/decidim/users.yml+0 11 removed
    @@ -1,11 +0,0 @@
    -# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
    -
    -# This model initially had no columns defined. If you add columns to the
    -# model remove the '{}' from the fixture names and add the columns immediately
    -# below each fixture, per the syntax in the comments below
    -#
    -one: {}
    -# column: value
    -#
    -two: {}
    -# column: value
    
  • decidim-core/test/lib/generators/decidim/decidim_generator_test.rb+0 17 removed
    @@ -1,17 +0,0 @@
    -# frozen_string_literal: true
    -require "test_helper"
    -require "generators/decidim/decidim_generator"
    -
    -module Decidim
    -  class DecidimGeneratorTest < Rails::Generators::TestCase
    -    tests DecidimGenerator
    -    destination Rails.root.join("tmp/generators")
    -    setup :prepare_destination
    -
    -    # test "generator runs without errors" do
    -    #   assert_nothing_raised do
    -    #     run_generator ["arguments"]
    -    #   end
    -    # end
    -  end
    -end
    
  • decidim-core/test/lib/generators/decidim/installation_generator_test.rb+0 17 removed
    @@ -1,17 +0,0 @@
    -# frozen_string_literal: true
    -require "test_helper"
    -require "generators/installation/installation_generator"
    -
    -module Decidim
    -  class InstallationGeneratorTest < Rails::Generators::TestCase
    -    tests InstallationGenerator
    -    destination Rails.root.join("tmp/generators")
    -    setup :prepare_destination
    -
    -    # test "generator runs without errors" do
    -    #   assert_nothing_raised do
    -    #     run_generator ["arguments"]
    -    #   end
    -    # end
    -  end
    -end
    
  • decidim-core/test/lib/generators/decidim/install_generator_test.rb+0 17 removed
    @@ -1,17 +0,0 @@
    -# frozen_string_literal: true
    -require "test_helper"
    -require "generators/install/install_generator"
    -
    -module Decidim
    -  class InstallGeneratorTest < Rails::Generators::TestCase
    -    tests InstallGenerator
    -    destination Rails.root.join("tmp/generators")
    -    setup :prepare_destination
    -
    -    # test "generator runs without errors" do
    -    #   assert_nothing_raised do
    -    #     run_generator ["arguments"]
    -    #   end
    -    # end
    -  end
    -end
    
  • decidim-core/test/models/decidim/user_test.rb+0 10 removed
    @@ -1,10 +0,0 @@
    -# frozen_string_literal: true
    -require "test_helper"
    -
    -module Decidim
    -  class UserTest < ActiveSupport::TestCase
    -    # test "the truth" do
    -    #   assert true
    -    # end
    -  end
    -end
    
  • decidim.gemspec+2 1 modified
    @@ -23,9 +23,10 @@ Gem::Specification.new do |spec|
       spec.require_paths = ["lib"]
     
       spec.add_dependency "decidim-core", Decidim.version
    +  spec.add_dependency "decidim-system", Decidim.version
       spec.add_dependency "rails", Decidim.rails_version
     
       spec.add_development_dependency "bundler", "~> 1.12"
    -  spec.add_development_dependency "rake", "~> 10.0"
    +  spec.add_development_dependency "rake", "~> 11.0"
       spec.add_development_dependency "rspec", "~> 3.0"
     end
    
  • decidim-system/app/assets/config/decidim_system_manifest.js+2 0 added
    @@ -0,0 +1,2 @@
    +//= link_directory ../javascripts/decidim/system .js
    +//= link_directory ../stylesheets/decidim/system .css
    
  • decidim-system/app/assets/images/decidim/system/.keep+0 0 renamed
  • decidim-system/app/assets/javascripts/decidim/system.js+21 0 added
    @@ -0,0 +1,21 @@
    +// This is a manifest file that'll be compiled into application.js, which will include all the files
    +// listed below.
    +//
    +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
    +// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
    +//
    +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
    +// compiled file. JavaScript code in this file should be added after the last require_* statement.
    +//
    +// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
    +// about supported directives.
    +//
    +//= require jquery
    +//= require jquery.turbolinks
    +//= require jquery_ujs
    +//= require foundation
    +//= require_self
    +
    +$(function(){ $(document).foundation(); });
    +
    +//= require turbolinks
    
  • decidim-system/app/assets/stylesheets/decidim/system/_actions.scss+8 0 added
    @@ -0,0 +1,8 @@
    +.actions{
    +  text-align: right;
    +
    +  &.title{
    +    margin-bottom: $global-margin;
    +    font-size: 1.2em;
    +  }
    +}
    \ No newline at end of file
    
  • decidim-system/app/assets/stylesheets/decidim/system/_foundation_and_overrides.scss+52 0 added
    @@ -0,0 +1,52 @@
    +@charset "utf-8";
    +
    +@import "settings";
    +@import "foundation";
    +
    +// If you'd like to include motion-ui the foundation-rails gem comes prepackaged with it, uncomment the 3 @imports, if you are not using the gem you need to install the motion-ui sass package.
    +//
    +// @import 'motion-ui/motion-ui';
    +
    +// We include everything by default.  To slim your CSS, remove components you don't use.
    +
    +@include foundation-global-styles;
    +@include foundation-flex-grid;
    +@include foundation-typography;
    +@include foundation-button;
    +@include foundation-forms;
    +@include foundation-visibility-classes;
    +@include foundation-flex-classes;
    +@include foundation-accordion;
    +@include foundation-accordion-menu;
    +@include foundation-badge;
    +@include foundation-breadcrumbs;
    +@include foundation-button-group;
    +@include foundation-callout;
    +@include foundation-close-button;
    +@include foundation-drilldown-menu;
    +@include foundation-dropdown;
    +@include foundation-dropdown-menu;
    +@include foundation-flex-video;
    +@include foundation-label;
    +@include foundation-media-object;
    +@include foundation-menu;
    +@include foundation-menu-icon;
    +@include foundation-off-canvas;
    +@include foundation-orbit;
    +@include foundation-pagination;
    +@include foundation-progress-bar;
    +@include foundation-slider;
    +@include foundation-sticky;
    +@include foundation-reveal;
    +@include foundation-switch;
    +@include foundation-table;
    +@include foundation-tabs;
    +@include foundation-thumbnail;
    +@include foundation-title-bar;
    +@include foundation-tooltip;
    +@include foundation-top-bar;
    +
    +// If you'd like to include motion-ui the foundation-rails gem comes prepackaged with it, uncomment the 3 @imports, if you are not using the gem you need to install the motion-ui sass package.
    +//
    +// @include motion-ui-transitions;
    +// @include motion-ui-animations;
    
  • decidim-system/app/assets/stylesheets/decidim/system/_layout.scss+16 0 added
    @@ -0,0 +1,16 @@
    +.off-canvas, .sidebar, .off-canvas-wrapper{
    +  min-height: 100vh;
    +}
    +
    +.off-canvas{
    +  color: #fff;
    +}
    +
    +.main-content{
    +  padding: $global-padding 0;
    +  min-height: 100vh;
    +}
    +
    +.page-title{
    +  margin-bottom: $global-margin;
    +}
    \ No newline at end of file
    
  • decidim-system/app/assets/stylesheets/decidim/system/_login.scss+36 0 added
    @@ -0,0 +1,36 @@
    +body.login{
    +  background: $light-gray;
    +  position: relative;
    +
    +  .login-form-wrapper{
    +    @include grid-row;
    +    @include flex;
    +
    +    height: 100vh;
    +
    +    .login-form{
    +      @include grid-column(12);
    +      @include grid-column-position(center);
    +
    +      @include breakpoint(medium){
    +        @include grid-column(8);
    +        @include grid-column-position(center);
    +      }
    +
    +      @include breakpoint(large){
    +        @include grid-column(6);
    +        @include grid-column-position(center);
    +      }
    +    }
    +
    +    @include breakpoint(medium){
    +      @include flex-align(center, middle);
    +      transform: translateY(-8%);
    +    }
    +
    +    .login-form-inner{
    +      background-color: $white;
    +      padding: $global-padding;
    +    }
    +  }
    +}
    \ No newline at end of file
    
  • decidim-system/app/assets/stylesheets/decidim/system.scss+8 0 added
    @@ -0,0 +1,8 @@
    +@import 'https://fonts.googleapis.com/css?family=Montserrat:400,700';
    +
    +@import "system/foundation_and_overrides";
    +@import "system/layout";
    +@import "system/login";
    +@import "system/sidebar";
    +@import "system/tables";
    +@import "system/actions";
    
  • decidim-system/app/assets/stylesheets/decidim/system/_settings.scss+566 0 added
    @@ -0,0 +1,566 @@
    +//  Foundation for Sites Settings
    +//  -----------------------------
    +//
    +//  Table of Contents:
    +//
    +//   1. Global
    +//   2. Breakpoints
    +//   3. The Grid
    +//   4. Base Typography
    +//   5. Typography Helpers
    +//   6. Abide
    +//   7. Accordion
    +//   8. Accordion Menu
    +//   9. Badge
    +//  10. Breadcrumbs
    +//  11. Button
    +//  12. Button Group
    +//  13. Callout
    +//  14. Close Button
    +//  15. Drilldown
    +//  16. Dropdown
    +//  17. Dropdown Menu
    +//  18. Flex Video
    +//  19. Forms
    +//  20. Label
    +//  21. Media Object
    +//  22. Menu
    +//  23. Meter
    +//  24. Off-canvas
    +//  25. Orbit
    +//  26. Pagination
    +//  27. Progress Bar
    +//  28. Reveal
    +//  29. Slider
    +//  30. Switch
    +//  31. Table
    +//  32. Tabs
    +//  33. Thumbnail
    +//  34. Title Bar
    +//  35. Tooltip
    +//  36. Top Bar
    +
    +@import "util/util";
    +
    +// 1. Global
    +// ---------
    +
    +// $global-font-size: 100%;
    +// $global-width: rem-calc(1200);
    +// $global-lineheight: 1.5;
    +$foundation-palette: (
    +  primary: #2199e8,
    +  secondary: #777,
    +  success: #3adb76,
    +  warning: #ffae00,
    +  alert: #ec5840,
    +);
    +$light-gray: #e6e6e6;
    +// $medium-gray: #cacaca;
    +// $dark-gray: #8a8a8a;
    +$black: #232323;
    +// $white: #fefefe;
    +// $body-background: $white;
    +// $body-font-color: $black;
    +$body-font-family: 'Montserrat', sans-serif;
    +// $body-antialiased: true;
    +// $global-margin: 1rem;
    +// $global-padding: 1rem;
    +// $global-weight-normal: normal;
    +// $global-weight-bold: bold;
    +// $global-radius: 0;
    +// $global-text-direction: ltr;
    +$global-flexbox: true;
    +// $print-transparent-backgrounds: true;
    +
    +@include add-foundation-colors;
    +
    +// 2. Breakpoints
    +// --------------
    +
    +// $breakpoints: (
    +//   small: 0,
    +//   medium: 640px,
    +//   large: 1024px,
    +//   xlarge: 1200px,
    +//   xxlarge: 1440px,
    +// );
    +// $breakpoint-classes: (small medium large);
    +
    +// 3. The Grid
    +// -----------
    +
    +// $grid-row-width: $global-width;
    +// $grid-column-count: 12;
    +// $grid-column-gutter: (
    +//   small: 20px,
    +//   medium: 30px,
    +// );
    +// $grid-column-align-edge: true;
    +// $block-grid-max: 8;
    +
    +// 4. Base Typography
    +// ------------------
    +
    +// $header-font-family: $body-font-family;
    +// $header-font-weight: $global-weight-normal;
    +// $header-font-style: normal;
    +// $font-family-monospace: Consolas, 'Liberation Mono', Courier, monospace;
    +// $header-sizes: (
    +//   small: (
    +//     'h1': 24,
    +//     'h2': 20,
    +//     'h3': 19,
    +//     'h4': 18,
    +//     'h5': 17,
    +//     'h6': 16,
    +//   ),
    +//   medium: (
    +//     'h1': 48,
    +//     'h2': 40,
    +//     'h3': 31,
    +//     'h4': 25,
    +//     'h5': 20,
    +//     'h6': 16,
    +//   ),
    +// );
    +// $header-color: inherit;
    +// $header-lineheight: 1.4;
    +// $header-margin-bottom: 0.5rem;
    +// $header-text-rendering: optimizeLegibility;
    +// $small-font-size: 80%;
    +// $header-small-font-color: $medium-gray;
    +// $paragraph-lineheight: 1.6;
    +// $paragraph-margin-bottom: 1rem;
    +// $paragraph-text-rendering: optimizeLegibility;
    +// $code-color: $black;
    +// $code-font-family: $font-family-monospace;
    +// $code-font-weight: $global-weight-normal;
    +// $code-background: $light-gray;
    +// $code-border: 1px solid $medium-gray;
    +// $code-padding: rem-calc(2 5 1);
    +// $anchor-color: $primary-color;
    +// $anchor-color-hover: scale-color($anchor-color, $lightness: -14%);
    +// $anchor-text-decoration: none;
    +// $anchor-text-decoration-hover: none;
    +// $hr-width: $global-width;
    +// $hr-border: 1px solid $medium-gray;
    +// $hr-margin: rem-calc(20) auto;
    +// $list-lineheight: $paragraph-lineheight;
    +// $list-margin-bottom: $paragraph-margin-bottom;
    +// $list-style-type: disc;
    +// $list-style-position: outside;
    +// $list-side-margin: 1.25rem;
    +// $list-nested-side-margin: 1.25rem;
    +// $defnlist-margin-bottom: 1rem;
    +// $defnlist-term-weight: $global-weight-bold;
    +// $defnlist-term-margin-bottom: 0.3rem;
    +// $blockquote-color: $dark-gray;
    +// $blockquote-padding: rem-calc(9 20 0 19);
    +// $blockquote-border: 1px solid $medium-gray;
    +// $cite-font-size: rem-calc(13);
    +// $cite-color: $dark-gray;
    +// $keystroke-font: $font-family-monospace;
    +// $keystroke-color: $black;
    +// $keystroke-background: $light-gray;
    +// $keystroke-padding: rem-calc(2 4 0);
    +// $keystroke-radius: $global-radius;
    +// $abbr-underline: 1px dotted $black;
    +
    +// 5. Typography Helpers
    +// ---------------------
    +
    +// $lead-font-size: $global-font-size * 1.25;
    +// $lead-lineheight: 1.6;
    +// $subheader-lineheight: 1.4;
    +// $subheader-color: $dark-gray;
    +// $subheader-font-weight: $global-weight-normal;
    +// $subheader-margin-t#23262Cop: 0.2rem;
    +// $subheader-margin-bottom: 0.5rem;
    +// $stat-font-size: 2.5rem;
    +
    +// 6. Abide
    +// --------
    +
    +// $abide-inputs: true;
    +// $abide-labels: true;
    +// $input-background-invalid: map-get($foundation-palette, alert);
    +// $form-label-color-invalid: map-get($foundation-palette, alert);
    +// $input-error-color: map-get($foundation-palette, alert);
    +// $input-error-font-size: rem-calc(12);
    +// $input-error-font-weight: $global-weight-bold;
    +
    +// 7. Accordion
    +// ------------
    +
    +// $accordion-background: $white;
    +// $accordion-plusminus: true;
    +// $accordion-item-color: foreground($accordion-background, $primary-color);
    +// $accordion-item-background-hover: $light-gray;
    +// $accordion-item-padding: 1.25rem 1rem;
    +// $accordion-content-background: $white;
    +// $accordion-content-border: 1px solid $light-gray;
    +// $accordion-content-color: foreground($accordion-content-background, $body-font-color);
    +// $accordion-content-padding: 1rem;
    +
    +// 8. Accordion Menu
    +// -----------------
    +
    +// $accordionmenu-arrows: true;
    +// $accordionmenu-arrow-color: $primary-color;
    +
    +// 9. Badge
    +// --------
    +
    +// $badge-background: $primary-color;
    +// $badge-color: foreground($badge-background);
    +// $badge-padding: 0.3em;
    +// $badge-minwidth: 2.1em;
    +// $badge-font-size: 0.6rem;
    +
    +// 10. Breadcrumbs
    +// ---------------
    +
    +// $breadcrumbs-margin: 0 0 $global-margin 0;
    +// $breadcrumbs-item-font-size: rem-calc(11);
    +// $breadcrumbs-item-color: $primary-color;
    +// $breadcrumbs-item-color-current: $black;
    +// $breadcrumbs-item-color-disabled: $medium-gray;
    +// $breadcrumbs-item-margin: 0.75rem;
    +// $breadcrumbs-item-uppercase: true;
    +// $breadcrumbs-item-slash: true;
    +
    +// 11. Button
    +// ----------
    +
    +// $button-padding: 0.85em 1em;
    +$button-margin: 0;
    +// $button-fill: solid;
    +// $button-background: $primary-color;
    +// $button-background-hover: scale-color($button-background, $lightness: -15%);
    +// $button-color: $white;
    +// $button-color-alt: $black;
    +// $button-radius: $global-radius;
    +// $button-sizes: (
    +//   tiny: 0.6rem,
    +//   small: 0.75rem,
    +//   default: 0.9rem,
    +//   large: 1.25rem,
    +// );
    +// $button-opacity-disabled: 0.25;
    +
    +// 12. Button Group
    +// ----------------
    +
    +// $buttongroup-margin: 1rem;
    +// $buttongroup-spacing: 1px;
    +// $buttongroup-child-selector: ".button";
    +// $buttongroup-expand-max: 6;
    +
    +// 13. Callout
    +// -----------
    +
    +// $callout-background: $white;
    +// $callout-background-fade: 85%;
    +// $callout-border: 1px solid rgba($black, 0.25);
    +// $callout-margin: 0 0 1rem 0;
    +// $callout-padding: 1rem;
    +// $callout-font-color: $body-font-color;
    +// $callout-font-color-alt: $body-background;
    +// $callout-radius: $global-radius;
    +// $callout-link-tint: 30%;
    +
    +// 14. Close Button
    +// ----------------
    +
    +// $closebutton-position: right top;
    +// $closebutton-offset-horizontal: 1rem;
    +// $closebutton-offset-vertical: 0.5rem;
    +// $closebutton-size: 2em;
    +// $closebutton-lineheight: 1;
    +// $closebutton-color: $dark-gray;
    +// $closebutton-color-hover: $black;
    +
    +// 15. Drilldown
    +// -------------
    +
    +// $drilldown-transition: transform 0.15s linear;
    +// $drilldown-arrows: true;
    +// $drilldown-arrow-color: $primary-color;
    +// $drilldown-background: $white;
    +
    +// 16. Dropdown
    +// ------------
    +
    +// $dropdown-padding: 1rem;
    +// $dropdown-border: 1px solid $medium-gray;
    +// $dropdown-font-size: 1rem;
    +// $dropdown-width: 300px;
    +// $dropdown-radius: $global-radius;
    +// $dropdown-sizes: (
    +//   tiny: 100px,
    +//   small: 200px,
    +//   large: 400px,
    +// );
    +
    +// 17. Dropdown Menu
    +// -----------------
    +
    +// $dropdownmenu-arrows: true;
    +// $dropdownmenu-arrow-color: $anchor-color;
    +// $dropdownmenu-min-width: 200px;
    +// $dropdownmenu-background: $white;
    +// $dropdownmenu-border: 1px solid $medium-gray;
    +
    +// 18. Flex Video
    +// --------------
    +
    +// $flexvideo-margin-bottom: rem-calc(16);
    +// $flexvideo-ratio: 4 by 3;
    +// $flexvideo-ratio-widescreen: 16 by 9;
    +
    +// 19. Forms
    +// ---------
    +
    +// $fieldset-border: 1px solid $medium-gray;
    +// $fieldset-padding: rem-calc(20);
    +// $fieldset-margin: rem-calc(18 0);
    +// $legend-padding: rem-calc(0 3);
    +// $form-spacing: rem-calc(16);
    +// $helptext-color: $black;
    +// $helptext-font-size: rem-calc(13);
    +// $helptext-font-style: italic;
    +// $input-prefix-color: $black;
    +// $input-prefix-background: $light-gray;
    +// $input-prefix-border: 1px solid $medium-gray;
    +// $input-prefix-padding: 1rem;
    +// $form-label-color: $black;
    +// $form-label-font-size: rem-calc(14);
    +// $form-label-font-weight: $global-weight-normal;
    +// $form-label-line-height: 1.8;
    +// $select-background: $white;
    +// $select-triangle-color: $dark-gray;
    +// $select-radius: $global-radius;
    +// $input-color: $black;
    +// $input-placeholder-color: $medium-gray;
    +// $input-font-family: inherit;
    +// $input-font-size: rem-calc(16);
    +// $input-background: $white;
    +// $input-background-focus: $white;
    +// $input-background-disabled: $light-gray;
    +// $input-border: 1px solid $medium-gray;
    +// $input-border-focus: 1px solid $dark-gray;
    +// $input-shadow: inset 0 1px 2px rgba($black, 0.1);
    +// $input-shadow-focus: 0 0 5px $medium-gray;
    +// $input-cursor-disabled: not-allowed;
    +// $input-transition: box-shadow 0.5s, border-color 0.25s ease-in-out;
    +// $input-number-spinners: true;
    +// $input-radius: $global-radius;
    +
    +// 20. Label
    +// ---------
    +
    +// $label-background: $primary-color;
    +// $label-color: foreground($label-background);
    +// $label-font-size: 0.8rem;
    +// $label-padding: 0.33333rem 0.5rem;
    +// $label-radius: $global-radius;
    +
    +// 21. Media Object
    +// ----------------
    +
    +// $mediaobject-margin-bottom: $global-margin;
    +// $mediaobject-section-padding: $global-padding;
    +// $mediaobject-image-width-stacked: 100%;
    +
    +// 22. Menu
    +// --------
    +
    +// $menu-margin: 0;
    +// $menu-margin-nested: 1rem;
    +// $menu-item-padding: 0.7rem 1rem;
    +// $menu-item-color-active: $white;
    +// $menu-item-background-active: map-get($foundation-palette, primary);
    +// $menu-icon-spacing: 0.25rem;
    +
    +// 23. Meter
    +// ---------
    +
    +// $meter-height: 1rem;
    +// $meter-radius: $global-radius;
    +// $meter-background: $medium-gray;
    +// $meter-fill-good: $success-color;
    +// $meter-fill-medium: $warning-color;
    +// $meter-fill-bad: $alert-color;
    +
    +// 24. Off-canvas
    +// --------------
    +
    +// $offcanvas-size: 250px;
    +$offcanvas-background: $black;
    +// $offcanvas-zindex: -1;
    +// $offcanvas-transition-length: 0.5s;
    +// $offcanvas-transition-timing: ease;
    +// $offcanvas-fixed-reveal: true;
    +// $offcanvas-exit-background: rgba($white, 0.25);
    +// $maincontent-class: "off-canvas-content";
    +$maincontent-shadow: none;
    +
    +// 25. Orbit
    +// ---------
    +
    +// $orbit-bullet-background: $medium-gray;
    +// $orbit-bullet-background-active: $dark-gray;
    +// $orbit-bullet-diameter: 1.2rem;
    +// $orbit-bullet-margin: 0.1rem;
    +// $orbit-bullet-margin-top: 0.8rem;
    +// $orbit-bullet-margin-bottom: 0.8rem;
    +// $orbit-caption-background: rgba($black, 0.5);
    +// $orbit-caption-padding: 1rem;
    +// $orbit-control-background-hover: rgba($black, 0.5);
    +// $orbit-control-padding: 1rem;
    +// $orbit-control-zindex: 10;
    +
    +// 26. Pagination
    +// --------------
    +
    +// $pagination-font-size: rem-calc(14);
    +// $pagination-margin-bottom: $global-margin;
    +// $pagination-item-color: $black;
    +// $pagination-item-padding: rem-calc(3 10);
    +// $pagination-item-spacing: rem-calc(1);
    +// $pagination-radius: $global-radius;
    +// $pagination-item-background-hover: $light-gray;
    +// $pagination-item-background-current: $primary-color;
    +// $pagination-item-color-current: foreground($pagination-item-background-current);
    +// $pagination-item-color-disabled: $medium-gray;
    +// $pagination-ellipsis-color: $black;
    +// $pagination-mobile-items: false;
    +// $pagination-arrows: true;
    +
    +// 27. Progress Bar
    +// ----------------
    +
    +// $progress-height: 1rem;
    +// $progress-background: $medium-gray;
    +// $progress-margin-bottom: $global-margin;
    +// $progress-meter-background: $primary-color;
    +// $progress-radius: $global-radius;
    +
    +// 28. Reveal
    +// ----------
    +
    +// $reveal-background: $white;
    +// $reveal-width: 600px;
    +// $reveal-max-width: $global-width;
    +// $reveal-padding: $global-padding;
    +// $reveal-border: 1px solid $medium-gray;
    +// $reveal-radius: $global-radius;
    +// $reveal-zindex: 1005;
    +// $reveal-overlay-background: rgba($black, 0.45);
    +
    +// 29. Slider
    +// ----------
    +
    +// $slider-width-vertical: 0.5rem;
    +// $slider-transition: all 0.2s ease-in-out;
    +// $slider-height: 0.5rem;
    +// $slider-background: $light-gray;
    +// $slider-fill-background: $medium-gray;
    +// $slider-handle-height: 1.4rem;
    +// $slider-handle-width: 1.4rem;
    +// $slider-handle-background: $primary-color;
    +// $slider-opacity-disabled: 0.25;
    +// $slider-radius: $global-radius;
    +
    +// 30. Switch
    +// ----------
    +
    +// $switch-background: $medium-gray;
    +// $switch-background-active: $primary-color;
    +// $switch-height: 2rem;
    +// $switch-height-tiny: 1.5rem;
    +// $switch-height-small: 1.75rem;
    +// $switch-height-large: 2.5rem;
    +// $switch-radius: $global-radius;
    +// $switch-margin: $global-margin;
    +// $switch-paddle-background: $white;
    +// $switch-paddle-offset: 0.25rem;
    +// $switch-paddle-radius: $global-radius;
    +// $switch-paddle-transition: all 0.25s ease-out;
    +
    +// 31. Table
    +// ---------
    +
    +// $table-background: $white;
    +// $table-color-scale: 5%;
    +// $table-border: 1px solid smart-scale($table-background, $table-color-scale);
    +// $table-padding: rem-calc(8 10 10);
    +// $table-hover-scale: 2%;
    +// $table-row-hover: darken($table-background, $table-hover-scale);
    +// $table-row-stripe-hover: darken($table-background, $table-color-scale + $table-hover-scale);
    +// $table-striped-background: smart-scale($table-background, $table-color-scale);
    +// $table-stripe: even;
    +// $table-head-background: smart-scale($table-background, $table-color-scale / 2);
    +// $table-foot-background: smart-scale($table-background, $table-color-scale);
    +// $table-head-font-color: $body-font-color;
    +// $show-header-for-stacked: false;
    +
    +// 32. Tabs
    +// --------
    +
    +// $tab-margin: 0;
    +// $tab-background: $white;
    +// $tab-background-active: $light-gray;
    +// $tab-item-font-size: rem-calc(12);
    +// $tab-item-background-hover: $white;
    +// $tab-item-padding: 1.25rem 1.5rem;
    +// $tab-expand-max: 6;
    +// $tab-content-background: $white;
    +// $tab-content-border: $light-gray;
    +// $tab-content-color: foreground($tab-background, $primary-color);
    +// $tab-content-padding: 1rem;
    +
    +// 33. Thumbnail
    +// -------------
    +
    +// $thumbnail-border: solid 4px $white;
    +// $thumbnail-margin-bottom: $global-margin;
    +// $thumbnail-shadow: 0 0 0 1px rgba($black, 0.2);
    +// $thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5);
    +// $thumbnail-transition: box-shadow 200ms ease-out;
    +// $thumbnail-radius: $global-radius;
    +
    +// 34. Title Bar
    +// -------------
    +
    +$titlebar-background: map-get($foundation-palette, primary);
    +// $titlebar-color: $white;
    +// $titlebar-padding: 0.5rem;
    +// $titlebar-text-font-weight: bold;
    +// $titlebar-icon-color: $white;
    +// $titlebar-icon-color-hover: $medium-gray;
    +// $titlebar-icon-spacing: 0.25rem;
    +
    +// 35. Tooltip
    +// -----------
    +
    +// $has-tip-font-weight: $global-weight-bold;
    +// $has-tip-border-bottom: dotted 1px $dark-gray;
    +// $tooltip-background-color: $black;
    +// $tooltip-color: $white;
    +// $tooltip-padding: 0.75rem;
    +// $tooltip-font-size: $small-font-size;
    +// $tooltip-pip-width: 0.75rem;
    +// $tooltip-pip-height: $tooltip-pip-width * 0.866;
    +// $tooltip-radius: $global-radius;
    +
    +// 36. Top Bar
    +// -----------
    +
    +// $topbar-padding: 0.5rem;
    +// $topbar-background: $light-gray;
    +// $topbar-submenu-background: $topbar-background;
    +// $topbar-title-spacing: 1rem;
    +// $topbar-input-width: 200px;
    +// $topbar-unstack-breakpoint: medium;
    
  • decidim-system/app/assets/stylesheets/decidim/system/_sidebar.scss+73 0 added
    @@ -0,0 +1,73 @@
    +@mixin menu-title{
    +  text-transform: uppercase;
    +  letter-spacing: 5px;
    +}
    +
    +.title-bar-title{
    +  @include menu-title;
    +}
    +
    +.sidebar{
    +  color: $light-gray;
    +
    +  .title{
    +    background-color: black;
    +    background-color: map-get($foundation-palette, primary);
    +
    +    h1{
    +      margin: 0;
    +      padding: 0;
    +      font-size: rem-calc(18);
    +      font-weight: bold;
    +    }
    +
    +    a{
    +      display: block;
    +      color: inherit;
    +      padding: rem-calc(20) $global-padding;
    +      @include menu-title;
    +    }
    +
    +    @include show-for(large);
    +  }
    +
    +  .main-menu{
    +    border-top: 1px solid black;
    +    border-bottom: 1px solid black;
    +    a{
    +      padding: $global-padding;
    +      display: block;
    +      color: $light-gray;
    +      text-transform: uppercase;
    +      font-weight: bold;
    +      font-size: 0.9em;
    +      border-left: 2px solid transparent;
    +
    +      transition: all 0.3s;
    +
    +      &.active {
    +        border-color: map-get($foundation-palette, primary);
    +        &, &:hover{
    +          background-color: #101010;
    +        }
    +      }
    +
    +      &:hover {
    +        background-color: #1a1a1a;
    +      }
    +    }
    +  }
    +
    +  .session-box {
    +    padding: $global-padding 0;
    +
    +    @include grid-row;
    +
    +    .admin-email {
    +      @include grid-column(8);
    +      text-overflow: ellipsis;
    +      overflow: hidden;
    +    }
    +    .sign-out{ @include grid-column(4); }
    +  }
    +}
    \ No newline at end of file
    
  • decidim-system/app/assets/stylesheets/decidim/system/_tables.scss+5 0 added
    @@ -0,0 +1,5 @@
    +table{
    +  td.actions, th.actions{
    +    text-align: right;
    +  }
    +}
    \ No newline at end of file
    
  • decidim-system/app/commands/decidim/system/create_admin.rb+40 0 added
    @@ -0,0 +1,40 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module System
    +    # A command with all the business logic when creating a new admin in
    +    # the system.
    +    class CreateAdmin < Rectify::Command
    +      # Public: Initializes the command.
    +      #
    +      # form - A form object with the params.
    +      def initialize(form)
    +        @form = form
    +      end
    +
    +      # Executes the command. Braodcasts these events:
    +      #
    +      # - :ok when everything is valid.
    +      # - :invalid if the form wasn't valid and we couldn't proceed.
    +      #
    +      # Returns nothing.
    +      def call
    +        return broadcast(:invalid) if form.invalid?
    +
    +        create_admin
    +        broadcast(:ok)
    +      end
    +
    +      private
    +
    +      attr_reader :form
    +
    +      def create_admin
    +        Admin.create!(
    +          email: form.email,
    +          password: form.password,
    +          password_confirmation: form.password_confirmation
    +        )
    +      end
    +    end
    +  end
    +end
    
  • decidim-system/app/commands/decidim/system/register_organization.rb+51 0 added
    @@ -0,0 +1,51 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module System
    +    # A command with all the business logic when creating a new organization in
    +    # the system. It creates the organization and invites the admin to the
    +    # system.
    +    class RegisterOrganization < Rectify::Command
    +      # Public: Initializes the command.
    +      #
    +      # form - A form object with the params.
    +      def initialize(form)
    +        @form = form
    +      end
    +
    +      # Executes the command. Braodcasts these events:
    +      #
    +      # - :ok when everything is valid.
    +      # - :invalid if the form wasn't valid and we couldn't proceed.
    +      #
    +      # Returns nothing.
    +      def call
    +        return broadcast(:invalid) if form.invalid?
    +
    +        transaction do
    +          organization = create_organization
    +          invite_admin(organization)
    +        end
    +
    +        broadcast(:ok)
    +      rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
    +        broadcast(:invalid)
    +      end
    +
    +      private
    +
    +      attr_reader :form
    +
    +      def create_organization
    +        Decidim::Organization.create!(name: form.name, host: form.host)
    +      end
    +
    +      def invite_admin(organization)
    +        Decidim::User.invite!(
    +          email: form.organization_admin_email,
    +          organization: organization,
    +          roles: ["admin"]
    +        )
    +      end
    +    end
    +  end
    +end
    
  • decidim-system/app/commands/decidim/system/update_admin.rb+52 0 added
    @@ -0,0 +1,52 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module System
    +    # A command with all the business logic when updating an admin in
    +    # the system.
    +    class UpdateAdmin < Rectify::Command
    +      # Public: Initializes the command.
    +      #
    +      # form - A form object with the params.
    +      def initialize(admin, form)
    +        @admin = admin
    +        @form = form
    +      end
    +
    +      # Executes the command. Braodcasts these events:
    +      #
    +      # - :ok when everything is valid.
    +      # - :invalid if the form wasn't valid and we couldn't proceed.
    +      #
    +      # Returns nothing.
    +      def call
    +        return broadcast(:invalid) if form.invalid?
    +
    +        update_admin
    +        broadcast(:ok)
    +      end
    +
    +      private
    +
    +      attr_reader :form
    +
    +      def update_admin
    +        @admin.update_attributes!(attributes)
    +      end
    +
    +      def attributes
    +        {
    +          email: form.email
    +        }.merge(password_attributes)
    +      end
    +
    +      def password_attributes
    +        return {} if form.password == form.password_confirmation && form.password.blank?
    +
    +        {
    +          password: form.password,
    +          password_confirmation: form.password_confirmation
    +        }
    +      end
    +    end
    +  end
    +end
    
  • decidim-system/app/commands/decidim/system/update_organization.rb+49 0 added
    @@ -0,0 +1,49 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module System
    +    # A command with all the business logic when creating a new organization in
    +    # the system. It creates the organization and invites the admin to the
    +    # system.
    +    class UpdateOrganization < Rectify::Command
    +      # Public: Initializes the command.
    +      #
    +      # form - A form object with the params.
    +      def initialize(id, form)
    +        @organization_id = id
    +        @form = form
    +      end
    +
    +      # Executes the command. Braodcasts these events:
    +      #
    +      # - :ok when everything is valid.
    +      # - :invalid if the form wasn't valid and we couldn't proceed.
    +      #
    +      # Returns nothing.
    +      def call
    +        return broadcast(:invalid) if form.invalid?
    +
    +        transaction do
    +          save_organization
    +        end
    +
    +        broadcast(:ok)
    +      rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
    +        broadcast(:invalid)
    +      end
    +
    +      private
    +
    +      attr_reader :form, :organization_id
    +
    +      def organization
    +        @organization ||= Organization.find(organization_id)
    +      end
    +
    +      def save_organization
    +        organization.name = form.name
    +        organization.host = form.host
    +        organization.save!
    +      end
    +    end
    +  end
    +end
    
  • decidim-system/app/controllers/decidim/system/admins_controller.rb+67 0 added
    @@ -0,0 +1,67 @@
    +# frozen_string_literal: true
    +require_dependency "decidim/system/application_controller"
    +
    +module Decidim
    +  module System
    +    # Controller that allows managing all the Admins.
    +    #
    +    class AdminsController < ApplicationController
    +      def index
    +        @admins = Admin.all
    +      end
    +
    +      def new
    +        @form = AdminForm.new
    +      end
    +
    +      def create
    +        @form = AdminForm.from_params(params)
    +
    +        CreateAdmin.call(@form) do
    +          on(:ok) do
    +            flash[:notice] = I18n.t("admins.create.success", scope: "decidim.system")
    +            redirect_to admins_path
    +          end
    +
    +          on(:invalid) do
    +            flash.now[:alert] = I18n.t("admins.create.error", scope: "decidim.system")
    +            render :new
    +          end
    +        end
    +      end
    +
    +      def edit
    +        @admin = Admin.find(params[:id])
    +        @form = AdminForm.from_model(@admin)
    +      end
    +
    +      def update
    +        @admin = Admin.find(params[:id])
    +        @form = AdminForm.from_params(params)
    +
    +        UpdateAdmin.call(@admin, @form) do
    +          on(:ok) do
    +            flash[:notice] = I18n.t("admins.update.success", scope: "decidim.system")
    +            redirect_to admins_path
    +          end
    +
    +          on(:invalid) do
    +            flash.now[:alert] = I18n.t("admins.update.error", scope: "decidim.system")
    +            render :new
    +          end
    +        end
    +      end
    +
    +      def show
    +        @admin = Admin.find(params[:id])
    +      end
    +
    +      def destroy
    +        @admin = Admin.find(params[:id]).destroy!
    +        flash[:notice] = I18n.t("admins.destroy.success", scope: "decidim.system")
    +
    +        redirect_to admins_path
    +      end
    +    end
    +  end
    +end
    
  • decidim-system/app/controllers/decidim/system/application_controller.rb+9 0 added
    @@ -0,0 +1,9 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module System
    +    # The main application controller that inherits from Rails.
    +    class ApplicationController < ActionController::Base
    +      protect_from_forgery with: :exception, prepend: true
    +    end
    +  end
    +end
    
  • decidim-system/app/controllers/decidim/system/dashboard_controller.rb+9 0 added
    @@ -0,0 +1,9 @@
    +# frozen_string_literal: true
    +require_dependency "decidim/system/application_controller"
    +
    +module Decidim
    +  module System
    +    class DashboardController < ApplicationController
    +    end
    +  end
    +end
    
  • decidim-system/app/controllers/decidim/system/devise/passwords_controller.rb+11 0 added
    @@ -0,0 +1,11 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module System
    +    module Devise
    +      # A controller so admins can recover their passwords.
    +      class PasswordsController < Devise::PasswordsController
    +        layout "decidim/system/login"
    +      end
    +    end
    +  end
    +end
    
  • decidim-system/app/controllers/decidim/system/devise/sessions_controller.rb+12 0 added
    @@ -0,0 +1,12 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module System
    +    module Devise
    +      # A controller so admins can log-in and use their backend.
    +      class SessionsController < ::Devise::SessionsController
    +        helper Decidim::System::ApplicationHelper
    +        layout "decidim/system/login"
    +      end
    +    end
    +  end
    +end
    
  • decidim-system/app/controllers/decidim/system/organizations_controller.rb+59 0 added
    @@ -0,0 +1,59 @@
    +# frozen_string_literal: true
    +require_dependency "decidim/system/application_controller"
    +
    +module Decidim
    +  module System
    +    # Controller to manage Organizations (tenants).
    +    #
    +    class OrganizationsController < ApplicationController
    +      def new
    +        @form = RegisterOrganizationForm.new
    +      end
    +
    +      def create
    +        @form = RegisterOrganizationForm.from_params(params)
    +
    +        RegisterOrganization.call(@form) do
    +          on(:ok) do
    +            flash[:notice] = t("organizations.create.success", scope: "decidim.system")
    +            redirect_to organizations_path
    +          end
    +
    +          on(:invalid) do
    +            flash.now[:alert] = t("organizations.create.error", scope: "decidim.system")
    +            render :new
    +          end
    +        end
    +      end
    +
    +      def index
    +        @organizations = Organization.all
    +      end
    +
    +      def show
    +        @organization = Organization.find(params[:id])
    +      end
    +
    +      def edit
    +        organization = Organization.find(params[:id])
    +        @form = UpdateOrganizationForm.from_model(organization)
    +      end
    +
    +      def update
    +        @form = UpdateOrganizationForm.from_params(params)
    +
    +        UpdateOrganization.call(params[:id], @form) do
    +          on(:ok) do
    +            flash[:notice] = t("organizations.update.success", scope: "decidim.system")
    +            redirect_to organizations_path
    +          end
    +
    +          on(:invalid) do
    +            flash.now[:alert] = I18n.t("organizations.update.error", scope: "decidim.system")
    +            render :edit
    +          end
    +        end
    +      end
    +    end
    +  end
    +end
    
  • decidim-system/app/forms/decidim/system/admin_form.rb+33 0 added
    @@ -0,0 +1,33 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module System
    +    # A form object used to create organizations from the system dashboard.
    +    #
    +    class AdminForm < Rectify::Form
    +      mimic :admin
    +
    +      attribute :email, String
    +      attribute :password, String
    +      attribute :password_confirmation, String
    +
    +      validates :email, presence: true
    +      validates :password, presence: true, unless: :admin_exists?
    +      validates :password_confirmation, presence: true, unless: :admin_exists?
    +
    +      validate :email, :email_uniqueness
    +
    +      private
    +
    +      def email_uniqueness
    +        return unless Admin.where(email: email).where.not(id: id).any?
    +
    +        errors.add(:email, I18n.t("models.admin.validations.email_uniqueness",
    +                                  scope: "decidim.system"))
    +      end
    +
    +      def admin_exists?
    +        id.present?
    +      end
    +    end
    +  end
    +end
    
  • decidim-system/app/forms/decidim/system/register_organization_form.rb+14 0 added
    @@ -0,0 +1,14 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module System
    +    # A form object used to create organizations from the system dashboard.
    +    #
    +    class RegisterOrganizationForm < UpdateOrganizationForm
    +      mimic :organization
    +
    +      attribute :organization_admin_email, String
    +
    +      validates :organization_admin_email, presence: true
    +    end
    +  end
    +end
    
  • decidim-system/app/forms/decidim/system/update_organization_form.rb+22 0 added
    @@ -0,0 +1,22 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module System
    +    # A form object used to update organizations from the system dashboard.
    +    #
    +    class UpdateOrganizationForm < Rectify::Form
    +      mimic :organization
    +
    +      attribute :name, String
    +      attribute :host, String
    +
    +      validate :validate_organization_uniqueness
    +
    +      private
    +
    +      def validate_organization_uniqueness
    +        errors.add(:name, :taken) if Decidim::Organization.where(name: name).where.not(id: id).exists?
    +        errors.add(:host, :taken) if Decidim::Organization.where(host: host).where.not(id: id).exists?
    +      end
    +    end
    +  end
    +end
    
  • decidim-system/app/helpers/decidim/system/application_helper.rb+16 0 added
    @@ -0,0 +1,16 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module System
    +    # Custom helpers, scoped to the system panel.
    +    #
    +    module ApplicationHelper
    +      def title
    +        "Decidim"
    +      end
    +
    +      def field_name(model, field)
    +        st "models.#{model}.fields.#{field}"
    +      end
    +    end
    +  end
    +end
    
  • decidim-system/app/jobs/decidim/system/application_job.rb+9 0 added
    @@ -0,0 +1,9 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module System
    +    # Custom ApplicationJob scoped to the system panel.
    +    #
    +    class ApplicationJob < ActiveJob::Base
    +    end
    +  end
    +end
    
  • decidim-system/app/mailers/decidim/system/application_mailer.rb+11 0 added
    @@ -0,0 +1,11 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module System
    +    # Custom application mailer, scoped to the system mailer.
    +    #
    +    class ApplicationMailer < ActionMailer::Base
    +      default from: "from@example.com"
    +      layout "mailer"
    +    end
    +  end
    +end
    
  • decidim-system/app/models/decidim/system/admin.rb+11 0 added
    @@ -0,0 +1,11 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module System
    +    # Admins are the users in charge of managing a Decidim installation.
    +    class Admin < ApplicationRecord
    +      devise :database_authenticatable, :recoverable, :rememberable, :validatable
    +
    +      validates :email, uniqueness: true
    +    end
    +  end
    +end
    
  • decidim-system/app/models/decidim/system/application_record.rb+10 0 added
    @@ -0,0 +1,10 @@
    +# frozen_string_literal: true
    +module Decidim
    +  module System
    +    # Custom ApplicationRecord scoped to the system panel.
    +    #
    +    class ApplicationRecord < ActiveRecord::Base
    +      self.abstract_class = true
    +    end
    +  end
    +end
    
  • decidim-system/app/views/decidim/system/admins/edit.html.erb+11 0 added
    @@ -0,0 +1,11 @@
    +<% content_for :title do %>
    +  <h2><%= t ".title" %></h2>
    +<% end %>
    +
    +<%= form_for(@form) do |f| %>
    +  <%= render partial: 'form', object: f %>
    +
    +  <div class="actions">
    +    <%= f.submit t(".update") %>
    +  </div>
    +<% end %>
    
  • decidim-system/app/views/decidim/system/admins/_form.html.erb+11 0 added
    @@ -0,0 +1,11 @@
    +<div class="field">
    +  <%= form.text_field :email, autofocus: true %>
    +</div>
    +
    +<div class="field">
    +  <%= form.password_field :password %>
    +</div>
    +
    +<div class="field">
    +  <%= form.password_field :password_confirmation %>
    +</div>
    
  • decidim-system/app/views/decidim/system/admins/index.html.erb+33 0 added
    @@ -0,0 +1,33 @@
    +<% content_for :title do %>
    +  <h2><%= t ".title" %></h2>
    +<% end %>
    +
    +<div class="actions title">
    +  <%= link_to t("actions.new", scope: "decidim.system", name: t("models.admin.name", scope: "decidim.system")), ['new', 'admin'], class: 'new' %>
    +</div>
    +
    +<table class="stack">
    +  <thead>
    +    <tr>
    +      <th><%= t("models.admin.fields.email", scope: "decidim.system") %></th>
    +      <th><%= t("models.admin.fields.created_at", scope: "decidim.system") %></th>
    +      <th class="actions"><%= t("actions.title", scope: "decidim.system") %></th>
    +    </tr>
    +  </thead>
    +  <tbody>
    +    <% @admins.each do |admin| %>
    +      <tr>
    +        <td>
    +          <%= link_to admin.email, admin %><br />
    +        </td>
    +        <td>
    +          <%= l admin.created_at, format: :short %>
    +        </td>
    +        <td class="actions">
    +          <%= link_to t("actions.edit", scope: "decidim.system"), ['edit', admin] %>
    +          <%= link_to t("actions.destroy", scope: "decidim.system"), admin, method: :delete, class: "small alert button", data: { confirm: t("actions.confirm_destroy", scope: "decidim.system") } %>
    +        </td>
    +      </tr>
    +    <% end %>
    +  </tbody>
    +</table>
    
  • decidim-system/app/views/decidim/system/admins/new.html.erb+11 0 added
    @@ -0,0 +1,11 @@
    +<% content_for :title do %>
    +  <h2><%= t ".title" %></h2>
    +<% end %>
    +
    +<%= form_for(@form) do |f| %>
    +  <%= render partial: 'form', object: f %>
    +
    +  <div class="actions">
    +    <%= f.submit t(".create") %>
    +  </div>
    +<% end %>
    
  • decidim-system/app/views/decidim/system/admins/show.html.erb+9 0 added
    @@ -0,0 +1,9 @@
    +<% content_for :title do %>
    +  <h2><%= link_to @admin.email, @admin %></h2>
    +<% end %>
    +
    +<div class="actions">
    +  <hr />
    +  <%= link_to "Edit", ['edit', @admin] %>
    +  <%= link_to "Destroy", @admin, method: :delete, class: "alert button", data: { confirm: "Are you sure you want to do this?" } %>
    +</div>
    
  • decidim-system/app/views/decidim/system/dashboard/show.html.erb+3 0 added
    @@ -0,0 +1,3 @@
    +<% content_for :title do %>
    +  <h2><%= t("decidim.system.titles.dashboard") %></h2>
    +<% end %>
    
  • decidim-system/app/views/decidim/system/devise/mailers/password_change.html.erb+3 0 added
    @@ -0,0 +1,3 @@
    +<p>Hello <%= @resource.email %>!</p>
    +
    +<p>We're contacting you to notify you that your password has been changed.</p>
    
  • decidim-system/app/views/decidim/system/devise/mailers/reset_password_instructions.html.erb+8 0 added
    @@ -0,0 +1,8 @@
    +<p>Hello <%= @resource.email %>!</p>
    +
    +<p>Someone has requested a link to change your password. You can do this through the link below.</p>
    +
    +<p><%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %></p>
    +
    +<p>If you didn't request this, please ignore this email.</p>
    +<p>Your password won't change until you access the link above and create a new one.</p>
    
  • decidim-system/app/views/decidim/system/devise/passwords/edit.html.erb+23 0 added
    @@ -0,0 +1,23 @@
    +<h2>Change your password</h2>
    +
    +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
    +  <%= devise_error_messages! %>
    +  <%= f.hidden_field :reset_password_token %>
    +
    +  <div class="field">
    +    <% if @minimum_password_length %>
    +      <em>(<%= @minimum_password_length %> characters minimum)</em><br />
    +    <% end %>
    +    <%= f.password_field :password, autofocus: true, autocomplete: "off" %>
    +  </div>
    +
    +  <div class="field">
    +    <%= f.password_field :password_confirmation, autocomplete: "off" %>
    +  </div>
    +
    +  <div class="actions">
    +    <%= f.submit "Change my password" %>
    +  </div>
    +<% end %>
    +
    +<%= render "decidim/system/devise/shared/links" %>
    
  • decidim-system/app/views/decidim/system/devise/passwords/new.html.erb+15 0 added
    @@ -0,0 +1,15 @@
    +<h2>Forgot your password?</h2>
    +
    +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
    +  <%= devise_error_messages! %>
    +
    +  <div class="field">
    +    <%= f.email_field :email, autofocus: true %>
    +  </div>
    +
    +  <div class="actions">
    +    <%= f.submit "Send me reset password instructions" %>
    +  </div>
    +<% end %>
    +
    +<%= render "decidim/system/devise/shared/links" %>
    
  • decidim-system/app/views/decidim/system/devise/sessions/new.html.erb+23 0 added
    @@ -0,0 +1,23 @@
    +<h2>Log in</h2>
    +
    +<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
    +  <div class="field">
    +    <%= f.email_field :email, autofocus: true %>
    +  </div>
    +
    +  <div class="field">
    +    <%= f.password_field :password, autocomplete: "off" %>
    +  </div>
    +
    +  <% if devise_mapping.rememberable? -%>
    +    <div class="field">
    +      <%= f.check_box :remember_me %>
    +    </div>
    +  <% end -%>
    +
    +  <div class="actions">
    +    <%= f.submit "Log in" %>
    +  </div>
    +<% end %>
    +
    +<%= render "decidim/system/devise/shared/links" %>
    
  • decidim-system/app/views/decidim/system/devise/shared/_links.html.erb+25 0 added
    @@ -0,0 +1,25 @@
    +<%- if controller_name != 'sessions' %>
    +  <%= link_to "Log in", new_session_path(resource_name) %><br />
    +<% end -%>
    +
    +<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
    +  <%= link_to "Sign up", new_registration_path(resource_name) %><br />
    +<% end -%>
    +
    +<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
    +  <%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
    +<% end -%>
    +
    +<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
    +  <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
    +<% end -%>
    +
    +<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
    +  <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
    +<% end -%>
    +
    +<%- if devise_mapping.omniauthable? %>
    +  <%- resource_class.omniauth_providers.each do |provider| %>
    +    <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %><br />
    +  <% end -%>
    +<% end -%>
    
  • decidim-system/app/views/decidim/system/organizations/edit.html.erb+13 0 added
    @@ -0,0 +1,13 @@
    +<%= form_for(@form) do |f| %>
    +  <div class="field">
    +    <%= f.text_field :name, autofocus: true %>
    +  </div>
    +
    +  <div class="field">
    +    <%= f.text_field :host %>
    +  </div>
    +
    +  <div class="actions">
    +    <%= f.submit t("decidim.system.actions.save") %>
    +  </div>
    +<% end %>
    
  • decidim-system/app/views/decidim/system/organizations/index.html.erb+34 0 added
    @@ -0,0 +1,34 @@
    +<% content_for :title do %>
    +  <h2><%= t ".title" %></h2>
    +<% end %>
    +
    +<div class="actions title">
    +  <%= link_to t("actions.new", scope: "decidim.system", name: t("models.organization.name", scope: "decidim.system")), ['new', 'organization'], class: 'new' %>
    +</div>
    +
    +<table class="stack">
    +  <thead>
    +    <tr>
    +      <th><%= t("models.organization.fields.name", scope: "decidim.system") %></th>
    +      <th><%= t("models.organization.fields.created_at", scope: "decidim.system") %></th>
    +      <th class="actions"><%= t("actions.title", scope: "decidim.system") %></th>
    +    </tr>
    +  </thead>
    +  <tbody>
    +    <% @organizations.each do |organization| %>
    +      <tr>
    +        <td>
    +          <%= link_to organization.name, organization %><br />
    +          <%= organization.host %>
    +        </td>
    +        <td>
    +          <%= l organization.created_at, format: :short %>
    +        </td>
    +        <td class="actions">
    +          <%= link_to t("actions.edit", scope: "decidim.system"), ['edit', organization] %>
    +          <%= link_to t("actions.destroy", scope: "decidim.system"), organization, method: :delete, class: "small alert button", data: { confirm: t("actions.confirm_destroy", scope: "decidim.system") } %>
    +        </td>
    +      </tr>
    +    <% end %>
    +  </tbody>
    +</table>
    
  • decidim-system/app/views/decidim/system/organizations/new.html.erb+21 0 added
    @@ -0,0 +1,21 @@
    +<% content_for :title do %>
    +  <h2><%= t ".title" %></h2>
    +<% end %>
    +
    +<%= form_for(@form) do |f| %>
    +  <div class="field">
    +    <%= f.text_field :name, autofocus: true %>
    +  </div>
    +
    +  <div class="field">
    +    <%= f.text_field :host %>
    +  </div>
    +
    +  <div class="field">
    +    <%= f.email_field :organization_admin_email %>
    +  </div>
    +
    +  <div class="actions">
    +    <%= f.submit t("decidim.system.models.organization.actions.save_and_invite") %>
    +  </div>
    +<% end %>
    
  • decidim-system/app/views/decidim/system/organizations/show.html.erb+10 0 added
    @@ -0,0 +1,10 @@
    +<% content_for :title do %>
    +  <h2><%= link_to @organization.name, @organization %></h2>
    +  <h3 class="subheader"><%= @organization.host %></h3>
    +<% end %>
    +
    +<div class="actions">
    +  <hr />
    +  <%= link_to t("decidim.system.actions.edit"), ['edit', @organization] %>
    +  <%= link_to t("decidim.system.actions.destroy"), @organization, method: :delete, class: "alert button", data: { confirm: t("decidim.system.actions.confirm_destroy") } %>
    +</div>
    
  • decidim-system/app/views/layouts/decidim/system/application.html.erb+40 0 added
    @@ -0,0 +1,40 @@
    +<!DOCTYPE html>
    +<html>
    +<head>
    +<title>Decidim - System</title>
    +<%= render partial: 'layouts/decidim/system/header' %>
    +</head>
    +
    +<body>
    +  <div class="off-canvas-wrapper">
    +    <div class="off-canvas-wrapper-inner" data-off-canvas-wrapper>
    +      <div class="off-canvas position-left reveal-for-large" id="my-info" data-off-canvas data-position="left">
    +        <div class="sidebar">
    +          <%= render partial: 'layouts/decidim/system/sidebar' %>
    +        </div>
    +      </div>
    +
    +      <div class="off-canvas-content" data-off-canvas-content>
    +        <div class="title-bar hide-for-large">
    +          <div class="title-bar-left">
    +            <button class="menu-icon" type="button" data-open="my-info"></button>
    +            <span class="title-bar-title"><%= title %></span>
    +          </div>
    +        </div>
    +        <div class="row main-content">
    +          <div class="small-12 column">
    +            <% if content_for?(:title) %>
    +              <section class="page-title">
    +                <%= content_for :title %>
    +              </section>
    +            <% end %>
    +
    +            <%= display_flash_messages %>
    +            <%= yield %>
    +          </div>
    +        </div>
    +      </div>
    +    </div>
    +  </div>
    +</body>
    +</html>
    
  • decidim-system/app/views/layouts/decidim/system/_header.html.erb+4 0 added
    @@ -0,0 +1,4 @@
    +<meta name="viewport" content="width=device-width, initial-scale=1">
    +<%= csrf_meta_tags %>
    +<%= stylesheet_link_tag    'decidim/system', media: 'all', 'data-turbolinks-track': 'reload' %>
    +<%= javascript_include_tag 'decidim/system', 'data-turbolinks-track': 'reload' %>
    
  • decidim-system/app/views/layouts/decidim/system/login.html.erb+19 0 added
    @@ -0,0 +1,19 @@
    +<!DOCTYPE html>
    +<html>
    +  <head>
    +    <title>Decidim - Login</title>
    +    <%= render partial: 'layouts/decidim/system/header' %>
    +  </head>
    +
    +  <body class="login">
    +    <div class="login-form-wrapper">
    +      <div class="login-form">
    +        <h1>Decidim</h1>
    +        <%= display_flash_messages %>
    +        <div class="login-form-inner">
    +          <%= yield %>
    +        </div>
    +      </div>
    +    </div>
    +  </body>
    +</html>
    
  • decidim-system/app/views/layouts/decidim/system/_login_items.html.erb+8 0 added
    @@ -0,0 +1,8 @@
    +<div class="session-box">
    +  <div class="admin-email">
    +    <%= current_admin.email %>
    +  </div>
    +  <div class="sign-out">
    +    <%= link_to('Logout', destroy_admin_session_path, :method => :delete) %>
    +  </div>
    +</div>
    
  • decidim-system/app/views/layouts/decidim/system/_sidebar.html.erb+15 0 added
    @@ -0,0 +1,15 @@
    +<div class="title">
    +  <h1>
    +    <%= link_to root_path do %>
    +      <%= title %>
    +    <% end %>
    +  </h1>
    +</div>
    +
    +<nav class="main-menu">
    +  <%= active_link_to t("menu.dashboard", scope: "decidim.system"), root_path, active: :exact %>
    +  <%= active_link_to t("menu.organizations", scope: "decidim.system"), organizations_path, active: :inclusive %>
    +  <%= active_link_to t("menu.admins", scope: "decidim.system"), admins_path, active: :inclusive %>
    +</nav>
    +
    +<%= render partial: 'layouts/decidim/system/login_items' %>
    
  • decidim-system/bin/rails+14 0 added
    @@ -0,0 +1,14 @@
    +#!/usr/bin/env ruby
    +# frozen_string_literal: true
    +# This command will automatically be run when you run "rails" with Rails gems
    +# installed from the root of your application.
    +
    +ENGINE_ROOT = File.expand_path("../..", __FILE__)
    +ENGINE_PATH = File.expand_path("../../lib/decidim/system/engine", __FILE__)
    +
    +# Set up gems listed in the Gemfile.
    +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
    +require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
    +
    +require "rails/all"
    +require "rails/engine/commands"
    
  • decidim-system/config/i18n-tasks.yml+120 0 added
    @@ -0,0 +1,120 @@
    +# i18n-tasks finds and manages missing and unused translations: https://github.com/glebm/i18n-tasks
    +
    +# The "main" locale.
    +base_locale: en
    +## All available locales are inferred from the data by default. Alternatively, specify them explicitly:
    +# locales: [es, fr]
    +## Reporting locale, default: en. Available: en, ru.
    +# internal_locale: en
    +
    +# Read and write translations.
    +data:
    +  ## Translations are read from the file system. Supported format: YAML, JSON.
    +  ## Provide a custom adapter:
    +  # adapter: I18n::Tasks::Data::FileSystem
    +
    +  # Locale files or `File.find` patterns where translations are read from:
    +  read:
    +    ## Default:
    +    # - config/locales/%{locale}.yml
    +    ## More files:
    +    # - config/locales/**/*.%{locale}.yml
    +    ## Another gem (replace %#= with %=):
    +    # - "<%#= %x[bundle show vagrant].chomp %>/templates/locales/%{locale}.yml"
    +
    +  # Locale files to write new keys to, based on a list of key pattern => file rules. Matched from top to bottom:
    +  # `i18n-tasks normalize -p` will force move the keys according to these rules
    +  write:
    +    ## For example, write devise and simple form keys to their respective files:
    +    # - ['{devise, simple_form}.*', 'config/locales/\1.%{locale}.yml']
    +    ## Catch-all default:
    +    # - config/locales/%{locale}.yml
    +
    +  ## Specify the router (see Readme for details). Valid values: conservative_router, pattern_router, or a custom class.
    +  # router: convervative_router
    +
    +  yaml:
    +    write:
    +      # do not wrap lines at 80 characters
    +      line_width: -1
    +
    +  ## Pretty-print JSON:
    +  # json:
    +  #   write:
    +  #     indent: '  '
    +  #     space: ' '
    +  #     object_nl: "\n"
    +  #     array_nl: "\n"
    +
    +# Find translate calls
    +search:
    +  ## Paths or `File.find` patterns to search in:
    +  # paths:
    +  #  - app/
    +
    +  ## Root directories for relative keys resolution.
    +  # relative_roots:
    +  #   - app/controllers
    +  #   - app/helpers
    +  #   - app/mailers
    +  #   - app/presenters
    +  #   - app/views
    +
    +  ## Files or `File.fnmatch` patterns to exclude from search. Some files are always excluded regardless of this setting:
    +  ##   %w(*.jpg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less *.yml *.json)
    +  exclude:
    +    - app/assets/images
    +    - app/assets/fonts
    +
    +  ## Alternatively, the only files or `File.fnmatch patterns` to search in `paths`:
    +  ## If specified, this settings takes priority over `exclude`, but `exclude` still applies.
    +  # only: ["*.rb", "*.html.slim"]
    +
    +  ## If `strict` is `false`, guess usages such as t("categories.#{category}.title"). The default is `true`.
    +  # strict: true
    +
    +  ## Multiple scanners can be used. Their results are merged.
    +  ## The options specified above are passed down to each scanner. Per-scanner options can be specified as well.
    +  ## See this example of a custom scanner: https://github.com/glebm/i18n-tasks/wiki/A-custom-scanner-example
    +
    +## Google Translate
    +# translation:
    +#   # Get an API key and set billing info at https://code.google.com/apis/console to use Google Translate
    +#   api_key: "AbC-dEf5"
    +
    +## Do not consider these keys missing:
    +# ignore_missing:
    +# - 'errors.messages.{accepted,blank,invalid,too_short,too_long}'
    +# - '{devise,simple_form}.*'
    +
    +## Consider these keys used:
    +# ignore_unused:
    +# - 'activerecord.attributes.*'
    +# - '{devise,kaminari,will_paginate}.*'
    +# - 'simple_form.{yes,no}'
    +# - 'simple_form.{placeholders,hints,labels}.*'
    +# - 'simple_form.{error_notification,required}.:'
    +
    +## Exclude these keys from the `i18n-tasks eq-base' report:
    +# ignore_eq_base:
    +#   all:
    +#     - common.ok
    +#   fr,es:
    +#     - common.brand
    +
    +## Ignore these keys completely:
    +# ignore:
    +#  - kaminari.*
    +
    +## Sometimes, it isn't possible for i18n-tasks to match the key correctly,
    +## e.g. in case of a relative key defined in a helper method.
    +## In these cases you can use the built-in PatternMapper to map patterns to keys, e.g.:
    +#
    +# <%#= I18n::Tasks.add_scanner 'I18n::Tasks::Scanners::PatternMapper',
    +#        only: %w(*.html.haml *.html.slim),
    +#        patterns: [['= title\b', '.page_title']] %>
    +#
    +# The PatternMapper can also match key literals via a special %{key} interpolation, e.g.:
    +#
    +# <%#= I18n::Tasks.add_scanner 'I18n::Tasks::Scanners::PatternMapper',
    +#        patterns: [['\bSpree\.t[( ]\s*%{key}', 'spree.%{key}']] %>
    
  • decidim-system/config/locales/en.yml+60 0 added
    @@ -0,0 +1,60 @@
    +---
    +en:
    +  decidim:
    +    system:
    +      actions:
    +        confirm_destroy: Are you sure you want to delete this?
    +        destroy: Destroy
    +        edit: Edit
    +        new: New %{name}
    +        save: Save
    +        title: Actions
    +      admins:
    +        create:
    +          error: There was an error when creating a new admin.
    +          success: Admin created successfully
    +        destroy:
    +          success: Admin successfully destroyed
    +        edit:
    +          title: Edit admin
    +          update: Update admin
    +        index:
    +          title: Admins
    +        new:
    +          create: Create admin
    +          title: New admin
    +        update:
    +          error: There was an error when updating this admin.
    +          success: Admin updated successfully
    +      menu:
    +        admins: Admins
    +        dashboard: Dashboard
    +        organizations: Organizations
    +      models:
    +        admin:
    +          fields:
    +            created_at: Created at
    +            email: Email
    +          name: Admin
    +          validations:
    +            email_uniqueness: another admin with the same email already exists
    +        organization:
    +          actions:
    +            save_and_invite: Create organization & invite admin
    +          fields:
    +            created_at: Created at
    +            name: Name
    +          name: Organization
    +      organizations:
    +        create:
    +          error: There was an error when creating a new organization.
    +          success: Organization created successfully.
    +        index:
    +          title: Organizations
    +        new:
    +          title: New organization
    +        update:
    +          error: There was an error when updating this organization.
    +          success: Organization updated successfully.
    +      titles:
    +        dashboard: Dashboard
    
  • decidim-system/config/routes.rb+13 0 added
    @@ -0,0 +1,13 @@
    +# frozen_string_literal: true
    +Decidim::System::Engine.routes.draw do
    +  devise_for :admins,
    +             class_name: "Decidim::System::Admin",
    +             module: :'decidim/system/devise',
    +             router_name: "decidim_system"
    +
    +  authenticate(:admin) do
    +    resources :organizations
    +    resources :admins
    +    root to: "dashboard#show"
    +  end
    +end
    
  • decidim-system/db/migrate/20160919105637_devise_create_decidim_admins.rb+26 0 added
    @@ -0,0 +1,26 @@
    +class DeviseCreateDecidimAdmins < ActiveRecord::Migration[5.0]
    +  def change
    +    create_table :decidim_system_admins do |t|
    +      ## Database authenticatable
    +      t.string :email,              null: false, default: ""
    +      t.string :encrypted_password, null: false, default: ""
    +
    +      ## Recoverable
    +      t.string   :reset_password_token
    +      t.datetime :reset_password_sent_at
    +
    +      ## Rememberable
    +      t.datetime :remember_created_at
    +
    +      ## Lockable
    +      t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
    +      t.string   :unlock_token # Only if unlock strategy is :email or :both
    +      t.datetime :locked_at
    +
    +      t.timestamps null: false
    +    end
    +
    +    add_index :decidim_system_admins, :email,                unique: true
    +    add_index :decidim_system_admins, :reset_password_token, unique: true
    +  end
    +end
    
  • decidim-system/decidim-system.gemspec+44 0 added
    @@ -0,0 +1,44 @@
    +# frozen_string_literal: true
    +$LOAD_PATH.push File.expand_path("../lib", __FILE__)
    +
    +# Maintain your gem's version:
    +require_relative "../decidim-core/lib/decidim/core/version"
    +
    +# Describe your gem and declare its dependencies:
    +Gem::Specification.new do |s|
    +  s.name        = "decidim-system"
    +  s.version     = Decidim.version
    +  s.authors     = ["Josep Jaume Rey Peroy", "Marc Riera Casals", "Oriol Gual Oliva"]
    +  s.email       = ["josepjaume@gmail.com", "mrc2407@gmail.com", "oriolgual@gmail.com"]
    +  s.homepage    = ""
    +  s.summary     = "System administration"
    +  s.description = "System administration to create new organization in an installation."
    +  s.license     = "MIT"
    +
    +  s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
    +
    +  s.add_dependency "decidim-core"
    +  s.add_dependency "rails", Decidim.rails_version
    +  s.add_dependency "devise", "~> 4.2"
    +  s.add_dependency "rectify", "~> 0.6"
    +  s.add_dependency "devise_invitable", "~> 1.7.0"
    +  s.add_dependency "foundation-rails", "~> 6.2.3.0"
    +  s.add_dependency "sass-rails", "~> 5.0.0"
    +  s.add_dependency "jquery-rails", "~> 4.0"
    +  s.add_dependency "turbolinks", Decidim.rails_version
    +  s.add_dependency "jquery-turbolinks", "~> 2.1.0"
    +  s.add_dependency "jbuilder", "~> 2.5"
    +  s.add_dependency "foundation_rails_helper", "~> 2.0.0"
    +  s.add_dependency "active_link_to", "~> 1.0.0"
    +
    +  s.add_development_dependency "factory_girl_rails"
    +  s.add_development_dependency "database_cleaner", "~> 1.5.0"
    +  s.add_development_dependency "capybara", "~> 2.4"
    +  s.add_development_dependency "rspec-rails", "~> 3.5"
    +  s.add_development_dependency "byebug"
    +  s.add_development_dependency "wisper-rspec"
    +  s.add_development_dependency "pg"
    +  s.add_development_dependency "listen"
    +  s.add_development_dependency "launchy"
    +  s.add_development_dependency "i18n-tasks", "~> 0.9.5"
    +end
    
  • decidim-system/Gemfile+17 0 added
    @@ -0,0 +1,17 @@
    +eval(File.read(File.dirname(__FILE__) + "/../common_gemfile.rb"))
    +
    +# Declare your gem's dependencies in decidim-system.gemspec.
    +# Bundler will treat runtime dependencies like base dependencies, and
    +# development dependencies will be added by default to the :development group.
    +gemspec
    +
    +# Declare any dependencies that are still in development here instead of in
    +# your gemspec. These might include edge Rails or gems from your path or
    +# Git. Remember to move these dependencies to your gemspec before releasing
    +# your gem to rubygems.org.
    +
    +# To use a debugger
    +# gem 'byebug', group: [:development, :test]
    +
    +gemspec path: "../decidim-core"
    +gemspec path: ".."
    
  • decidim-system/Gemfile.lock+334 0 added
    @@ -0,0 +1,334 @@
    +GIT
    +  remote: git://github.com/sgruhier/foundation_rails_helper.git
    +  revision: d8819b3a5f9d82251aac7559e0d07505c9777717
    +  specs:
    +    foundation_rails_helper (2.0.0)
    +      actionpack (>= 4.1)
    +      activemodel (>= 4.1)
    +      activesupport (>= 4.1)
    +      railties (>= 4.1)
    +      tzinfo (~> 1.2, >= 1.2.2)
    +
    +PATH
    +  remote: ../decidim-core
    +  specs:
    +    decidim-core (0.0.1.alpha2)
    +      active_link_to (~> 1.0.0)
    +      devise (~> 4.2)
    +      foundation-rails (~> 6.2.3.0)
    +      foundation_rails_helper (~> 2.0.0)
    +      jbuilder (~> 2.5)
    +      jquery-rails (~> 4.0)
    +      jquery-turbolinks (~> 2.1.0)
    +      rails (~> 5.0.0)
    +      rectify (~> 0.6)
    +      sass-rails (~> 5.0.0)
    +      turbolinks (~> 5.0.0)
    +
    +PATH
    +  remote: ..
    +  specs:
    +    decidim (0.0.1.alpha2)
    +      decidim-core (= 0.0.1.alpha2)
    +      decidim-system (= 0.0.1.alpha2)
    +      rails (~> 5.0.0)
    +
    +PATH
    +  remote: .
    +  specs:
    +    decidim-system (0.0.1.alpha2)
    +      active_link_to (~> 1.0.0)
    +      decidim-core
    +      devise (~> 4.2)
    +      devise_invitable (~> 1.7.0)
    +      foundation-rails (~> 6.2.3.0)
    +      foundation_rails_helper (~> 2.0.0)
    +      jbuilder (~> 2.5)
    +      jquery-rails (~> 4.0)
    +      jquery-turbolinks (~> 2.1.0)
    +      rails (~> 5.0.0)
    +      rectify (~> 0.6)
    +      sass-rails (~> 5.0.0)
    +      turbolinks (~> 5.0.0)
    +
    +GEM
    +  remote: https://rubygems.org/
    +  specs:
    +    actioncable (5.0.0.1)
    +      actionpack (= 5.0.0.1)
    +      nio4r (~> 1.2)
    +      websocket-driver (~> 0.6.1)
    +    actionmailer (5.0.0.1)
    +      actionpack (= 5.0.0.1)
    +      actionview (= 5.0.0.1)
    +      activejob (= 5.0.0.1)
    +      mail (~> 2.5, >= 2.5.4)
    +      rails-dom-testing (~> 2.0)
    +    actionpack (5.0.0.1)
    +      actionview (= 5.0.0.1)
    +      activesupport (= 5.0.0.1)
    +      rack (~> 2.0)
    +      rack-test (~> 0.6.3)
    +      rails-dom-testing (~> 2.0)
    +      rails-html-sanitizer (~> 1.0, >= 1.0.2)
    +    actionview (5.0.0.1)
    +      activesupport (= 5.0.0.1)
    +      builder (~> 3.1)
    +      erubis (~> 2.7.0)
    +      rails-dom-testing (~> 2.0)
    +      rails-html-sanitizer (~> 1.0, >= 1.0.2)
    +    active_link_to (1.0.3)
    +      actionpack
    +    activejob (5.0.0.1)
    +      activesupport (= 5.0.0.1)
    +      globalid (>= 0.3.6)
    +    activemodel (5.0.0.1)
    +      activesupport (= 5.0.0.1)
    +    activerecord (5.0.0.1)
    +      activemodel (= 5.0.0.1)
    +      activesupport (= 5.0.0.1)
    +      arel (~> 7.0)
    +    activesupport (5.0.0.1)
    +      concurrent-ruby (~> 1.0, >= 1.0.2)
    +      i18n (~> 0.7)
    +      minitest (~> 5.1)
    +      tzinfo (~> 1.1)
    +    addressable (2.4.0)
    +    arel (7.1.2)
    +    ast (2.3.0)
    +    axiom-types (0.1.1)
    +      descendants_tracker (~> 0.0.4)
    +      ice_nine (~> 0.11.0)
    +      thread_safe (~> 0.3, >= 0.3.1)
    +    babel-source (5.8.35)
    +    babel-transpiler (0.7.0)
    +      babel-source (>= 4.0, < 6)
    +      execjs (~> 2.0)
    +    bcrypt (3.1.11)
    +    builder (3.2.2)
    +    byebug (9.0.5)
    +    capybara (2.9.0)
    +      addressable
    +      mime-types (>= 1.16)
    +      nokogiri (>= 1.3.3)
    +      rack (>= 1.0.0)
    +      rack-test (>= 0.5.4)
    +      xpath (~> 2.0)
    +    coercible (1.0.0)
    +      descendants_tracker (~> 0.0.1)
    +    concurrent-ruby (1.0.2)
    +    database_cleaner (1.5.3)
    +    descendants_tracker (0.0.4)
    +      thread_safe (~> 0.3, >= 0.3.1)
    +    devise (4.2.0)
    +      bcrypt (~> 3.0)
    +      orm_adapter (~> 0.1)
    +      railties (>= 4.1.0, < 5.1)
    +      responders
    +      warden (~> 1.2.3)
    +    devise_invitable (1.7.0)
    +      actionmailer (>= 4.0.0)
    +      devise (>= 4.0.0)
    +    diff-lcs (1.2.5)
    +    easy_translate (0.5.0)
    +      json
    +      thread
    +      thread_safe
    +    equalizer (0.0.11)
    +    erubis (2.7.0)
    +    execjs (2.7.0)
    +    factory_girl (4.7.0)
    +      activesupport (>= 3.0.0)
    +    factory_girl_rails (4.7.0)
    +      factory_girl (~> 4.7.0)
    +      railties (>= 3.0.0)
    +    ffi (1.9.14)
    +    foundation-rails (6.2.3.0)
    +      railties (>= 3.1.0)
    +      sass (>= 3.3.0, < 3.5)
    +      sprockets-es6 (>= 0.9.0)
    +    globalid (0.3.7)
    +      activesupport (>= 4.1.0)
    +    highline (1.7.8)
    +    i18n (0.7.0)
    +    i18n-tasks (0.9.5)
    +      activesupport (>= 4.0.2)
    +      ast (>= 2.1.0)
    +      easy_translate (>= 0.5.0)
    +      erubis
    +      highline (>= 1.7.3)
    +      i18n
    +      parser (>= 2.2.3.0)
    +      term-ansicolor (>= 1.3.2)
    +      terminal-table (>= 1.5.1)
    +    ice_nine (0.11.2)
    +    jbuilder (2.6.0)
    +      activesupport (>= 3.0.0, < 5.1)
    +      multi_json (~> 1.2)
    +    jquery-rails (4.2.1)
    +      rails-dom-testing (>= 1, < 3)
    +      railties (>= 4.2.0)
    +      thor (>= 0.14, < 2.0)
    +    jquery-turbolinks (2.1.0)
    +      railties (>= 3.1.0)
    +      turbolinks
    +    json (2.0.2)
    +    launchy (2.4.3)
    +      addressable (~> 2.3)
    +    listen (3.0.8)
    +      rb-fsevent (~> 0.9, >= 0.9.4)
    +      rb-inotify (~> 0.9, >= 0.9.7)
    +    loofah (2.0.3)
    +      nokogiri (>= 1.5.9)
    +    mail (2.6.4)
    +      mime-types (>= 1.16, < 4)
    +    method_source (0.8.2)
    +    mime-types (3.1)
    +      mime-types-data (~> 3.2015)
    +    mime-types-data (3.2016.0521)
    +    mini_portile2 (2.1.0)
    +    minitest (5.9.0)
    +    multi_json (1.12.1)
    +    nio4r (1.2.1)
    +    nokogiri (1.6.8)
    +      mini_portile2 (~> 2.1.0)
    +      pkg-config (~> 1.1.7)
    +    orm_adapter (0.5.0)
    +    parser (2.3.1.4)
    +      ast (~> 2.2)
    +    pg (0.18.4)
    +    pkg-config (1.1.7)
    +    rack (2.0.1)
    +    rack-test (0.6.3)
    +      rack (>= 1.0)
    +    rails (5.0.0.1)
    +      actioncable (= 5.0.0.1)
    +      actionmailer (= 5.0.0.1)
    +      actionpack (= 5.0.0.1)
    +      actionview (= 5.0.0.1)
    +      activejob (= 5.0.0.1)
    +      activemodel (= 5.0.0.1)
    +      activerecord (= 5.0.0.1)
    +      activesupport (= 5.0.0.1)
    +      bundler (>= 1.3.0, < 2.0)
    +      railties (= 5.0.0.1)
    +      sprockets-rails (>= 2.0.0)
    +    rails-dom-testing (2.0.1)
    +      activesupport (>= 4.2.0, < 6.0)
    +      nokogiri (~> 1.6.0)
    +    rails-html-sanitizer (1.0.3)
    +      loofah (~> 2.0)
    +    railties (5.0.0.1)
    +      actionpack (= 5.0.0.1)
    +      activesupport (= 5.0.0.1)
    +      method_source
    +      rake (>= 0.8.7)
    +      thor (>= 0.18.1, < 2.0)
    +    rake (11.3.0)
    +    rb-fsevent (0.9.7)
    +    rb-inotify (0.9.7)
    +      ffi (>= 0.5.0)
    +    rectify (0.6.1)
    +      activemodel (>= 4.1.0)
    +      activerecord (>= 4.1.0)
    +      activesupport (>= 4.1.0)
    +      virtus (~> 1.0.5)
    +      wisper (>= 1.6.1)
    +    responders (2.3.0)
    +      railties (>= 4.2.0, < 5.1)
    +    rspec (3.5.0)
    +      rspec-core (~> 3.5.0)
    +      rspec-expectations (~> 3.5.0)
    +      rspec-mocks (~> 3.5.0)
    +    rspec-core (3.5.3)
    +      rspec-support (~> 3.5.0)
    +    rspec-expectations (3.5.0)
    +      diff-lcs (>= 1.2.0, < 2.0)
    +      rspec-support (~> 3.5.0)
    +    rspec-mocks (3.5.0)
    +      diff-lcs (>= 1.2.0, < 2.0)
    +      rspec-support (~> 3.5.0)
    +    rspec-rails (3.5.2)
    +      actionpack (>= 3.0)
    +      activesupport (>= 3.0)
    +      railties (>= 3.0)
    +      rspec-core (~> 3.5.0)
    +      rspec-expectations (~> 3.5.0)
    +      rspec-mocks (~> 3.5.0)
    +      rspec-support (~> 3.5.0)
    +    rspec-support (3.5.0)
    +    sass (3.4.22)
    +    sass-rails (5.0.6)
    +      railties (>= 4.0.0, < 6)
    +      sass (~> 3.1)
    +      sprockets (>= 2.8, < 4.0)
    +      sprockets-rails (>= 2.0, < 4.0)
    +      tilt (>= 1.1, < 3)
    +    sprockets (3.7.0)
    +      concurrent-ruby (~> 1.0)
    +      rack (> 1, < 3)
    +    sprockets-es6 (0.9.1)
    +      babel-source (>= 5.8.11)
    +      babel-transpiler
    +      sprockets (>= 3.0.0)
    +    sprockets-rails (3.2.0)
    +      actionpack (>= 4.0)
    +      activesupport (>= 4.0)
    +      sprockets (>= 3.0.0)
    +    term-ansicolor (1.3.2)
    +      tins (~> 1.0)
    +    terminal-table (1.7.3)
    +      unicode-display_width (~> 1.1.1)
    +    thor (0.19.1)
    +    thread (0.2.2)
    +    thread_safe (0.3.5)
    +    tilt (2.0.5)
    +    tins (1.12.0)
    +    turbolinks (5.0.1)
    +      turbolinks-source (~> 5)
    +    turbolinks-source (5.0.0)
    +    tzinfo (1.2.2)
    +      thread_safe (~> 0.1)
    +    unicode-display_width (1.1.1)
    +    virtus (1.0.5)
    +      axiom-types (~> 0.1)
    +      coercible (~> 1.0)
    +      descendants_tracker (~> 0.0, >= 0.0.3)
    +      equalizer (~> 0.0, >= 0.0.9)
    +    warden (1.2.6)
    +      rack (>= 1.0)
    +    websocket-driver (0.6.4)
    +      websocket-extensions (>= 0.1.0)
    +    websocket-extensions (0.1.2)
    +    wisper (1.6.1)
    +    wisper-rspec (0.0.2)
    +    xpath (2.0.0)
    +      nokogiri (~> 1.3)
    +
    +PLATFORMS
    +  ruby
    +
    +DEPENDENCIES
    +  bundler (~> 1.12)
    +  byebug
    +  capybara (~> 2.4)
    +  database_cleaner (~> 1.5.0)
    +  decidim!
    +  decidim-core!
    +  decidim-system!
    +  factory_girl_rails
    +  foundation_rails_helper!
    +  i18n-tasks (~> 0.9.5)
    +  launchy
    +  listen
    +  pg
    +  rake (~> 11.0)
    +  rspec (~> 3.0)
    +  rspec-rails (~> 3.5)
    +  wisper-rspec
    +
    +RUBY VERSION
    +   ruby 2.3.1p112
    +
    +BUNDLED WITH
    +   1.13.1
    
  • decidim-system/lib/decidim/system/engine.rb+30 0 added
    @@ -0,0 +1,30 @@
    +# frozen_string_literal: true
    +require "rails"
    +require "active_support/all"
    +
    +require "decidim/core"
    +require "devise"
    +require "jquery-rails"
    +require "sass-rails"
    +require "turbolinks"
    +require "jquery-turbolinks"
    +require "foundation-rails"
    +require "foundation_rails_helper"
    +require "jbuilder"
    +require "rectify"
    +
    +module Decidim
    +  module System
    +    # Decidim's core Rails Engine.
    +    class Engine < ::Rails::Engine
    +      isolate_namespace Decidim::System
    +
    +      config.to_prepare do
    +        Rails.application.config.assets.precompile += %w(
    +          decidim/system.js
    +          decidim/system.css
    +        )
    +      end
    +    end
    +  end
    +end
    
  • decidim-system/lib/decidim/system.rb+12 0 added
    @@ -0,0 +1,12 @@
    +# frozen_string_literal: true
    +require "decidim/system/engine"
    +
    +module Decidim
    +  # This module contains all the logic related to a system-wide
    +  # administration panel. The scope of the domain is to be able
    +  # to manage Organizations (tenants), as well as have a bird's
    +  # eye view of the whole system.
    +  #
    +  module System
    +  end
    +end
    
  • decidim-system/lib/generators/decidim/system/dummy_generator.rb+60 0 added
    @@ -0,0 +1,60 @@
    +# frozen_string_literal: true
    +require "rails/generators"
    +require_relative "../../../../lib/generators/decidim/app_generator"
    +
    +module Decidim
    +  module System
    +    module Generators
    +      # Generates a dummy test Rails app for the given library folder. It uses
    +      # the `AppGenerator` class to actually generate the Rails app, so this
    +      # generator only sets the path and some flags.
    +      #
    +      # The Rails app will be installed with some flags to disable git, tests,
    +      # Gemfile and other options. Refer to the `create_dummy_app` method to see
    +      # all the flags passed to the `AppGenerator` class, which is the one that
    +      # actually generates the Rails app.
    +      #
    +      # Remember that, for how generators work, actions are executed based on the
    +      # definition order of the public methods.
    +      class DummyGenerator < Rails::Generators::Base
    +        desc "Generate dummy app for testing purposes"
    +
    +        class_option :lib_name, type: :string,
    +                                desc: "The library where the dummy app will be installed"
    +
    +        class_option :migrate, type: :boolean, default: false,
    +                               desc: "Run migrations after installing decidim"
    +
    +        def cleanup
    +          remove_directory_if_exists(dummy_path)
    +        end
    +
    +        def create_dummy_app
    +          Decidim::Generators::AppGenerator.start [
    +            dummy_path,
    +            "--skip_gemfile",
    +            "--skip-bundle",
    +            "--skip-git",
    +            "--skip-keeps",
    +            "--skip-test",
    +            "--migrate=#{options[:migrate]}"
    +          ]
    +        end
    +
    +        private
    +
    +        def dummy_path
    +          ENV["DUMMY_PATH"] || "spec/#{short_lib_name}_dummy"
    +        end
    +
    +        def remove_directory_if_exists(path)
    +          remove_dir(path) if File.directory?(path)
    +        end
    +
    +        def short_lib_name
    +          options[:lib_name].split("/").last
    +        end
    +      end
    +    end
    +  end
    +end
    
  • decidim-system/lib/tasks/decidim/system_tasks.rake+5 0 added
    @@ -0,0 +1,5 @@
    +# frozen_string_literal: true
    +# desc "Explaining what the task does"
    +# task :decidim_system do
    +#   # Task goes here
    +# end
    
  • decidim-system/Rakefile+35 0 added
    @@ -0,0 +1,35 @@
    +# frozen_string_literal: true
    +begin
    +  require "bundler/setup"
    +rescue LoadError
    +  puts "You must `gem install bundler` and `bundle install` to run rake tasks"
    +end
    +
    +require_relative "../decidim-core/lib/decidim/testing_support/common_rake"
    +require "rdoc/task"
    +
    +RDoc::Task.new(:rdoc) do |rdoc|
    +  rdoc.rdoc_dir = "rdoc"
    +  rdoc.title    = "Decidim::System"
    +  rdoc.options << "--line-numbers"
    +  rdoc.rdoc_files.include("README.md")
    +  rdoc.rdoc_files.include("lib/**/*.rb")
    +end
    +
    +load "rails/tasks/statistics.rake"
    +
    +require "bundler/gem_tasks"
    +
    +task default: :test
    +
    +desc "Generates a dummy app for testing"
    +task :test_app do
    +  ENV["LIB_NAME"] = "decidim/system"
    +  Rake::Task["common:test_app"].invoke
    +end
    +
    +task test: :test_app do
    +  Dir.chdir(File.dirname(__FILE__).to_s) do
    +    sh "rspec spec"
    +  end
    +end
    
  • decidim-system/README.md+28 0 added
    @@ -0,0 +1,28 @@
    +# Decidim::System
    +Short description and motivation.
    +
    +## Usage
    +How to use my plugin.
    +
    +## Installation
    +Add this line to your application's Gemfile:
    +
    +```ruby
    +gem 'decidim-system'
    +```
    +
    +And then execute:
    +```bash
    +$ bundle
    +```
    +
    +Or install it yourself as:
    +```bash
    +$ gem install decidim-system
    +```
    +
    +## Contributing
    +Contribution directions go here.
    +
    +## License
    +The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
    
  • decidim-system/spec/commands/decidim/system/create_admin_spec.rb+48 0 added
    @@ -0,0 +1,48 @@
    +# frozen_string_literal: true
    +require "spec_helper"
    +
    +module Decidim
    +  module System
    +    describe CreateAdmin, :db do
    +      describe "call" do
    +        let(:form) { AdminForm.new(params) }
    +        let(:command) { described_class.new(form) }
    +
    +        describe "when the admin already exists" do
    +          before do
    +            create(:admin, email: "email@foo.bar")
    +          end
    +
    +          let(:params) do
    +            {
    +              email: "email@foo.bar"
    +            }
    +          end
    +
    +          it "broadcasts invalid" do
    +            expect { command.call }.to broadcast(:invalid)
    +          end
    +        end
    +
    +        describe "when the admin doesn't exist" do
    +          before do
    +            create(:admin, email: "email@foo.bar")
    +          end
    +
    +          let(:params) do
    +            {
    +              email: "different_email@foo.bar",
    +              password: "fake123",
    +              password_confirmation: "fake123"
    +            }
    +          end
    +
    +          it "broadcasts ok and creates an admin" do
    +            expect { command.call }.to broadcast(:ok)
    +            expect(Admin.where(email: "different_email@foo.bar")).to exist
    +          end
    +        end
    +      end
    +    end
    +  end
    +end
    
  • decidim-system/spec/commands/decidim/system/register_organization_spec.rb+61 0 added
    @@ -0,0 +1,61 @@
    +# frozen_string_literal: true
    +require "spec_helper"
    +
    +module Decidim
    +  module System
    +    describe RegisterOrganization, :db do
    +      describe "call" do
    +        let(:form) do
    +          RegisterOrganizationForm.new(params)
    +        end
    +
    +        let(:command) { described_class.new(form) }
    +
    +        context "when the form is valid" do
    +          let(:params) do
    +            {
    +              name: "Gotham City",
    +              host: "decide.gotham.gov",
    +              organization_admin_email: "f.laguardia@gotham.gov"
    +            }
    +          end
    +
    +          it "returns a valid response" do
    +            expect { command.call }.to broadcast(:ok)
    +          end
    +
    +          it "creates a new organization" do
    +            expect { command.call }.to change { Organization.count }.by(1)
    +            organization = Organization.last
    +
    +            expect(organization.name).to eq("Gotham City")
    +            expect(organization.host).to eq("decide.gotham.gov")
    +          end
    +
    +          it "invites a user as organization admin" do
    +            expect { command.call }.to change { User.count }.by(1)
    +            admin = User.last
    +
    +            expect(admin.email).to eq("f.laguardia@gotham.gov")
    +            expect(admin.organization.name).to eq("Gotham City")
    +            expect(admin).to be_admin
    +            expect(admin).to be_created_by_invite
    +          end
    +        end
    +
    +        context "when the form is invalid" do
    +          let(:params) do
    +            {
    +              name: nil,
    +              host: "foo.com"
    +            }
    +          end
    +
    +          it "returns an invalid response" do
    +            expect { command.call }.to broadcast(:invalid)
    +          end
    +        end
    +      end
    +    end
    +  end
    +end
    
  • decidim-system/spec/factories.rb+9 0 added
    @@ -0,0 +1,9 @@
    +require_relative "../../decidim-core/spec/factories"
    +
    +FactoryGirl.define do
    +  factory :admin, class: Decidim::System::Admin do
    +    sequence(:email)      { |n| "admin#{n}@citizen.corp" }
    +    password              "password1234"
    +    password_confirmation "password1234"
    +  end
    +end
    
  • decidim-system/spec/features/manage_admins_spec.rb+67 0 added
    @@ -0,0 +1,67 @@
    +# frozen_string_literal: true
    +
    +require "spec_helper"
    +
    +describe "Manage admins", type: :feature do
    +  let(:admin) { create(:admin) }
    +  let!(:admin2) { create(:admin) }
    +
    +  before do
    +    login_as admin, scope: :admin
    +    visit decidim_system.admins_path
    +  end
    +
    +  it "creates a new admin" do
    +    find(".actions .new").click
    +
    +    within ".new_admin" do
    +      fill_in :admin_email, with: "admin@foo.bar"
    +      fill_in :admin_password, with: "fake123"
    +      fill_in :admin_password_confirmation, with: "fake123"
    +
    +      find("*[type=submit]").click
    +    end
    +
    +    within ".flash" do
    +      expect(page).to have_content("successfully")
    +    end
    +
    +    within "table" do
    +      expect(page).to have_content("admin@foo.bar")
    +    end
    +  end
    +
    +  it "updates an admin" do
    +    within find("tr", text: admin.email) do
    +      click_link "Edit"
    +    end
    +
    +    within ".edit_admin" do
    +      fill_in :admin_email, with: "admin@another.domain"
    +
    +      find("*[type=submit]").click
    +    end
    +
    +    within ".flash" do
    +      expect(page).to have_content("successfully")
    +    end
    +
    +    within "table" do
    +      expect(page).to have_content("admin@another.domain")
    +    end
    +  end
    +
    +  it "deletes an admin" do
    +    within find("tr", text: admin2.email) do
    +      click_link "Destroy"
    +    end
    +
    +    within ".flash" do
    +      expect(page).to have_content("successfully")
    +    end
    +
    +    within "table" do
    +      expect(page).to_not have_content(admin2.email)
    +    end
    +  end
    +end
    
  • decidim-system/spec/features/organizations_spec.rb+61 0 added
    @@ -0,0 +1,61 @@
    +# frozen_string_literal: true
    +
    +require "spec_helper"
    +
    +describe "Organizations", type: :feature do
    +  let(:admin) { create(:admin) }
    +
    +  context "authenticated admin" do
    +    before do
    +      login_as admin, scope: :admin
    +      visit decidim_system.root_path
    +    end
    +
    +    context "creating an organization" do
    +      before do
    +        click_link "Organizations"
    +        click_link "New"
    +      end
    +
    +      it "creates a new organization" do
    +        fill_in "Name", with: "Citizen Corp"
    +        fill_in "Host", with: "www.citizen.corp"
    +        fill_in "Organization admin email", with: "mayor@citizen.corp"
    +        click_button "Create organization & invite admin"
    +
    +        expect(page).to have_css("div.flash.success")
    +        expect(page).to have_content("Citizen Corp")
    +      end
    +
    +      context "with invalid data" do
    +        it "doesn't create an organization" do
    +          fill_in "Name", with: "Bad"
    +          click_button "Create organization & invite admin"
    +
    +          expect(page).to have_css("div.flash.alert")
    +          expect(page).to_not have_content("Organization created successfully")
    +        end
    +      end
    +    end
    +
    +    context "editing an organization" do
    +      let!(:organization) { create(:organization, name: "Citizen Corp") }
    +
    +      before do
    +        click_link "Organizations"
    +        within("table tbody tr:first") do
    +          click_link "Edit"
    +        end
    +      end
    +
    +      it "edits the data" do
    +        fill_in "Name", with: "Citizens Rule!"
    +        fill_in "Host", with: "www.foo.org"
    +        click_button "Save"
    +
    +        expect(page).to have_css("div.flash.success")
    +        expect(page).to have_content("Citizens Rule!")
    +      end
    +    end
    +  end
    +end
    
  • decidim-system/spec/features/sessions_spec.rb+38 0 added
    @@ -0,0 +1,38 @@
    +# frozen_string_literal: true
    +require "spec_helper"
    +
    +describe "Sessions", type: :feature do
    +  let!(:admin) do
    +    create(:admin, email: "admin@example.org",
    +                   password: "123456",
    +                   password_confirmation: "123456")
    +  end
    +
    +  before(:each) do
    +    visit decidim_system.root_path
    +  end
    +
    +  context "when using a correct username and password" do
    +    it "lets you into the system panel" do
    +      within ".new_admin" do
    +        fill_in :admin_email, with: "admin@example.org"
    +        fill_in :admin_password, with: "123456"
    +        click_button "Log in"
    +      end
    +
    +      expect(page).to have_content("Dashboard")
    +    end
    +  end
    +
    +  context "when using an incorrectusername and password" do
    +    it "doesn't let you in the admin panel" do
    +      within ".new_admin" do
    +        fill_in :admin_email, with: "admin@example.org"
    +        fill_in :admin_password, with: "forged_password"
    +        find("*[type=submit]").click
    +      end
    +
    +      expect(page).not_to have_content("Dashboard")
    +    end
    +  end
    +end
    
  • decidim-system/spec/i18n_spec.rb+18 0 added
    @@ -0,0 +1,18 @@
    +# frozen_string_literal: true
    +require 'i18n/tasks'
    +
    +RSpec.describe 'I18n' do
    +  let(:i18n) { I18n::Tasks::BaseTask.new }
    +  let(:missing_keys) { i18n.missing_keys }
    +  let(:unused_keys) { i18n.unused_keys }
    +
    +  it 'does not have missing keys' do
    +    expect(missing_keys).to be_empty,
    +      "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them"
    +  end
    +
    +  it 'does not have unused keys' do
    +    expect(unused_keys).to be_empty,
    +      "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them"
    +  end
    +end
    
  • decidim-system/spec/spec_helper.rb+39 0 added
    @@ -0,0 +1,39 @@
    +# frozen_string_literal: true
    +ENV["RAILS_ENV"] ||= "test"
    +
    +if ENV["CI"]
    +  require "simplecov"
    +  SimpleCov.start
    +
    +  require "codecov"
    +  SimpleCov.formatter = SimpleCov::Formatter::Codecov
    +end
    +
    +begin
    +  require File.expand_path("../system_dummy/config/environment", __FILE__)
    +rescue LoadError
    +  puts "Could not load dummy application. Please ensure you have run `bundle exec rake test_app`"
    +  exit
    +end
    +
    +require "rspec/rails"
    +require "factory_girl_rails"
    +require "database_cleaner"
    +require "byebug"
    +
    +# Requires supporting files with custom matchers and macros, etc,
    +# in ./support/ and its subdirectories.
    +Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
    +
    +RSpec.configure do |config|
    +  config.color = true
    +  config.fail_fast = ENV["FAIL_FAST"] || false
    +  config.infer_spec_type_from_file_location!
    +  config.mock_with :rspec
    +  config.raise_errors_for_deprecations!
    +
    +  # If you're not using ActiveRecord, or you'd prefer not to run each of your
    +  # examples within a transaction, comment the following line or assign false
    +  # instead of true.
    +  config.use_transactional_fixtures = false
    +end
    
  • decidim-system/spec/support/capybara.rb+0 0 added
  • decidim-system/spec/support/database_cleaner.rb+12 0 added
    @@ -0,0 +1,12 @@
    +RSpec.configure do |config|
    +  config.before(:suite) do
    +    DatabaseCleaner.strategy = :transaction
    +    DatabaseCleaner.clean_with(:truncation)
    +  end
    +
    +  config.around(:each) do |example|
    +    DatabaseCleaner.cleaning do
    +      example.run
    +    end
    +  end
    +end
    
  • decidim-system/spec/support/factory_girl.rb+3 0 added
    @@ -0,0 +1,3 @@
    +RSpec.configure do |config|
    +  config.include FactoryGirl::Syntax::Methods
    +end
    
  • decidim-system/spec/support/warden.rb+7 0 added
    @@ -0,0 +1,7 @@
    +RSpec.configure do |config|
    +  config.include Warden::Test::Helpers, type: :feature
    +
    +  config.after :each, type: :feature do
    +    Warden.test_reset!
    +  end
    +end
    
  • decidim-system/spec/support/wisper.rb+5 0 added
    @@ -0,0 +1,5 @@
    +require 'wisper/rspec/matchers'
    +
    +RSpec::configure do |config|
    +  config.include(Wisper::RSpec::BroadcastMatcher)
    +end
    
  • Gemfile+5 0 modified
    @@ -5,3 +5,8 @@ eval(File.read(File.dirname(__FILE__) + "/common_gemfile.rb"))
     gemspec
     gem "rspec_junit_formatter", "0.2.3", group: :test, require: false
     gem "codecov", require: false, group: :test
    +
    +gemspec path: "."
    +gemspec path: "decidim-core"
    +
    +gem "rubocop"
    
  • Gemfile.lock+120 19 modified
    @@ -1,13 +1,51 @@
    +GIT
    +  remote: git://github.com/sgruhier/foundation_rails_helper.git
    +  revision: 809f127b76747b53fb199ff4e5d3f06aa3d35f97
    +  specs:
    +    foundation_rails_helper (2.0.0)
    +      actionpack (>= 4.1)
    +      activemodel (>= 4.1)
    +      activesupport (>= 4.1)
    +      railties (>= 4.1)
    +      tzinfo (~> 1.2, >= 1.2.2)
    +
     PATH
       remote: .
       specs:
         decidim (0.0.1.alpha2)
           decidim-core (= 0.0.1.alpha2)
    +      decidim-system (= 0.0.1.alpha2)
    +      rails (~> 5.0.0)
    +    decidim-system (0.0.1.alpha2)
    +      active_link_to (~> 1.0.0)
    +      decidim-core
    +      devise (~> 4.2)
    +      devise_invitable (~> 1.7.0)
    +      foundation-rails (~> 6.2.3.0)
    +      foundation_rails_helper (~> 2.0.0)
    +      jbuilder (~> 2.5)
    +      jquery-rails (~> 4.0)
    +      jquery-turbolinks (~> 2.1.0)
           rails (~> 5.0.0)
    +      rectify (~> 0.6)
    +      sass-rails (~> 5.0.0)
    +      turbolinks (~> 5.0.0)
    +
    +PATH
    +  remote: decidim-core
    +  specs:
         decidim-core (0.0.1.alpha2)
    -      devise
    -      foundation-rails (~> 6.2.3)
    +      active_link_to (~> 1.0.0)
    +      devise (~> 4.2)
    +      foundation-rails (~> 6.2.3.0)
    +      foundation_rails_helper (~> 2.0.0)
    +      jbuilder (~> 2.5)
    +      jquery-rails (~> 4.0)
    +      jquery-turbolinks (~> 2.1.0)
           rails (~> 5.0.0)
    +      rectify (~> 0.6)
    +      sass-rails (~> 5.0.0)
    +      turbolinks (~> 5.0.0)
     
     GEM
       remote: https://rubygems.org/
    @@ -35,6 +73,8 @@ GEM
           erubis (~> 2.7.0)
           rails-dom-testing (~> 2.0)
           rails-html-sanitizer (~> 1.0, >= 1.0.2)
    +    active_link_to (1.0.3)
    +      actionpack
         activejob (5.0.0.1)
           activesupport (= 5.0.0.1)
           globalid (>= 0.3.6)
    @@ -51,6 +91,11 @@ GEM
           tzinfo (~> 1.1)
         addressable (2.4.0)
         arel (7.1.2)
    +    ast (2.3.0)
    +    axiom-types (0.1.1)
    +      descendants_tracker (~> 0.0.4)
    +      ice_nine (~> 0.11.0)
    +      thread_safe (~> 0.3, >= 0.3.1)
         babel-source (5.8.35)
         babel-transpiler (0.7.0)
           babel-source (>= 4.0, < 6)
    @@ -69,39 +114,65 @@ GEM
           json
           simplecov
           url
    -    coffee-rails (4.2.1)
    -      coffee-script (>= 2.2.0)
    -      railties (>= 4.0.0, < 5.2.x)
    -    coffee-script (2.4.1)
    -      coffee-script-source
    -      execjs
    -    coffee-script-source (1.10.0)
    +    coercible (1.0.0)
    +      descendants_tracker (~> 0.0.1)
         concurrent-ruby (1.0.2)
    +    database_cleaner (1.5.3)
    +    descendants_tracker (0.0.4)
    +      thread_safe (~> 0.3, >= 0.3.1)
         devise (4.2.0)
           bcrypt (~> 3.0)
           orm_adapter (~> 0.1)
           railties (>= 4.1.0, < 5.1)
           responders
           warden (~> 1.2.3)
    +    devise_invitable (1.7.0)
    +      actionmailer (>= 4.0.0)
    +      devise (>= 4.0.0)
         diff-lcs (1.2.5)
         docile (1.1.5)
    +    easy_translate (0.5.0)
    +      json
    +      thread
    +      thread_safe
    +    equalizer (0.0.11)
         erubis (2.7.0)
         execjs (2.7.0)
    +    factory_girl (4.7.0)
    +      activesupport (>= 3.0.0)
    +    factory_girl_rails (4.7.0)
    +      factory_girl (~> 4.7.0)
    +      railties (>= 3.0.0)
         ffi (1.9.14)
         foundation-rails (6.2.3.0)
           railties (>= 3.1.0)
           sass (>= 3.3.0, < 3.5)
           sprockets-es6 (>= 0.9.0)
         globalid (0.3.7)
           activesupport (>= 4.1.0)
    +    highline (1.7.8)
         i18n (0.7.0)
    +    i18n-tasks (0.9.5)
    +      activesupport (>= 4.0.2)
    +      ast (>= 2.1.0)
    +      easy_translate (>= 0.5.0)
    +      erubis
    +      highline (>= 1.7.3)
    +      i18n
    +      parser (>= 2.2.3.0)
    +      term-ansicolor (>= 1.3.2)
    +      terminal-table (>= 1.5.1)
    +    ice_nine (0.11.2)
         jbuilder (2.6.0)
           activesupport (>= 3.0.0, < 5.1)
           multi_json (~> 1.2)
         jquery-rails (4.2.1)
           rails-dom-testing (>= 1, < 3)
           railties (>= 4.2.0)
           thor (>= 0.14, < 2.0)
    +    jquery-turbolinks (2.1.0)
    +      railties (>= 3.1.0)
    +      turbolinks
         json (2.0.2)
         listen (3.0.8)
           rb-fsevent (~> 0.9, >= 0.9.4)
    @@ -122,8 +193,11 @@ GEM
           mini_portile2 (~> 2.1.0)
           pkg-config (~> 1.1.7)
         orm_adapter (0.5.0)
    +    parser (2.3.1.4)
    +      ast (~> 2.2)
         pg (0.18.4)
         pkg-config (1.1.7)
    +    powerpack (0.1.1)
         rack (2.0.1)
         rack-test (0.6.3)
           rack (>= 1.0)
    @@ -150,10 +224,17 @@ GEM
           method_source
           rake (>= 0.8.7)
           thor (>= 0.18.1, < 2.0)
    -    rake (10.5.0)
    +    rainbow (2.1.0)
    +    rake (11.3.0)
         rb-fsevent (0.9.7)
         rb-inotify (0.9.7)
           ffi (>= 0.5.0)
    +    rectify (0.6.1)
    +      activemodel (>= 4.1.0)
    +      activerecord (>= 4.1.0)
    +      activesupport (>= 4.1.0)
    +      virtus (~> 1.0.5)
    +      wisper (>= 1.6.1)
         responders (2.3.0)
           railties (>= 4.2.0, < 5.1)
         rspec (3.5.0)
    @@ -180,6 +261,13 @@ GEM
         rspec_junit_formatter (0.2.3)
           builder (< 4)
           rspec-core (>= 2, < 4, != 2.12.0)
    +    rubocop (0.43.0)
    +      parser (>= 2.3.1.1, < 3.0)
    +      powerpack (~> 0.1)
    +      rainbow (>= 1.99.1, < 3.0)
    +      ruby-progressbar (~> 1.7)
    +      unicode-display_width (~> 1.0, >= 1.0.1)
    +    ruby-progressbar (1.8.1)
         sass (3.4.22)
         sass-rails (5.0.6)
           railties (>= 4.0.0, < 6)
    @@ -203,22 +291,34 @@ GEM
           actionpack (>= 4.0)
           activesupport (>= 4.0)
           sprockets (>= 3.0.0)
    +    term-ansicolor (1.3.2)
    +      tins (~> 1.0)
    +    terminal-table (1.7.3)
    +      unicode-display_width (~> 1.1.1)
         thor (0.19.1)
    +    thread (0.2.2)
         thread_safe (0.3.5)
         tilt (2.0.5)
    +    tins (1.12.0)
         turbolinks (5.0.1)
           turbolinks-source (~> 5)
         turbolinks-source (5.0.0)
         tzinfo (1.2.2)
           thread_safe (~> 0.1)
    -    uglifier (3.0.2)
    -      execjs (>= 0.3.0, < 3)
    +    unicode-display_width (1.1.1)
         url (0.3.2)
    +    virtus (1.0.5)
    +      axiom-types (~> 0.1)
    +      coercible (~> 1.0)
    +      descendants_tracker (~> 0.0, >= 0.0.3)
    +      equalizer (~> 0.0, >= 0.0.9)
         warden (1.2.6)
           rack (>= 1.0)
         websocket-driver (0.6.4)
           websocket-extensions (>= 0.1.0)
         websocket-extensions (0.1.2)
    +    wisper (1.6.1)
    +    wisper-rspec (0.0.2)
         xpath (2.0.0)
           nokogiri (~> 1.3)
     
    @@ -230,19 +330,20 @@ DEPENDENCIES
       byebug
       capybara (~> 2.4)
       codecov
    -  coffee-rails (~> 4.2)
    +  database_cleaner (~> 1.5.0)
       decidim!
    -  jbuilder (~> 2.5)
    -  jquery-rails
    +  decidim-core!
    +  factory_girl_rails
    +  foundation_rails_helper!
    +  i18n-tasks (~> 0.9.5)
       listen
       pg
    -  rake (~> 10.0)
    +  rake (~> 11.0)
       rspec (~> 3.0)
       rspec-rails (~> 3.5)
       rspec_junit_formatter (= 0.2.3)
    -  sass-rails (~> 5.0)
    -  turbolinks (~> 5)
    -  uglifier (>= 1.3.0)
    +  rubocop
    +  wisper-rspec
     
     RUBY VERSION
        ruby 2.3.1p112
    
  • .hound.yml+4 0 modified
    @@ -1,4 +1,8 @@
     ruby:
       config_file: .rubocop.yml
    +scss:
    +  enabled: false
    +jshint:
    +  enabled: false
     
     fail_on_violations: true
    \ No newline at end of file
    
  • lib/decidim.rb+1 0 modified
    @@ -1,5 +1,6 @@
     # frozen_string_literal: true
     require "decidim/core"
    +require "decidim/system"
     
     # Module declaration.
     module Decidim
    
  • lib/generators/decidim/templates/Gemfile.erb+3 0 modified
    @@ -21,6 +21,9 @@ gem 'jquery-rails'
     gem 'turbolinks', '~> 5'
     gem 'redis', '~> 3.0'
     
    +gem 'foundation_rails_helper', github: 'sgruhier/foundation_rails_helper'
    +
    +
     group :development, :test do
       gem 'byebug', platform: :mri
     end
    
  • Rakefile+3 12 modified
    @@ -8,26 +8,17 @@ rescue LoadError
       raise "Could not find decidim/testing_support/common_rake. You need to run this command using Bundler."
     end
     
    -DECIDIM_GEMS = %w(core).freeze
    +DECIDIM_GEMS = %w(core system).freeze
     
     RSpec::Core::RakeTask.new(:spec)
     
     task default: :test
     
     desc "Runs all tests in all Decidim engines"
    -task test: :test_app do
    +task :test do
       DECIDIM_GEMS.each do |gem_name|
         Dir.chdir("#{File.dirname(__FILE__)}/decidim-#{gem_name}") do
    -      sh "rspec"
    -    end
    -  end
    -end
    -
    -desc "Generates a dummy app for testing for every Decidim engine"
    -task :test_app do
    -  DECIDIM_GEMS.each do |gem_name|
    -    Dir.chdir("#{File.dirname(__FILE__)}/decidim-#{gem_name}") do
    -      sh "rake test_app"
    +      sh "rake"
         end
       end
     end
    
  • .rubocop.yml+13 3 modified
    @@ -23,9 +23,9 @@ AllCops:
         - '**/Gemfile'
         - 'vendor/**/*'
         - 'spec/*'
    -    - 'decidim-core/spec/**/*'
    -    - 'decidim-core/test/**/*'
    -    - 'decidim-core/db/**/*'
    +    - 'decidim-*/spec/**/*'
    +    - 'decidim-*/test/**/*'
    +    - 'decidim-*/db/**/*'
       # Default formatter will be used if no -f/--format option is given.
       DefaultFormatter: progress
       # Cop names are not displayed in offense messages by default. Change behavior
    @@ -93,6 +93,10 @@ Style/Alias:
         - prefer_alias
         - prefer_alias_method
     
    +Style/AccessorMethodName:
    +  Exclude:
    +    - "lib/generators/decidim/app_generator.rb"
    +
     # Align the elements of a hash literal if they span more than one line.
     Style/AlignHash:
       # Alignment of entries using hash rocket as separator. Valid values are:
    @@ -293,6 +297,7 @@ Style/CaseIndentation:
       IndentationWidth: ~
     
     Style/ClassAndModuleChildren:
    +  Enabled: false
       # Checks the style of children definitions at classes and modules.
       #
       # Basically there are two different styles:
    @@ -814,6 +819,10 @@ Style/PredicateName:
       Exclude:
         - 'spec/**/*'
     
    +Style/Documentation:
    +  Exclude:
    +    - '**/*/spec/**/*_spec.rb'
    +
     Style/RaiseArgs:
       EnforcedStyle: exploded
       SupportedStyles:
    @@ -1081,6 +1090,7 @@ Metrics/AbcSize:
       # The ABC size is a calculated magnitude, so this number can be an Integer or
       # a Float.
       Max: 15
    +  Enabled: false
     
     Metrics/BlockNesting:
       Max: 3
    
  • spec/decidim_spec.rb+1 1 modified
    @@ -1,5 +1,5 @@
     # frozen_string_literal: true
    -require "spec_helper"
    +require_relative "spec_helper"
     
     describe Decidim do
       it "has a version number" do
    
94d859c7de08

clear invitation token when password is reset

https://github.com/scambra/devise_invitableSergio CambraFeb 21, 2011via ghsa
7 files changed · +20 5
  • lib/devise_invitable/model.rb+6 0 modified
    @@ -77,6 +77,12 @@ def valid_password?(password)
     
           protected
     
    +        # Clear invitation token when reset password token is cleared too
    +        def clear_reset_password_token
    +          self.invitation_token = nil if invited?
    +          super
    +        end
    +
             # Checks if the invitation for the user is within the limit time.
             # We do this by calculating if the difference between today and the
             # invitation sent date does not exceed the invite for time configured.
    
  • test/models/invitable_test.rb+9 0 modified
    @@ -118,6 +118,15 @@ def setup
         assert_present user.invitation_token
       end
     
    +  test 'should clear invitation token while resetting the password' do
    +    user = User.invite!(:email => "valid@email.com")
    +    user.send(:generate_reset_password_token!)
    +    assert_present user.reset_password_token
    +    assert_present user.invitation_token
    +    User.reset_password_by_token(:reset_password_token => user.reset_password_token, :password => '123456789', :password_confirmation => '123456789')
    +    assert_nil user.reload.invitation_token
    +  end
    +
       test 'should reset invitation token and send invitation by email' do
         user = new_user
         assert_difference('ActionMailer::Base.deliveries.size') do
    
  • test/models_test.rb+1 1 modified
    @@ -17,7 +17,7 @@ def assert_include_modules(klass, *modules)
       end
     
       test 'should include Devise modules' do
    -    assert_include_modules User, :database_authenticatable, :registerable, :validatable, :confirmable, :invitable
    +    assert_include_modules User, :database_authenticatable, :registerable, :validatable, :confirmable, :invitable, :recoverable
       end
     
       test 'should have a default value for invite_for' do
    
  • test/rails_app/app/active_record/user.rb+1 1 modified
    @@ -1,5 +1,5 @@
     class User < ActiveRecord::Base
    -  devise :database_authenticatable, :registerable, :validatable, :confirmable, :invitable
    +  devise :database_authenticatable, :registerable, :validatable, :confirmable, :invitable, :recoverable
       
       attr_accessible :email, :username, :password, :password_confirmation
       
    
  • test/rails_app/app/mongoid/user.rb+1 1 modified
    @@ -5,7 +5,7 @@ class User
       field :created_at, :type => DateTime
       field :username,   :type => String
       
    -  devise :database_authenticatable, :registerable, :validatable, :confirmable, :invitable
    +  devise :database_authenticatable, :registerable, :validatable, :confirmable, :invitable, :recoverable
       
       validates :username, :length => { :maximum => 20 }
     end
    
  • test/rails_app/config/initializers/devise.rb+1 1 modified
    @@ -82,7 +82,7 @@
     
       # If true, uses the password salt as remember token. This should be turned
       # to false if you are not using database authenticatable.
    -  config.use_salt_as_remember_token = true
    +  config.use_salt_as_remember_token = false
     
       # ==> Configuration for :validatable
       # Range for password length. Default is 6..20.
    
  • test/rails_app/db/migrate/20100401102949_create_tables.rb+1 1 modified
    @@ -5,7 +5,7 @@ def self.up
           t.string :username
           t.confirmable
           t.invitable
    -      t.encryptable
    +      t.recoverable
           
           t.timestamps
         end
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

11

News mentions

0

No linked articles in our index yet.