diff --git a/Gemfile b/Gemfile
index 1819992d3..e61e92e25 100644
--- a/Gemfile
+++ b/Gemfile
@@ -23,6 +23,7 @@ gem 'turbolinks'
gem 'sprockets', '~> 3.6.3'
gem 'devise', '~> 3.5.7'
+gem 'devise_security_extension'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'
gem 'omniauth'
@@ -41,7 +42,7 @@ gem 'ckeditor', '~> 4.2.0'
gem 'invisible_captcha', '~> 0.9.1'
gem 'cancancan'
gem 'social-share-button'
-gem 'initialjs-rails', '0.2.0.1'
+gem 'initialjs-rails', '0.2.0.4'
gem 'unicorn', '~> 5.1.0'
gem 'paranoia'
gem 'rinku', require: 'rails_rinku'
@@ -79,7 +80,7 @@ group :development, :test do
gem 'i18n-tasks'
gem 'capistrano', '3.5.0', require: false
gem "capistrano-bundler", '1.1.4', require: false
- gem "capistrano-rails", '1.1.7', require: false
+ gem "capistrano-rails", '1.1.8', require: false
gem "capistrano-rvm", require: false
gem 'capistrano3-delayed-job', '~> 1.0'
gem "bullet"
diff --git a/Gemfile.lock b/Gemfile.lock
index 121d7b86e..68570d756 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -51,7 +51,7 @@ GEM
safely_block (>= 0.1.1)
user_agent_parser
uuidtools
- airbrussh (1.0.2)
+ airbrussh (1.1.1)
sshkit (>= 1.6.1, != 1.7.0)
akami (1.3.1)
gyoku (>= 0.4.0)
@@ -70,7 +70,7 @@ GEM
bullet (5.2.0)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.10.0)
- byebug (9.0.5)
+ byebug (9.0.6)
cancancan (1.15.0)
capistrano (3.5.0)
airbrussh (>= 1.0.0)
@@ -81,8 +81,8 @@ GEM
capistrano-bundler (1.1.4)
capistrano (~> 3.1)
sshkit (~> 1.2)
- capistrano-harrow (0.5.2)
- capistrano-rails (1.1.7)
+ capistrano-harrow (0.5.3)
+ capistrano-rails (1.1.8)
capistrano (~> 3.1)
capistrano-bundler (~> 1.1)
capistrano-rvm (0.1.2)
@@ -120,7 +120,7 @@ GEM
term-ansicolor (~> 1.3)
thor (~> 0.19.1)
tins (>= 1.6.0, < 2)
- daemons (1.2.3)
+ daemons (1.2.4)
dalli (2.7.6)
database_cleaner (1.5.3)
debug_inspector (0.0.2)
@@ -138,6 +138,9 @@ GEM
warden (~> 1.2.3)
devise-async (0.10.2)
devise (>= 3.2, < 4.0)
+ devise_security_extension (0.10.0)
+ devise (>= 3.0.0, < 4.0)
+ railties (>= 3.2.6, < 5.0)
diff-lcs (1.2.5)
docile (1.1.5)
easy_translate (0.5.0)
@@ -176,7 +179,7 @@ GEM
geocoder (1.3.7)
globalid (0.3.7)
activesupport (>= 4.1.0)
- groupdate (3.0.1)
+ groupdate (3.0.2)
activesupport (>= 3)
gyoku (1.3.1)
builder (>= 2.1.2)
@@ -196,8 +199,8 @@ GEM
parser (>= 2.2.3.0)
term-ansicolor (>= 1.3.2)
terminal-table (>= 1.5.1)
- initialjs-rails (0.2.0.1)
- railties (>= 3.1, < 5.0)
+ initialjs-rails (0.2.0.4)
+ railties (>= 3.1, < 6.0)
invisible_captcha (0.9.1)
rails
jquery-rails (4.1.1)
@@ -228,14 +231,14 @@ GEM
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
mini_portile2 (2.1.0)
- minitest (5.9.0)
+ minitest (5.9.1)
multi_json (1.12.1)
multi_xml (0.5.5)
multipart-post (2.0.0)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
net-ssh (3.2.0)
- newrelic_rpm (3.16.0.318)
+ newrelic_rpm (3.16.3.323)
nokogiri (1.6.8)
mini_portile2 (~> 2.1.0)
pkg-config (~> 1.1.7)
@@ -315,7 +318,7 @@ GEM
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
raindrops (0.16.0)
- rake (11.2.2)
+ rake (11.3.0)
redcarpet (3.3.4)
referer-parser (0.3.0)
request_store (1.3.1)
@@ -328,7 +331,7 @@ GEM
rspec-core (~> 3.5.0)
rspec-expectations (~> 3.5.0)
rspec-mocks (~> 3.5.0)
- rspec-core (3.5.1)
+ rspec-core (3.5.4)
rspec-support (~> 3.5.0)
rspec-expectations (3.5.0)
diff-lcs (>= 1.2.0, < 2.0)
@@ -336,7 +339,7 @@ GEM
rspec-mocks (3.5.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.5.0)
- rspec-rails (3.5.1)
+ rspec-rails (3.5.2)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
@@ -386,7 +389,7 @@ GEM
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
- sshkit (1.11.1)
+ sshkit (1.11.3)
net-scp (>= 1.1.2)
net-ssh (>= 2.8.0)
term-ansicolor (1.3.2)
@@ -408,7 +411,7 @@ GEM
tilt (>= 1.4, < 3)
tzinfo (1.2.2)
thread_safe (~> 0.1)
- uglifier (3.0.1)
+ uglifier (3.0.2)
execjs (>= 0.3.0, < 3)
unicorn (5.1.0)
kgio (~> 2.6)
@@ -447,7 +450,7 @@ DEPENDENCIES
cancancan
capistrano (= 3.5.0)
capistrano-bundler (= 1.1.4)
- capistrano-rails (= 1.1.7)
+ capistrano-rails (= 1.1.8)
capistrano-rvm
capistrano3-delayed-job (~> 1.0)
capybara
@@ -460,6 +463,7 @@ DEPENDENCIES
delayed_job_active_record (~> 4.1.0)
devise (~> 3.5.7)
devise-async
+ devise_security_extension
email_spec
factory_girl_rails
faker
@@ -468,7 +472,7 @@ DEPENDENCIES
fuubar
groupdate
i18n-tasks
- initialjs-rails (= 0.2.0.1)
+ initialjs-rails (= 0.2.0.4)
invisible_captcha (~> 0.9.1)
jquery-rails
jquery-ui-rails
@@ -506,4 +510,4 @@ DEPENDENCIES
whenever
BUNDLED WITH
- 1.12.5
+ 1.13.1
diff --git a/README.md b/README.md
index 2690b84a7..b6c10b878 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,6 @@ cd consul
bundle install
cp config/database.yml.example config/database.yml
cp config/secrets.yml.example config/secrets.yml
-rake db:create
bin/rake db:setup
bin/rake db:dev_seed
RAILS_ENV=test rake db:setup
diff --git a/README_ES.md b/README_ES.md
index 6051f1343..6f0f413ec 100644
--- a/README_ES.md
+++ b/README_ES.md
@@ -34,7 +34,6 @@ cd consul
bundle install
cp config/database.yml.example config/database.yml
cp config/secrets.yml.example config/secrets.yml
-rake db:create
bin/rake db:setup
bin/rake db:dev_seed
RAILS_ENV=test rake db:setup
diff --git a/app/assets/fonts/icons.eot b/app/assets/fonts/icons.eot
index f1420b532..c3c5d6289 100644
Binary files a/app/assets/fonts/icons.eot and b/app/assets/fonts/icons.eot differ
diff --git a/app/assets/fonts/icons.svg b/app/assets/fonts/icons.svg
index a7f352c4f..a548d9575 100644
--- a/app/assets/fonts/icons.svg
+++ b/app/assets/fonts/icons.svg
@@ -54,4 +54,8 @@
+
+
+
+
diff --git a/app/assets/fonts/icons.ttf b/app/assets/fonts/icons.ttf
index 16401c79b..6f938f863 100644
Binary files a/app/assets/fonts/icons.ttf and b/app/assets/fonts/icons.ttf differ
diff --git a/app/assets/fonts/icons.woff b/app/assets/fonts/icons.woff
index 83689755d..f3c31e804 100644
Binary files a/app/assets/fonts/icons.woff and b/app/assets/fonts/icons.woff differ
diff --git a/app/assets/images/ballot.gif b/app/assets/images/ballot.gif
new file mode 100644
index 000000000..18cf0717f
Binary files /dev/null and b/app/assets/images/ballot.gif differ
diff --git a/app/assets/images/ballot_tiny.gif b/app/assets/images/ballot_tiny.gif
new file mode 100644
index 000000000..976d37591
Binary files /dev/null and b/app/assets/images/ballot_tiny.gif differ
diff --git a/app/assets/stylesheets/icons.scss b/app/assets/stylesheets/icons.scss
index a92a3b61a..a4b0da4f7 100644
--- a/app/assets/stylesheets/icons.scss
+++ b/app/assets/stylesheets/icons.scss
@@ -172,3 +172,6 @@
.icon-arrow-right:before {
content: "\55";
}
+.icon-checkmark-circle:before {
+ content: "\59";
+}
diff --git a/app/assets/stylesheets/layout.scss b/app/assets/stylesheets/layout.scss
index b99f0300a..c180735eb 100644
--- a/app/assets/stylesheets/layout.scss
+++ b/app/assets/stylesheets/layout.scss
@@ -732,6 +732,56 @@ form {
// 07. Callout
// -----------
+.callout-slide {
+ animation-duration: 1s;
+ -webkit-animation-duration: 1s;
+ animation-fill-mode: both;
+ -webkit-animation-fill-mode: both;
+ animation-name: slide;
+ -webkit-animation-name: slide;
+}
+
+@-webkit-keyframes slide {
+ from {
+ -webkit-transform: translate3d(100%, 0, 0);
+ transform: translate3d(100%, 0, 0);
+ visibility: visible;
+ }
+
+ to {
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
+ }
+}
+
+@keyframes slide {
+ from {
+ -webkit-transform: translate3d(100%, 0, 0);
+ transform: translate3d(100%, 0, 0);
+ visibility: visible;
+ }
+
+ to {
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
+ }
+}
+
+.notice-container {
+ min-width: $line-height*12;
+ position: absolute;
+ right: 24px;
+ top: 24px;
+
+ .notice {
+ height: $line-height*4;
+
+ .notice-text {
+ width: 95%;
+ }
+ }
+}
+
.callout {
font-size: $small-font-size;
diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss
index ffe08617f..efd385c19 100644
--- a/app/assets/stylesheets/participation.scss
+++ b/app/assets/stylesheets/participation.scss
@@ -5,6 +5,7 @@
// 03. Show participation
// 04. List participation
// 05. Featured
+// 06. Proposals successfull
//
// 01. Votes and supports
@@ -269,6 +270,21 @@
}
}
+.message {
+ @include supports;
+ background: none;
+ border-top: 0;
+
+ @include breakpoint(medium) {
+ border-left: 1px solid $border;
+ margin: $line-height rem-calc(-25) 0 rem-calc(12);
+ }
+
+ p {
+ font-size: $small-font-size;
+ }
+}
+
// 02. New participation
// ---------------------
@@ -548,14 +564,14 @@
.debates-list, .proposals-list, .investment-projects-list {
- @include breakpoint(small) {
+ @include breakpoint(medium) {
margin-bottom: rem-calc(48);
}
}
.investment-projects-list {
- @include breakpoint(small) {
+ @include breakpoint(medium) {
min-height: $line-height*15;
}
}
@@ -574,7 +590,7 @@
min-height: rem-calc(192);
padding: rem-calc(12) rem-calc(12) 0 rem-calc(12);
- @include breakpoint(small) {
+ @include breakpoint(medium) {
margin-bottom: rem-calc(-1);
padding-bottom: rem-calc(12);
}
@@ -732,7 +748,7 @@
border: 1px solid $votes-border;
margin: 0 rem-calc(-12);
- @include breakpoint(small) {
+ @include breakpoint(medium) {
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
margin: 0 rem-calc(-25) 0 rem-calc(12);
@@ -750,7 +766,7 @@
right: -1px;
border-width: 13px 13px 0 0;
- @include breakpoint(small) {
+ @include breakpoint(medium) {
content: "";
}
}
@@ -762,7 +778,7 @@
padding-top: rem-calc(12);
vertical-align: top;
- @include breakpoint(small) {
+ @include breakpoint(medium) {
display: block;
float: none;
line-height: $line-height*2;
@@ -771,7 +787,7 @@
}
}
- @include breakpoint(small) {
+ @include breakpoint(medium) {
.like, .unlike {
span.percentage {
@@ -790,7 +806,7 @@
border: 1px solid $proposals-border;
margin: 0 rem-calc(-12);
- @include breakpoint(small) {
+ @include breakpoint(medium) {
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
margin: 0 rem-calc(-25) 0 rem-calc(12);
@@ -808,7 +824,7 @@
right: -1px;
border-width: 13px 13px 0 0;
- @include breakpoint(small) {
+ @include breakpoint(medium) {
content: "";
}
}
@@ -819,7 +835,7 @@
padding-top: rem-calc(12);
vertical-align: top;
- @include breakpoint(small) {
+ @include breakpoint(medium) {
display: block;
float: none;
margin-left: 0;
@@ -840,7 +856,7 @@
min-height: rem-calc(180);
padding-top: 0;
- @include breakpoint(small) {
+ @include breakpoint(medium) {
padding-top: $line-height*1.5;
}
@@ -929,7 +945,8 @@
// 05. Featured
// ------------
-.featured-debates, .featured-proposals {
+.featured-debates, .featured-proposals,
+.proposals-ballot, .proposals-ballot-list {
padding: $line-height/2 0;
@include breakpoint(medium) {
@@ -1029,3 +1046,112 @@
}
}
}
+
+// 06. Proposals successfull
+// -------------------------
+
+.dark-heading {
+ background: #2D3E50;
+ color: white;
+
+ @include breakpoint(medium) {
+ padding-bottom: $line-height;
+ }
+
+ p {
+ margin-bottom: 0;
+
+ &.title {
+ color: #FFD200;
+ }
+ }
+
+ .info {
+ background: #314253;
+ padding-top: $line-height;
+
+ @include breakpoint(medium) {
+ border-top: rem-calc(6) solid #FFD200;
+ }
+ }
+}
+
+.featured-proposals-ballot-banner {
+ background: #2D3E50 image-url("ballot_tiny.gif") no-repeat;
+ background-position: 75% 0;
+ position: relative;
+
+ h2, a:hover h2 {
+ color: #FFD200 !important;
+ }
+
+ p {
+ color: white;
+ }
+
+ @include breakpoint(medium) {
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+ }
+
+ @include breakpoint(large) {
+ background: #2D3E50 image-url("ballot.gif") no-repeat;
+ background-position: 90% 0;
+ }
+}
+
+.featured-proposals-ballot-banner,
+.successfull .panel {
+
+ .icon-successfull {
+ border-right: 60px solid #FFD200;
+ border-top: 0;
+ border-bottom: 60px solid transparent;
+ height: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+ width: 0;
+
+ &:after {
+ color: #1B254C;
+ content: "\59";
+ font-family: "icons" !important;
+ left: 34px;
+ position: absolute;
+ top: 5px;
+ }
+ }
+}
+
+.proposals-ballot-list {
+
+ .proposal-sucessfull {
+ background: white;
+ border-top: 1px solid $border;
+ padding: $line-height 0;
+ position: relative;
+ }
+}
+
+.successfull {
+
+ .panel {
+ position: relative;
+ }
+
+ .truncate {
+ display: none;
+ }
+
+ .message {
+ @include supports;
+ background: none;
+ border-top: 0;
+
+ @include breakpoint(medium) {
+ border-left: 1px solid $border;
+ margin: $line-height rem-calc(-25) 0 rem-calc(12);
+ }
+ }
+}
diff --git a/app/controllers/concerns/commentable_actions.rb b/app/controllers/concerns/commentable_actions.rb
index 3a79238ea..3f4398797 100644
--- a/app/controllers/concerns/commentable_actions.rb
+++ b/app/controllers/concerns/commentable_actions.rb
@@ -12,7 +12,7 @@ module CommentableActions
@tag_cloud = tag_cloud
@banners = Banner.with_active
-
+
set_resource_votes(@resources)
set_resources_instance
end
diff --git a/app/controllers/debates_controller.rb b/app/controllers/debates_controller.rb
index e077e5e8a..7d2149cad 100644
--- a/app/controllers/debates_controller.rb
+++ b/app/controllers/debates_controller.rb
@@ -22,6 +22,7 @@ class DebatesController < ApplicationController
def index_customization
@featured_debates = @debates.featured
+ @proposal_successfull_exists = Proposal.successfull.exists?
end
def show
diff --git a/app/controllers/proposal_ballots_controller.rb b/app/controllers/proposal_ballots_controller.rb
new file mode 100644
index 000000000..4e3e99671
--- /dev/null
+++ b/app/controllers/proposal_ballots_controller.rb
@@ -0,0 +1,8 @@
+class ProposalBallotsController < ApplicationController
+ skip_authorization_check
+
+ def index
+ @proposal_ballots = Proposal.successfull.sort_by_confidence_score
+ end
+
+end
diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb
index 578395ab3..ede5b0f5d 100644
--- a/app/controllers/proposals_controller.rb
+++ b/app/controllers/proposals_controller.rb
@@ -12,7 +12,7 @@ class ProposalsController < ApplicationController
invisible_captcha only: [:create, :update], honeypot: :subtitle
- has_orders %w{hot_score confidence_score created_at relevance}, only: :index
+ has_orders %w{hot_score confidence_score created_at relevance archival_date}, only: :index
has_orders %w{most_voted newest oldest}, only: :show
load_and_authorize_resource
@@ -26,8 +26,10 @@ class ProposalsController < ApplicationController
end
def index_customization
+ discard_archived
load_retired
- load_featured
+ load_proposal_ballots
+ load_featured unless @proposal_successfull_exists
end
def vote
@@ -80,6 +82,10 @@ class ProposalsController < ApplicationController
@featured_proposals_votes = current_user ? current_user.proposal_votes(proposals) : {}
end
+ def discard_archived
+ @resources = @resources.not_archived unless @current_order == "archival_date"
+ end
+
def load_retired
if params[:retired].present?
@resources = @resources.retired
@@ -90,11 +96,15 @@ class ProposalsController < ApplicationController
end
def load_featured
- @featured_proposals = Proposal.all.sort_by_confidence_score.limit(3) if (!@advanced_search_terms && @search_terms.blank? && @tag_filter.blank? && params[:retired].blank?)
+ @featured_proposals = Proposal.not_archived.sort_by_confidence_score.limit(3) if (!@advanced_search_terms && @search_terms.blank? && @tag_filter.blank? && params[:retired].blank?)
if @featured_proposals.present?
set_featured_proposal_votes(@featured_proposals)
@resources = @resources.where('proposals.id NOT IN (?)', @featured_proposals.map(&:id))
end
end
+ def load_proposal_ballots
+ @proposal_successfull_exists = Proposal.successfull.exists?
+ end
+
end
diff --git a/app/models/proposal.rb b/app/models/proposal.rb
index 9a92a8d18..7bd4b7a92 100644
--- a/app/models/proposal.rb
+++ b/app/models/proposal.rb
@@ -44,9 +44,13 @@ class Proposal < ActiveRecord::Base
scope :sort_by_random, -> { reorder("RANDOM()") }
scope :sort_by_relevance, -> { all }
scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) }
+ scope :sort_by_archival_date, -> { archived.sort_by_confidence_score }
+ scope :archived, -> { where("proposals.created_at <= ?", Setting["months_to_archive_proposals"].to_i.months.ago)}
+ scope :not_archived, -> { where("proposals.created_at > ?", Setting["months_to_archive_proposals"].to_i.months.ago)}
scope :last_week, -> { where("proposals.created_at >= ?", 7.days.ago)}
scope :retired, -> { where.not(retired_at: nil) }
scope :not_retired, -> { where(retired_at: nil) }
+ scope :successfull, -> { where("cached_votes_up + physical_votes >= ?", Proposal.votes_needed_for_success)}
def to_param
"#{id}-#{title}".parameterize
@@ -119,7 +123,7 @@ class Proposal < ActiveRecord::Base
end
def register_vote(user, vote_value)
- if votable_by?(user)
+ if votable_by?(user) && !archived?
vote_by(voter: user, vote: vote_value)
end
end
@@ -155,6 +159,14 @@ class Proposal < ActiveRecord::Base
Setting['votes_for_proposal_success'].to_i
end
+ def successfull?
+ total_votes >= Proposal.votes_needed_for_success
+ end
+
+ def archived?
+ self.created_at <= Setting["months_to_archive_proposals"].to_i.months.ago
+ end
+
def notifications
proposal_notifications
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 60bc2364a..916c3d0b9 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -2,8 +2,8 @@ class User < ActiveRecord::Base
include Verification
- devise :database_authenticatable, :registerable, :confirmable,
- :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :async
+ devise :database_authenticatable, :registerable, :confirmable, :recoverable, :rememberable,
+ :trackable, :validatable, :omniauthable, :async, :password_expirable, :secure_validatable
acts_as_voter
acts_as_paranoid column: :hidden_at
diff --git a/app/views/debates/index.html.erb b/app/views/debates/index.html.erb
index 6809782b1..22d628c65 100644
--- a/app/views/debates/index.html.erb
+++ b/app/views/debates/index.html.erb
@@ -29,7 +29,11 @@
<%= render "shared/banner" %>
<% end %>
- <% unless @tag_filter || @search_terms || !has_featured? %>
+ <% if @proposal_successfull_exists %>
+ <%= render "proposals/proposal_ballots_banner" %>
+ <% end %>
+
+ <% unless @tag_filter || @search_terms || !has_featured? || @proposal_ballots.present? || @proposal_successfull_exists %>
<%= render "featured_debates" %>
<% end %>
diff --git a/app/views/devise/password_expired/show.html.erb b/app/views/devise/password_expired/show.html.erb
new file mode 100644
index 000000000..33dc82f71
--- /dev/null
+++ b/app/views/devise/password_expired/show.html.erb
@@ -0,0 +1,13 @@
+
<%= t("devise.password_expired.expire_password") %>
+
+<%= form_for(resource, :as => resource_name, :url => [resource_name, :password_expired], :html => { :method => :put }) do |f| %>
+
+ <%= f.password_field :current_password %>
+
+ <%= f.label t("devise.password_expired.new_password") %>
+ <%= f.password_field :password, label: false %>
+
+ <%= f.password_field :password_confirmation %>
+
+ <%= f.submit t("devise.password_expired.change_password") %>
+<% end %>
\ No newline at end of file
diff --git a/app/views/direct_messages/show.html.erb b/app/views/direct_messages/show.html.erb
index 30a771a39..8d0f62d84 100644
--- a/app/views/direct_messages/show.html.erb
+++ b/app/views/direct_messages/show.html.erb
@@ -13,6 +13,6 @@
<%= @direct_message.title %>
- <%= @direct_message.body %>
+ <%= simple_format text_with_links(@direct_message.body), {}, sanitize: false %>
diff --git a/app/views/layouts/_flash.html.erb b/app/views/layouts/_flash.html.erb
index d07259c5c..9f55b449a 100644
--- a/app/views/layouts/_flash.html.erb
+++ b/app/views/layouts/_flash.html.erb
@@ -1,10 +1,12 @@
<% flash.each do |flash_key, flash_message| %>
-
-
+
+
" type="button" data-close>
×
- <%= flash_message %>
+
+ <%= flash_message %>
+
<% end %>
diff --git a/app/views/mailer/direct_message_for_receiver.html.erb b/app/views/mailer/direct_message_for_receiver.html.erb
index 2523f3502..ac86b994a 100644
--- a/app/views/mailer/direct_message_for_receiver.html.erb
+++ b/app/views/mailer/direct_message_for_receiver.html.erb
@@ -3,9 +3,9 @@
<%= @direct_message.title %>
-
- <%= @direct_message.body %>
-
+
+ <%= simple_format text_with_links(@direct_message.body), {}, sanitize: false %>
+
diff --git a/app/views/mailer/direct_message_for_sender.html.erb b/app/views/mailer/direct_message_for_sender.html.erb
index 4cf832414..127526cf4 100644
--- a/app/views/mailer/direct_message_for_sender.html.erb
+++ b/app/views/mailer/direct_message_for_sender.html.erb
@@ -9,7 +9,7 @@
<%= @direct_message.title %>
-
- <%= @direct_message.body %>
-
+
+ <%= simple_format text_with_links(@direct_message.body), {}, sanitize: false %>
+
diff --git a/app/views/proposal_ballots/_successfull_proposal.html.erb b/app/views/proposal_ballots/_successfull_proposal.html.erb
new file mode 100644
index 000000000..870e61605
--- /dev/null
+++ b/app/views/proposal_ballots/_successfull_proposal.html.erb
@@ -0,0 +1,13 @@
+
+
<%= link_to proposal.title, proposal %>
+
+ <% if proposal.author.hidden? || proposal.author.erased? %>
+ <%= t("proposals.show.author_deleted") %>
+ <% else %>
+ <%= proposal.author.name %>
+ <% end %>
+
+ •
+ <%= l proposal.created_at.to_date %>
+
+
diff --git a/app/views/proposal_ballots/index.html.erb b/app/views/proposal_ballots/index.html.erb
new file mode 100644
index 000000000..00a07fb9e
--- /dev/null
+++ b/app/views/proposal_ballots/index.html.erb
@@ -0,0 +1,35 @@
+
+
+
+
+ <%= t("proposal_ballots.title") %>
+
+
+ <%= t("proposal_ballots.description_html").html_safe %>
+
+
+
+
+
<%= t("proposal_ballots.date_title") %>
+
<%= t("proposal_ballots.date") %>
+
+
+
+
+
+
+
+ <% if @proposal_ballots.present? %>
+
+ <% @proposal_ballots.each do |proposal_for_vote| %>
+ <%= render "successfull_proposal", proposal: proposal_for_vote %>
+ <% end %>
+
+ <% else %>
+
+ <%= t("proposal_ballots.nothing_to_vote") %>
+
+ <% end %>
+
+
+
diff --git a/app/views/proposals/_proposal.html.erb b/app/views/proposals/_proposal.html.erb
index 70c2b9c89..2c7bb272a 100644
--- a/app/views/proposals/_proposal.html.erb
+++ b/app/views/proposals/_proposal.html.erb
@@ -1,10 +1,12 @@
-
+
Proposal.votes_needed_for_success) %>"
+ data-type="proposal">
+
-
<% cache [locale_and_user_status(proposal), 'index', proposal, proposal.author] do %>
<%= t("proposals.proposal.proposal") %>
@@ -50,11 +52,26 @@
-
- <%= render 'votes',
- { proposal: proposal, vote_url: vote_proposal_path(proposal, value: 'yes') } %>
+
+ <% if proposal.successfull? %>
+
+
+ <%= t("proposal_ballots.successfull",
+ voting: link_to(t("proposal_ballots.voting"), proposal_ballots_path)).html_safe %>
+
+
+ <% elsif proposal.archived? %>
+
+
<%= t("proposals.proposal.supports", count: proposal.total_votes) %>
+
<%= t("proposals.proposal.archived") %>
+
+ <% else %>
+
+ <%= render 'votes',
+ { proposal: proposal, vote_url: vote_proposal_path(proposal, value: 'yes') } %>
+
+ <% end %>
-
diff --git a/app/views/proposals/_proposal_ballots_banner.html.erb b/app/views/proposals/_proposal_ballots_banner.html.erb
new file mode 100644
index 000000000..df210a535
--- /dev/null
+++ b/app/views/proposals/_proposal_ballots_banner.html.erb
@@ -0,0 +1,9 @@
+
+ <%= link_to proposal_ballots_path do %>
+
+
+
<%= t("proposal_ballots.featured_title") %>
+
<%= t("proposal_ballots.info") %>
+
+ <% end %>
+
diff --git a/app/views/proposals/index.html.erb b/app/views/proposals/index.html.erb
index 6f6516890..5efa3e24c 100644
--- a/app/views/proposals/index.html.erb
+++ b/app/views/proposals/index.html.erb
@@ -30,8 +30,10 @@
<% if has_banners %>
<%= render "shared/banner" %>
<% end %>
-
- <% if @featured_proposals.present? %>
+
+ <% if @proposal_successfull_exists %>
+ <%= render "proposal_ballots_banner" %>
+ <% elsif @featured_proposals.present? %>
@@ -52,8 +54,10 @@
<%= link_to t("proposals.index.start_proposal"), new_proposal_path, class: 'button expanded' %>
- <%= render partial: 'proposals/proposal', collection: @proposals %>
- <%= paginate @proposals %>
+
+ <%= render partial: 'proposals/proposal', collection: @proposals %>
+ <%= paginate @proposals %>
+
diff --git a/app/views/proposals/show.html.erb b/app/views/proposals/show.html.erb
index 3a9869d83..5cb7867af 100644
--- a/app/views/proposals/show.html.erb
+++ b/app/views/proposals/show.html.erb
@@ -102,11 +102,23 @@
<%= t("votes.supports") %>
-
-
- <%= render 'votes',
- { proposal: @proposal, vote_url: vote_proposal_path(@proposal, value: 'yes') } %>
-
+
+ <% if @proposal.successfull? %>
+
+ <%= t("proposal_ballots.successfull",
+ voting: link_to(t("proposal_ballots.voting"), proposal_ballots_path)).html_safe %>
+
+ <% elsif @proposal.archived? %>
+
+ <%= t("proposals.proposal.supports", count: @proposal.total_votes) %>
+
+
<%= t("proposals.proposal.archived") %>
+ <% else %>
+
+ <%= render 'votes',
+ { proposal: @proposal, vote_url: vote_proposal_path(@proposal, value: 'yes') } %>
+
+ <% end %>
<%= t("proposals.show.share") %>
diff --git a/app/views/shared/_subnavigation.html.erb b/app/views/shared/_subnavigation.html.erb
index cd025eaf2..9d87a0294 100644
--- a/app/views/shared/_subnavigation.html.erb
+++ b/app/views/shared/_subnavigation.html.erb
@@ -8,6 +8,9 @@
<%= link_to t("layouts.header.proposals"), proposals_path, class: ("active" if controller_name == "proposals"), accesskey: "p" %>
+
+ <%= link_to t("layouts.header.proposal_ballot"), proposal_ballots_path, class: ("active" if controller_name == "proposal_ballots"), accesskey: "v" %>
+
<% if feature?(:spending_proposals) %>
<%= link_to t("layouts.header.spending_proposals"), spending_proposals_path, class: ("active" if controller_name == "spending_proposals"), accesskey: "s" %>
diff --git a/bin/rails b/bin/rails
index 4d608edeb..5191e6927 100755
--- a/bin/rails
+++ b/bin/rails
@@ -1,8 +1,4 @@
#!/usr/bin/env ruby
-begin
- load File.expand_path("../spring", __FILE__)
-rescue LoadError
-end
APP_PATH = File.expand_path('../../config/application', __FILE__)
require_relative '../config/boot'
require 'rails/commands'
diff --git a/bin/rake b/bin/rake
index 8017a0271..17240489f 100755
--- a/bin/rake
+++ b/bin/rake
@@ -1,8 +1,4 @@
#!/usr/bin/env ruby
-begin
- load File.expand_path("../spring", __FILE__)
-rescue LoadError
-end
require_relative '../config/boot'
require 'rake'
Rake.application.run
diff --git a/bin/rspec b/bin/rspec
index 5a3c87c5f..d738b23c0 100755
--- a/bin/rspec
+++ b/bin/rspec
@@ -1,9 +1,5 @@
#!/usr/bin/env ruby
-begin
- load File.expand_path('../spring', __FILE__)
-rescue LoadError => e
- raise unless e.message.include?('spring')
-end
+# frozen_string_literal: true
#
# This file was generated by Bundler.
#
@@ -11,11 +7,11 @@ end
# this file is here to facilitate running it.
#
-require 'pathname'
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
+require "pathname"
+ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
-require 'rubygems'
-require 'bundler/setup'
+require "rubygems"
+require "bundler/setup"
-load Gem.bin_path('rspec-core', 'rspec')
+load Gem.bin_path("rspec-core", "rspec")
diff --git a/bin/spring b/bin/spring
index 62ec28f8c..43731fe82 100755
--- a/bin/spring
+++ b/bin/spring
@@ -1,15 +1,17 @@
#!/usr/bin/env ruby
+# frozen_string_literal: true
+#
+# This file was generated by Bundler.
+#
+# The application 'spring' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
-# This file loads spring without using Bundler, in order to be fast.
-# It gets overwritten when you run the `spring binstub` command.
+require "pathname"
+ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
+ Pathname.new(__FILE__).realpath)
-unless defined?(Spring)
- require 'rubygems'
- require 'bundler'
+require "rubygems"
+require "bundler/setup"
- if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m))
- Gem.paths = { 'GEM_PATH' => [Bundler.bundle_path.to_s, *Gem.path].uniq }
- gem 'spring', match[1]
- require 'spring/binstub'
- end
-end
+load Gem.bin_path("spring", "spring")
diff --git a/config/application.rb b/config/application.rb
index 3f2a0861a..2b842992b 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -24,7 +24,6 @@ module Consul
config.i18n.available_locales = [:en, :es]
# Add the new directories to the locales load path
- config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
config.assets.paths << Rails.root.join("app", "assets", "fonts")
@@ -44,6 +43,7 @@ module Consul
config.autoload_paths << "#{Rails.root}/app/controllers/custom"
config.autoload_paths << "#{Rails.root}/app/models/custom"
config.paths['app/views'].unshift(Rails.root.join('app', 'views', 'custom'))
+ config.i18n.load_path += Dir[Rails.root.join('config', 'locales', 'custom', '*.{rb,yml}')]
end
end
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml
index 245eebbcf..5c5898783 100644
--- a/config/i18n-tasks.yml
+++ b/config/i18n-tasks.yml
@@ -102,6 +102,7 @@ ignore_missing:
- 'errors.messages.taken'
- 'devise.failure.invalid'
- 'devise.registrations.destroyed'
+ - 'devise.password_expired.*'
## Consider these keys used:
ignore_unused:
diff --git a/config/initializers/devise_security_extension.rb b/config/initializers/devise_security_extension.rb
new file mode 100644
index 000000000..6e691acbe
--- /dev/null
+++ b/config/initializers/devise_security_extension.rb
@@ -0,0 +1,71 @@
+Devise.setup do |config|
+ # ==> Security Extension
+ # Configure security extension for devise
+
+ # Should the password expire (e.g 3.months)
+ # config.expire_password_after = false
+ config.expire_password_after = 1.year
+
+ # Need 1 char of A-Z, a-z and 0-9
+ # config.password_regex = /(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])/
+
+ # How many passwords to keep in archive
+ #config.password_archiving_count = 5
+
+ # Deny old password (true, false, count)
+ # config.deny_old_passwords = true
+
+ # enable email validation for :secure_validatable. (true, false, validation_options)
+ # dependency: need an email validator like rails_email_validator
+ # config.email_validation = true
+ # captcha integration for recover form
+ # config.captcha_for_recover = true
+
+ # captcha integration for sign up form
+ # config.captcha_for_sign_up = true
+
+ # captcha integration for sign in form
+ # config.captcha_for_sign_in = true
+
+ # captcha integration for unlock form
+ # config.captcha_for_unlock = true
+
+ # captcha integration for confirmation form
+ # config.captcha_for_confirmation = true
+
+ # Time period for account expiry from last_activity_at
+ # config.expire_after = 90.days
+end
+
+module Devise
+ module Models
+ module PasswordExpirable
+ def need_change_password?
+ self.administrator? && password_expired?
+ end
+
+ def password_expired?
+ self.password_changed_at < self.expire_password_after.ago
+ end
+ end
+
+ module SecureValidatable
+ def self.included(base)
+ base.extend ClassMethods
+ assert_secure_validations_api!(base)
+ base.class_eval do
+ validate :current_equal_password_validation
+ end
+ end
+
+ def current_equal_password_validation
+ if !self.new_record? && !self.encrypted_password_change.nil? && !self.erased?
+ dummy = self.class.new
+ dummy.encrypted_password = self.encrypted_password_change.first
+ dummy.password_salt = self.password_salt_change.first if self.respond_to? :password_salt_change and not self.password_salt_change.nil?
+ self.errors.add(:password, :equal_to_current_password) if dummy.valid_password?(self.password)
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml
index 32f4ef78f..f34757195 100644
--- a/config/locales/activerecord.en.yml
+++ b/config/locales/activerecord.en.yml
@@ -54,6 +54,7 @@ en:
username: "Username"
password_confirmation: "Password confirmation"
password: "Password"
+ current_password: "Current password"
phone_number: "Phone number"
official_position: "Official position"
official_level: "Official level"
diff --git a/config/locales/activerecord.es.yml b/config/locales/activerecord.es.yml
index a9047df7b..a439a6c5c 100644
--- a/config/locales/activerecord.es.yml
+++ b/config/locales/activerecord.es.yml
@@ -54,6 +54,7 @@ es:
username: "Nombre de usuario"
password_confirmation: "Confirmación de contraseña"
password: "Contraseña"
+ current_password: "Contraseña actual"
phone_number: "Teléfono"
official_position: "Cargo público"
official_level: "Nivel del cargo"
diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml
index 30fb488ce..29bc0a620 100755
--- a/config/locales/devise.en.yml
+++ b/config/locales/devise.en.yml
@@ -2,6 +2,12 @@
en:
devise:
+ password_expired:
+ expire_password: "Password expired"
+ change_required: "Your password is expired"
+ change_password: "Change your password"
+ new_password: "New password"
+ updated: "Password successfully updated"
confirmations:
confirmed: "Your account has been confirmed."
send_instructions: "In a few minutes you will receive an email containing instructions on how to reset your password."
@@ -62,3 +68,4 @@ en:
not_saved:
one: "1 error prevented this %{resource} from being saved:"
other: "%{count} errors prevented this %{resource} from being saved:"
+ equal_to_current_password: "must be different than the current password."
diff --git a/config/locales/devise.es.yml b/config/locales/devise.es.yml
index 1a9c3a3b8..6ede31f7d 100644
--- a/config/locales/devise.es.yml
+++ b/config/locales/devise.es.yml
@@ -1,5 +1,11 @@
es:
devise:
+ password_expired:
+ expire_password: "Contraseña caducada"
+ change_required: "Tu contraseña ha caducado"
+ change_password: "Cambia tu contraseña"
+ new_password: "Nueva contraseña"
+ updated: "Contraseña actualizada con éxito"
confirmations:
confirmed: "Tu cuenta ha sido confirmada. Por favor autentifícate con tu red social o tu usuario y contraseña"
send_instructions: "Recibirás un correo electrónico en unos minutos con instrucciones sobre cómo restablecer tu contraseña."
@@ -60,3 +66,4 @@ es:
not_saved:
one: "1 error impidió que este %{resource} fuera guardado:"
other: "%{count} errores impidieron que este %{resource} fuera guardado:"
+ equal_to_current_password: "debe ser diferente a la contraseña actual"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 45ec5fac0..9230ecbf8 100755
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -210,6 +210,7 @@ en:
open_data: Open data
open_gov: Open government
proposals: Proposals
+ proposal_ballot: Voting
see_all: See proposals
spending_proposals: Spending proposals
legislation:
@@ -302,6 +303,7 @@ en:
hot_score: most active
most_commented: most commented
relevance: relevance
+ archival_date: Archived
retired_proposals: Retired proposals
retired_proposals_link: "Proposals retired by the author"
retired_links:
@@ -351,6 +353,7 @@ en:
zero: No supports
supports_necessary: "%{number} supports needed"
total_percent: 100%
+ archived: "This proposal has been archived and can't collect supports."
show:
author_deleted: User deleted
code: 'Proposal code:'
@@ -372,6 +375,17 @@ en:
update:
form:
submit_button: Save changes
+ proposal_ballots:
+ title: "Votings"
+ description_html: "The following citizen proposals that have reached the required supports and will be voted."
+ date_title: "Dates of participation"
+ date: "Soon we'll announce the date of vote these proposals."
+ successfull: "This proposal has reached the required supports and will be voted in the %{voting}."
+ voting: "next voting"
+ featured_title: "#NextVoting"
+ nothing_to_vote: "There is nothing to vote at the moment."
+ info: "New proposals that have reached the voting phase."
+ button: "I want to decide"
proposal_notifications:
new:
title: "Send message"
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 7cb02f035..80541466b 100755
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -210,6 +210,7 @@ es:
open_data: Datos abiertos
open_gov: Gobierno %{open}
proposals: Propuestas
+ proposal_ballot: Votaciones
see_all: Ver propuestas
spending_proposals: Presupuestos ciudadanos
legislation:
@@ -302,6 +303,7 @@ es:
hot_score: Más activas hoy
most_commented: Más comentadas
relevance: Más relevantes
+ archival_date: Archivadas
retired_proposals: Propuestas retiradas
retired_proposals_link: "Propuestas retiradas por sus autores"
retired_links:
@@ -351,6 +353,7 @@ es:
zero: Sin apoyos
supports_necessary: "%{number} apoyos necesarios"
total_percent: 100%
+ archived: "Esta propuesta ha sido archivada y ya no puede recoger apoyos."
show:
author_deleted: Usuario eliminado
code: 'Código de la propuesta:'
@@ -372,6 +375,17 @@ es:
update:
form:
submit_button: Guardar cambios
+ proposal_ballots:
+ title: "Votaciones"
+ description_html: "Las siguientes propuestas ciudadanas han alcanzado el número de apoyos necesarios y pasarán a votación."
+ date_title: "Fechas de participación"
+ date: "En breve anunciaremos la fecha de votación de estas propuestas."
+ successfull: "Esta propuesta ha alcanzado los apoyos necesarios y pasará a la %{voting}."
+ voting: "próxima votación"
+ featured_title: "#PróximaVotación"
+ nothing_to_vote: "No hay nada que votar en este momento."
+ info: "Nuevas propuestas ciudadanas han llegado a la fase de votación."
+ button: "Quiero decidir"
proposal_notifications:
new:
title: "Enviar mensaje"
diff --git a/config/locales/settings.en.yml b/config/locales/settings.en.yml
index 32874e32b..0de4e5feb 100755
--- a/config/locales/settings.en.yml
+++ b/config/locales/settings.en.yml
@@ -11,9 +11,21 @@ en:
max_votes_for_debate_edit: "Number of votes from which a Debate can no longer be edited"
proposal_code_prefix: "Prefix for Proposal codes"
votes_for_proposal_success: "Number of votes necessary for approval of a Proposal"
+ months_to_archive_proposals: "Months to archive Proposals"
email_domain_for_officials: "Email domain for public officials"
per_page_code: "Code to be included on every page"
+ twitter_handle: "Twitter handle"
+ twitter_hashtag: "Twitter hashtag"
+ facebook_handle: "Facebook handle"
+ youtube_handle: "Youtube handle"
+ blog_url: "Blog URL"
+ url: "Main URL"
+ org_name: "Organization"
+ place_name: "Place"
feature:
+ twitter_login: Twitter login
+ facebook_login: Facebook login
+ google_login: Google login
debates: Debates
spending_proposals: Investment projects
spending_proposal_features:
diff --git a/config/locales/settings.es.yml b/config/locales/settings.es.yml
index aa5dea14e..05ba6c076 100644
--- a/config/locales/settings.es.yml
+++ b/config/locales/settings.es.yml
@@ -11,9 +11,21 @@ es:
max_votes_for_debate_edit: "Número de votos en que un Debate deja de poderse editar"
proposal_code_prefix: "Prefijo para los códigos de Propuestas"
votes_for_proposal_success: "Número de votos necesarios para aprobar una Propuesta"
+ months_to_archive_proposals: "Meses para archivar las Propuestas"
email_domain_for_officials: "Dominio de email para cargos públicos"
per_page_code: "Código a incluir en cada página"
+ twitter_handle: "Usuario de Twitter"
+ twitter_hashtag: "Hashtag para Twitter"
+ facebook_handle: "Identificador de Facebook"
+ youtube_handle: "Usuario de Youtube"
+ blog_url: "URL del blog"
+ url: "URL general de la web"
+ org_name: "Nombre de la organización"
+ place_name: "Nombre del lugar"
feature:
+ twitter_login: Registro con Twitter
+ facebook_login: Registro con Facebook
+ google_login: Registro con Google
debates: Debates
spending_proposals: Propuestas de inversión
spending_proposal_features:
diff --git a/config/routes.rb b/config/routes.rb
index f682ab529..16625aa7c 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -61,6 +61,8 @@ Rails.application.routes.draw do
end
end
+ resources :proposal_ballots, only: [:index]
+
resources :comments, only: [:create, :show], shallow: true do
member do
post :vote
diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb
index 2fe812ea5..51f46b3c6 100644
--- a/db/dev_seeds.rb
+++ b/db/dev_seeds.rb
@@ -13,6 +13,7 @@ Setting.create(key: 'max_votes_for_debate_edit', value: '1000')
Setting.create(key: 'max_votes_for_proposal_edit', value: '1000')
Setting.create(key: 'proposal_code_prefix', value: 'MAD')
Setting.create(key: 'votes_for_proposal_success', value: '100')
+Setting.create(key: 'months_to_archive_proposals', value: '12')
Setting.create(key: 'comments_body_max_length', value: '1000')
Setting.create(key: 'twitter_handle', value: '@consul_dev')
@@ -149,7 +150,7 @@ tags = Faker::Lorem.words(25)
description = "#{Faker::Lorem.paragraphs.join('
')}
"
proposal = Proposal.create!(author: author,
title: Faker::Lorem.sentence(3).truncate(60),
- question: Faker::Lorem.sentence(3),
+ question: Faker::Lorem.sentence(3) + "?",
summary: Faker::Lorem.sentence(3),
responsible_name: Faker::Name.name,
external_url: Faker::Internet.url,
@@ -161,6 +162,27 @@ tags = Faker::Lorem.words(25)
puts " #{proposal.title}"
end
+puts "Creating Archived Proposals"
+
+tags = Faker::Lorem.words(25)
+(1..5).each do |i|
+ author = User.reorder("RANDOM()").first
+ description = "#{Faker::Lorem.paragraphs.join('
')}
"
+ proposal = Proposal.create!(author: author,
+ title: Faker::Lorem.sentence(3).truncate(60),
+ question: Faker::Lorem.sentence(3) + "?",
+ summary: Faker::Lorem.sentence(3),
+ responsible_name: Faker::Name.name,
+ external_url: Faker::Internet.url,
+ description: description,
+ created_at: rand((Time.now - 1.week) .. Time.now),
+ tag_list: tags.sample(3).join(','),
+ geozone: Geozone.reorder("RANDOM()").first,
+ terms_of_service: "1",
+ created_at: Setting["months_to_archive_proposals"].to_i.months.ago)
+ puts " #{proposal.title}"
+end
+
tags = ActsAsTaggableOn::Tag.where(kind: 'category')
(1..30).each do |i|
@@ -168,7 +190,7 @@ tags = ActsAsTaggableOn::Tag.where(kind: 'category')
description = "#{Faker::Lorem.paragraphs.join('
')}
"
proposal = Proposal.create!(author: author,
title: Faker::Lorem.sentence(3).truncate(60),
- question: Faker::Lorem.sentence(3),
+ question: Faker::Lorem.sentence(3) + "?",
summary: Faker::Lorem.sentence(3),
responsible_name: Faker::Name.name,
external_url: Faker::Internet.url,
diff --git a/db/migrate/20160901104320_add_password_expired.rb b/db/migrate/20160901104320_add_password_expired.rb
new file mode 100644
index 000000000..aa759dd61
--- /dev/null
+++ b/db/migrate/20160901104320_add_password_expired.rb
@@ -0,0 +1,6 @@
+class AddPasswordExpired < ActiveRecord::Migration
+ def change
+ add_column :users, :password_changed_at, :datetime
+ add_index :users, :password_changed_at
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 98be0a56f..5d81903a1 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -491,12 +491,14 @@ ActiveRecord::Schema.define(version: 20160928113143) do
t.boolean "email_digest", default: true
t.boolean "email_on_direct_message", default: true
t.boolean "official_position_badge", default: false
+ t.datetime "password_changed_at"
end
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["geozone_id"], name: "index_users_on_geozone_id", using: :btree
add_index "users", ["hidden_at"], name: "index_users_on_hidden_at", using: :btree
+ add_index "users", ["password_changed_at"], name: "index_users_on_password_changed_at", using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
add_index "users", ["username"], name: "index_users_on_username", using: :btree
diff --git a/db/seeds.rb b/db/seeds.rb
index 939a5f045..a5d8537db 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -31,6 +31,9 @@ Setting["proposal_code_prefix"] = 'MAD'
# Number of votes needed for proposal success
Setting["votes_for_proposal_success"] = 53726
+# Months to archive proposals
+Setting["months_to_archive_proposals"] = 12
+
# Users with this email domain will automatically be marked as level 1 officials
# Emails under the domain's subdomains will also be included
Setting["email_domain_for_officials"] = ''
diff --git a/doc/en/dev_test_setup.md b/doc/en/dev_test_setup.md
index 7fc7c967e..d5fa28fe0 100644
--- a/doc/en/dev_test_setup.md
+++ b/doc/en/dev_test_setup.md
@@ -2,6 +2,8 @@
## Linux
+See [here](dev_test_setup_linux.md)
+
## Mac OS X
See [here](dev_test_setup_osx.md)
diff --git a/doc/en/dev_test_setup_linux.md b/doc/en/dev_test_setup_linux.md
new file mode 100644
index 000000000..df0dbb26a
--- /dev/null
+++ b/doc/en/dev_test_setup_linux.md
@@ -0,0 +1,126 @@
+# Configuration for development and test environments (GNU/Linux)
+
+## Git
+
+Git is officially maintained in Debian/Ubuntu:
+
+```
+sudo apt-get install git
+```
+
+## Ruby
+
+Ruby versions packaged in official repositories are not suitable to work with consul (at least Debian 7 and 8), so we'll have to install it manually.
+
+The preferred method is via rvm:
+
+(only the multi user option installs all dependencies automatically, as we use 'sudo'.)
+
+###as local user:
+
+```
+curl -L https://get.rvm.io | bash -s stable
+```
+###for all system users
+
+```
+curl -L https://get.rvm.io | sudo bash -s stable
+```
+
+and then add your user to rvm group
+
+```
+sudo usermod -a -G rvm
+```
+
+and finally, add rvm script source to user's bash (~/.bashrc) (this step it's only necessary if you still can't execute rvm command)
+
+```
+[[ -s /usr/local/rvm/scripts/rvm ]] && source /usr/local/rvm/scripts/rvm
+```
+
+with all this, you are suppose to be able to install a ruby version from rvm, as for example version 2.3.0:
+
+```
+sudo rvm install 2.3.0
+```
+
+## Bundler
+
+with
+
+```
+gem install bundler
+```
+
+or there is more methods [here](https://rvm.io/integration/bundler) that should be better as:
+
+```
+gem install rubygems-bundler
+```
+
+## PostgreSQL (>=9.4)
+
+PostgreSQL version 9.4 is not official in debian 7 (wheezy), in 8 it seems to be officially maintained.
+
+So you have to add a repository, the official postgresql works fine.
+
+Add the repository to apt, for example creating file */etc/apt/sources.list.d/pgdg.list* with:
+
+```
+deb http://apt.postgresql.org/pub/repos/apt/ wheezy-pgdg main
+```
+
+afterwards you'll have to download the key, and install it, by:
+
+```
+wget https://www.postgresql.org/media/keys/ACCC4CF8.asc
+apt-key add ACCC4CF8.asc
+```
+
+and install postgresql
+
+```
+apt-get update
+apt-get install postgresql-9.4
+```
+
+## Ghostscript
+
+```
+apt-get install ghostscript
+```
+
+## Cloning the repository
+
+Now, with all the dependencies installed, clone the Consul repository:
+
+```
+git clone https://github.com/consul/consul.git
+cd consul
+bundle install
+cp config/database.yml.example config/database.yml
+cp config/secrets.yml.example config/secrets.yml
+```
+Perhaps it's needed to create a superuser rol with password in postgresql, and write it in */config/database.yml* 'user:' and 'password:' fields.
+
+Also, it seems that postgresql use as default an unix socket for localhost communications. If we encounter problems creating database (connection problems) we can change in */config/database.yml* the line:
+
+```
+host: localhost
+```
+
+for:
+
+```
+host: /var/run/postgresql
+```
+
+After this:
+
+```
+rake db:create
+rake db:setup
+rake db:dev_seed
+RAILS_ENV=test bin/rake db:setup
+```
diff --git a/lib/tasks/users.rake b/lib/tasks/users.rake
index efbeeaa94..ccb749f9a 100644
--- a/lib/tasks/users.rake
+++ b/lib/tasks/users.rake
@@ -76,4 +76,12 @@ namespace :users do
task remove_erased_identities: :environment do
Identity.joins(:user).where('users.erased_at IS NOT NULL').destroy_all
end
+
+ desc "Update password changed at for existing users"
+ task update_password_changed_at: :environment do
+ User.all.each do |user|
+ user.update(password_changed_at: user.created_at)
+ end
+ end
+
end
diff --git a/public/404.html b/public/404.html
index 5d519fb70..cc6820bba 100644
--- a/public/404.html
+++ b/public/404.html
@@ -5,7 +5,7 @@