- <%= image_tag("icon_home_debate.png", size: "168x168", alt: t("welcome.debates.alt"), title: t("welcome.debates.title")) %>
+ <%= image_tag("icon_home_debate.png", size: "168x168", alt: "", title: t("welcome.debates.title")) %>
<%= t("welcome.debates.title") %>
<%= t("welcome.debates.description") %>
- <%= image_tag("icon_home_proposal.png", size: "168x168", alt: t("welcome.proposal.alt"), title: t("welcome.proposal.title")) %>
+ <%= image_tag("icon_home_proposal.png", size: "168x168", alt: "", title: t("welcome.proposal.title")) %>
<%= t("welcome.proposal.title") %>
<%= t("welcome.proposal.description") %>
- <%= image_tag("icon_home_decide.png", size: "168x168", alt: t("welcome.decide.alt"), title: t("welcome.decide.title")) %>
+ <%= image_tag("icon_home_decide.png", size: "168x168", alt: "", title: t("welcome.decide.title")) %>
<%= t("welcome.decide.title") %>
<%= t("welcome.decide.description") %>
- <%= image_tag("icon_home_do.png", size: "168x168", alt: t("welcome.do.alt"), title: t("welcome.do.title")) %>
+ <%= image_tag("icon_home_do.png", size: "168x168", alt: "", title: t("welcome.do.title")) %>
<%= t("welcome.do.title") %>
<%= t("welcome.do.description") %>
diff --git a/config/application.rb b/config/application.rb
index 395251a52..3f2a0861a 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -1,3 +1,4 @@
+
require File.expand_path('../boot', __FILE__)
require 'rails/all'
@@ -34,5 +35,17 @@ module Consul
config.autoload_paths << Rails.root.join('lib')
config.time_zone = 'Madrid'
config.active_job.queue_adapter = :delayed_job
+
+ # Consul specific custom overrides
+ # Read more on documentation:
+ # * English: https://github.com/consul/consul/blob/master/CUSTOMIZE_EN.md
+ # * Spanish: https://github.com/consul/consul/blob/master/CUSTOMIZE_ES.md
+ #
+ 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'))
+
end
end
+
+require "./config/application_custom.rb"
diff --git a/config/application_custom.rb b/config/application_custom.rb
new file mode 100644
index 000000000..b99944bc7
--- /dev/null
+++ b/config/application_custom.rb
@@ -0,0 +1,4 @@
+module Consul
+ class Application < Rails::Application
+ end
+end
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb
index 7764ae0ec..e5fc916f0 100644
--- a/config/initializers/assets.rb
+++ b/config/initializers/assets.rb
@@ -9,8 +9,12 @@ Rails.application.config.assets.version = '1.0'
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
# Rails.application.config.assets.precompile += %w( search.js )
-Rails.application.config.assets.precompile += %w( ckeditor/* )
+Rails.application.config.assets.precompile += %w( ckeditor/config.js )
Rails.application.config.assets.precompile += %w( ie_lt9.js )
Rails.application.config.assets.precompile += %w( stat_graphs.js )
Rails.application.config.assets.precompile += %w( print.css )
Rails.application.config.assets.precompile += %w( ie.css )
+
+# Loads app/assets/images/custom before app/assets/images
+images_path = Rails.application.config.assets.paths
+images_path = images_path.insert(0, Rails.root.join("app", "assets", "images", "custom").to_s)
diff --git a/config/initializers/ckeditor.rb b/config/initializers/ckeditor.rb
new file mode 100644
index 000000000..58e119048
--- /dev/null
+++ b/config/initializers/ckeditor.rb
@@ -0,0 +1,4 @@
+Ckeditor.setup do |config|
+ config.assets_languages = I18n.available_locales.map(&:to_s)
+ config.assets_plugins = []
+end
diff --git a/config/locales/custom/.keep b/config/locales/custom/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/config/locales/en.yml b/config/locales/en.yml
index fae75307f..14606346e 100755
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -205,6 +205,7 @@ en:
more_information: More information
my_account_link: My account
my_activity_link: My activity
+ notifications: Notifications
new_notifications:
one: You have a new notification
other: You have %{count} new notifications
@@ -420,6 +421,7 @@ en:
flag: Flag as inappropriate
print:
print_button: Print this info
+ show: Show
suggest:
debate:
found:
@@ -571,19 +573,15 @@ en:
not_voting_allowed: Voting phase is closed
welcome:
debates:
- alt: Icon debates
description: For meeting, discussing and sharing the things that matter to us in our city.
title: Debates
decide:
- alt: Icon decide
description: The public decides if it accepts or rejects the most supported proposals.
title: You decide
do:
- alt: Icon it gets done
description: If the proposal is accepted by the majority, the City Council accepts it as its own and it gets done.
title: It gets done
proposal:
- alt: Icon propose
description: Open space for citizen proposals about the kind of city we want to live in.
title: You propose
verification:
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 195a80766..cfcff53be 100755
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -205,6 +205,7 @@ es:
more_information: Más información
my_account_link: Mi cuenta
my_activity_link: Mi actividad
+ notifications: Notificaciones
new_notifications:
one: Tienes una nueva notificación
other: Tienes %{count} notificaciones nuevas
@@ -420,6 +421,7 @@ es:
flag: Denunciar como inapropiado
print:
print_button: Imprimir esta información
+ show: Mostrar
suggest:
debate:
found:
@@ -571,19 +573,15 @@ es:
not_voting_allowed: El periodo de votación está cerrado.
welcome:
debates:
- alt: Icono debates
description: Encontrarnos, debatir y compartir lo que nos parece importante en nuestra ciudad.
title: Debates
decide:
- alt: Icono decides
description: La ciudadanía decide si acepta o rechaza las propuestas más apoyadas.
title: Decides
do:
- alt: Icono se hace
description: Si la propuesta es aceptada mayoritariamente, el Ayuntamiento la asume como propia y se hace.
title: Se hace
proposal:
- alt: Icono propones
description: Espacio abierto para propuestas ciudadanas sobre el tipo de ciudad en el que queremos vivir.
title: Propones
verification:
diff --git a/db/migrate/20160803154011_add_emailed_at_to_notifications.rb b/db/migrate/20160803154011_add_emailed_at_to_notifications.rb
new file mode 100644
index 000000000..83e38b00f
--- /dev/null
+++ b/db/migrate/20160803154011_add_emailed_at_to_notifications.rb
@@ -0,0 +1,5 @@
+class AddEmailedAtToNotifications < ActiveRecord::Migration
+ def change
+ add_column :notifications, :emailed_at, :datetime
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index df58b15cb..78483e876 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20160617172616) do
+ActiveRecord::Schema.define(version: 20160803154011) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -338,10 +338,11 @@ ActiveRecord::Schema.define(version: 20160617172616) do
add_index "moderators", ["user_id"], name: "index_moderators_on_user_id", using: :btree
create_table "notifications", force: :cascade do |t|
- t.integer "user_id"
- t.integer "notifiable_id"
- t.string "notifiable_type"
- t.integer "counter", default: 1
+ t.integer "user_id"
+ t.integer "notifiable_id"
+ t.string "notifiable_type"
+ t.integer "counter", default: 1
+ t.datetime "emailed_at"
end
add_index "notifications", ["user_id"], name: "index_notifications_on_user_id", using: :btree
diff --git a/lib/email_digest.rb b/lib/email_digest.rb
index 90838f78f..209014dcc 100644
--- a/lib/email_digest.rb
+++ b/lib/email_digest.rb
@@ -1,14 +1,27 @@
class EmailDigest
- def initialize
+ attr_accessor :user, :notifications
+
+ def initialize(user)
+ @user = user
end
- def create
- User.email_digest.each do |user|
- if user.notifications.where(notifiable_type: "ProposalNotification").any?
- Mailer.proposal_notification_digest(user).deliver_later
- end
+ def notifications
+ user.notifications.not_emailed.where(notifiable_type: "ProposalNotification")
+ end
+
+ def pending_notifications?
+ notifications.any?
+ end
+
+ def deliver
+ if pending_notifications?
+ Mailer.proposal_notification_digest(user, notifications.to_a).deliver_later
end
end
+ def mark_as_emailed
+ notifications.update_all(emailed_at: Time.now)
+ end
+
end
\ No newline at end of file
diff --git a/lib/tasks/emails.rake b/lib/tasks/emails.rake
index 6670264a5..ffadebf05 100644
--- a/lib/tasks/emails.rake
+++ b/lib/tasks/emails.rake
@@ -2,8 +2,11 @@ namespace :emails do
desc "Sends email digest of proposal notifications to each user"
task digest: :environment do
- email_digest = EmailDigest.new
- email_digest.create
+ User.email_digest.find_each do |user|
+ email_digest = EmailDigest.new(user)
+ email_digest.deliver
+ email_digest.mark_as_emailed
+ end
end
end
diff --git a/spec/features/emails_spec.rb b/spec/features/emails_spec.rb
index 429d74738..a9569e9b2 100644
--- a/spec/features/emails_spec.rb
+++ b/spec/features/emails_spec.rb
@@ -201,8 +201,9 @@ feature 'Emails' do
notification2 = create_proposal_notification(proposal2)
notification3 = create_proposal_notification(proposal3)
- email_digest = EmailDigest.new
- email_digest.create
+ email_digest = EmailDigest.new(user)
+ email_digest.deliver
+ email_digest.mark_as_emailed
email = open_last_email
expect(email).to have_subject("Proposal notifications in Consul")
@@ -227,6 +228,11 @@ feature 'Emails' do
expect(email).to_not have_body_text(proposal3.title)
expect(email).to have_body_text(/#{account_path}/)
+
+ notification1.reload
+ notification2.reload
+ expect(notification1.emailed_at).to be
+ expect(notification2.emailed_at).to be
end
end
diff --git a/spec/features/notifications_spec.rb b/spec/features/notifications_spec.rb
index e16dedf37..5fbc51736 100644
--- a/spec/features/notifications_spec.rb
+++ b/spec/features/notifications_spec.rb
@@ -180,9 +180,10 @@ feature "Notifications" do
find(".icon-notification").click
+ notification_for_user1 = Notification.where(user: user1).first
expect(page).to have_css ".notification", count: 1
expect(page).to have_content "There is one new notification on #{proposal.title}"
- expect(page).to have_xpath "//a[@href='#{notification_path(Notification.last)}']"
+ expect(page).to have_xpath "//a[@href='#{notification_path(notification_for_user1)}']"
logout
login_as user2
@@ -190,9 +191,10 @@ feature "Notifications" do
find(".icon-notification").click
+ notification_for_user2 = Notification.where(user: user2).first
expect(page).to have_css ".notification", count: 1
expect(page).to have_content "There is one new notification on #{proposal.title}"
- expect(page).to have_xpath "//a[@href='#{notification_path(Notification.first)}']"
+ expect(page).to have_xpath "//a[@href='#{notification_path(notification_for_user2)}']"
logout
login_as user3
diff --git a/spec/features/proposal_notifications_spec.rb b/spec/features/proposal_notifications_spec.rb
index 6092289f0..a10e41a7d 100644
--- a/spec/features/proposal_notifications_spec.rb
+++ b/spec/features/proposal_notifications_spec.rb
@@ -24,6 +24,44 @@ feature 'Proposal Notifications' do
expect(page).to have_content "Please share it with others so we can make it happen!"
end
+ scenario "Send a notification (Active voter)" do
+ author = create(:user)
+ proposal = create(:proposal, author: author)
+
+ voter = create(:user, :level_two)
+ create(:vote, voter: voter, votable: proposal)
+
+ create_proposal_notification(proposal)
+
+ expect(Notification.count).to eq(1)
+ end
+
+ scenario "Send a notification (Blocked voter)" do
+ author = create(:user)
+ proposal = create(:proposal, author: author)
+
+ voter = create(:user, :level_two)
+ create(:vote, voter: voter, votable: proposal)
+ voter.block
+
+ create_proposal_notification(proposal)
+
+ expect(Notification.count).to eq(0)
+ end
+
+ scenario "Send a notification (Erased voter)" do
+ author = create(:user)
+ proposal = create(:proposal, author: author)
+
+ voter = create(:user, :level_two)
+ create(:vote, voter: voter, votable: proposal)
+ voter.erase
+
+ create_proposal_notification(proposal)
+
+ expect(Notification.count).to eq(0)
+ end
+
scenario "Show notifications" do
proposal = create(:proposal)
notification1 = create(:proposal_notification, proposal: proposal, title: "Hey guys", body: "Just wanted to let you know that...")
diff --git a/spec/lib/email_digests_spec.rb b/spec/lib/email_digests_spec.rb
index 6fc6eef53..ae2122793 100644
--- a/spec/lib/email_digests_spec.rb
+++ b/spec/lib/email_digests_spec.rb
@@ -2,8 +2,126 @@ require 'rails_helper'
describe EmailDigest do
- describe "create" do
- pending "only send unread notifications"
+ describe "notifications" do
+
+ it "returns notifications for a user" do
+ user1 = create(:user)
+ user2 = create(:user)
+
+ proposal_notification = create(:proposal_notification)
+ notification1 = create(:notification, notifiable: proposal_notification, user: user1)
+ notification2 = create(:notification, notifiable: proposal_notification, user: user2)
+
+ email_digest = EmailDigest.new(user1)
+
+ expect(email_digest.notifications).to include(notification1)
+ expect(email_digest.notifications).to_not include(notification2)
+ end
+
+ it "returns only proposal notifications" do
+ user = create(:user)
+
+ proposal_notification = create(:proposal_notification)
+ comment = create(:comment)
+
+ notification1 = create(:notification, notifiable: proposal_notification, user: user)
+ notification2 = create(:notification, notifiable: comment, user: user)
+
+ email_digest = EmailDigest.new(user)
+
+ expect(email_digest.notifications).to include(notification1)
+ expect(email_digest.notifications).to_not include(notification2)
+ end
+
+ end
+
+ describe "pending_notifications?" do
+
+ it "returns true when notifications have not been emailed" do
+ user = create(:user)
+
+ proposal_notification = create(:proposal_notification)
+ notification = create(:notification, notifiable: proposal_notification, user: user)
+
+ email_digest = EmailDigest.new(user)
+ expect(email_digest.pending_notifications?).to be
+ end
+
+ it "returns false when notifications have been emailed" do
+ user = create(:user)
+
+ proposal_notification = create(:proposal_notification)
+ notification = create(:notification, notifiable: proposal_notification, user: user, emailed_at: Time.now)
+
+ email_digest = EmailDigest.new(user)
+ expect(email_digest.pending_notifications?).to_not be
+ end
+
+ it "returns false when there are no notifications for a user" do
+ user = create(:user)
+ email_digest = EmailDigest.new(user)
+ expect(email_digest.pending_notifications?).to_not be
+ end
+
+ end
+
+ describe "deliver" do
+
+ it "delivers email if notifications pending" do
+ user = create(:user)
+
+ proposal_notification = create(:proposal_notification)
+ notification = create(:notification, notifiable: proposal_notification, user: user)
+
+ reset_mailer
+ email_digest = EmailDigest.new(user)
+ email_digest.deliver
+
+ email = open_last_email
+ expect(email).to have_subject("Proposal notifications in Consul")
+ end
+
+ it "does not deliver email if no notifications pending" do
+ user = create(:user)
+
+ proposal_notification = create(:proposal_notification)
+ notification = create(:notification, notifiable: proposal_notification, user: user, emailed_at: Time.now)
+
+ reset_mailer
+ email_digest = EmailDigest.new(user)
+ email_digest.deliver
+
+ expect(all_emails.count).to eq(0)
+ end
+
+ end
+
+ describe "mark_as_emailed" do
+
+ it "marks notifications as emailed" do
+ user1 = create(:user)
+ user2 = create(:user)
+
+ proposal_notification = create(:proposal_notification)
+ notification1 = create(:notification, notifiable: proposal_notification, user: user1)
+ notification2 = create(:notification, notifiable: proposal_notification, user: user1)
+ notification3 = create(:notification, notifiable: proposal_notification, user: user2)
+
+ expect(notification1.emailed_at).to_not be
+ expect(notification2.emailed_at).to_not be
+ expect(notification3.emailed_at).to_not be
+
+ email_digest = EmailDigest.new(user1)
+ email_digest.mark_as_emailed
+
+ notification1.reload
+ notification2.reload
+ notification3.reload
+ expect(notification1.emailed_at).to be
+ expect(notification2.emailed_at).to be
+ expect(notification3.emailed_at).to_not be
+ end
+
end
end
\ No newline at end of file
diff --git a/spec/models/custom/.keep b/spec/models/custom/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/spec/models/custom/residence_spec.rb b/spec/models/custom/residence_spec.rb
new file mode 100644
index 000000000..81c0b2e9b
--- /dev/null
+++ b/spec/models/custom/residence_spec.rb
@@ -0,0 +1,34 @@
+require 'rails_helper'
+
+describe Verification::Residence do
+
+ let(:residence) { build(:verification_residence, document_number: "12345678Z") }
+
+ describe "verification" do
+
+ describe "postal code" do
+ it "should be valid with postal codes starting with 280" do
+ residence.postal_code = "28012"
+ residence.valid?
+ expect(residence.errors[:postal_code].size).to eq(0)
+
+ residence.postal_code = "28023"
+ residence.valid?
+ expect(residence.errors[:postal_code].size).to eq(0)
+ end
+
+ it "should not be valid with postal codes not starting with 280" do
+ residence.postal_code = "12345"
+ residence.valid?
+ expect(residence.errors[:postal_code].size).to eq(1)
+
+ residence.postal_code = "13280"
+ residence.valid?
+ expect(residence.errors[:postal_code].size).to eq(1)
+ expect(residence.errors[:postal_code]).to include("In order to be verified, you must be registered.")
+ end
+ end
+
+ end
+
+end
diff --git a/spec/models/proposal_spec.rb b/spec/models/proposal_spec.rb
index 4958204ec..247cd9e66 100644
--- a/spec/models/proposal_spec.rb
+++ b/spec/models/proposal_spec.rb
@@ -367,6 +367,50 @@ describe Proposal do
end
end
+ describe "voters" do
+
+ it "returns users that have voted for the proposal" do
+ proposal = create(:proposal)
+ voter1 = create(:user, :level_two)
+ voter2 = create(:user, :level_two)
+ voter3 = create(:user, :level_two)
+
+ create(:vote, voter: voter1, votable: proposal)
+ create(:vote, voter: voter2, votable: proposal)
+
+ expect(proposal.voters).to include(voter1)
+ expect(proposal.voters).to include(voter2)
+ expect(proposal.voters).to_not include(voter3)
+ end
+
+ it "does not return users that have been erased" do
+ proposal = create(:proposal)
+ voter1 = create(:user, :level_two)
+ voter2 = create(:user, :level_two)
+
+ create(:vote, voter: voter1, votable: proposal)
+ create(:vote, voter: voter2, votable: proposal)
+ voter2.erase
+
+ expect(proposal.voters).to include(voter1)
+ expect(proposal.voters).to_not include(voter2)
+ end
+
+ it "does not return users that have been blocked" do
+ proposal = create(:proposal)
+ voter1 = create(:user, :level_two)
+ voter2 = create(:user, :level_two)
+
+ create(:vote, voter: voter1, votable: proposal)
+ create(:vote, voter: voter2, votable: proposal)
+ voter2.block
+
+ expect(proposal.voters).to include(voter1)
+ expect(proposal.voters).to_not include(voter2)
+ end
+
+ end
+
describe "search" do
context "attributes" do
diff --git a/spec/models/residence_spec.rb b/spec/models/residence_spec.rb
index 5288a20b5..968a80e62 100644
--- a/spec/models/residence_spec.rb
+++ b/spec/models/residence_spec.rb
@@ -30,29 +30,6 @@ describe Verification::Residence do
expect(residence.errors[:date_of_birth]).to include("You must be at least 16 years old")
end
- describe "postal code" do
- it "should be valid with postal codes starting with 280" do
- residence.postal_code = "28012"
- residence.valid?
- expect(residence.errors[:postal_code].size).to eq(0)
-
- residence.postal_code = "28023"
- residence.valid?
- expect(residence.errors[:postal_code].size).to eq(0)
- end
-
- it "should not be valid with postal codes not starting with 280" do
- residence.postal_code = "12345"
- residence.valid?
- expect(residence.errors[:postal_code].size).to eq(1)
-
- residence.postal_code = "13280"
- residence.valid?
- expect(residence.errors[:postal_code].size).to eq(1)
- expect(residence.errors[:postal_code]).to include("In order to be verified, you must be registered.")
- end
- end
-
it "should validate uniquness of document_number" do
user = create(:user)
residence.user = user
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index d54f3a42e..b6084a971 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -325,6 +325,34 @@ describe User do
end
+ describe "scopes" do
+
+ describe "active" do
+
+ it "returns users that have not been erased" do
+ user1 = create(:user, erased_at: nil)
+ user2 = create(:user, erased_at: nil)
+ user3 = create(:user, erased_at: Time.now)
+
+ expect(User.active).to include(user1)
+ expect(User.active).to include(user2)
+ expect(User.active).to_not include(user3)
+ end
+
+ it "returns users that have not been blocked" do
+ user1 = create(:user)
+ user2 = create(:user)
+ user3 = create(:user)
+ user3.block
+
+ expect(User.active).to include(user1)
+ expect(User.active).to include(user2)
+ expect(User.active).to_not include(user3)
+ end
+
+ end
+ end
+
describe "self.search" do
it "find users by email" do
user1 = create(:user, email: "larry@consul.dev")