diff --git a/app/controllers/maintenance/user/login_reminders_controller.rb b/app/controllers/maintenance/user/login_reminders_controller.rb
new file mode 100644
index 000000000..15b043798
--- /dev/null
+++ b/app/controllers/maintenance/user/login_reminders_controller.rb
@@ -0,0 +1,20 @@
+module Maintenance
+ module User
+ class LoginRemindersController < ApplicationController
+ def new
+ end
+
+ def create
+ @user = ::User.with_email(params[:user][:email]).first
+ if @user
+ LoginReminderMailer.notice(@user).deliver
+ flash[:notice] = "Email sent"
+ else
+ flash[:notice] = "Email address not found"
+ end
+
+ redirect_to new_maintenance_user_login_reminder_path
+ end
+ end
+ end
+end
diff --git a/app/controllers/maintenance/user/password_resets_controller.rb b/app/controllers/maintenance/user/password_resets_controller.rb
new file mode 100644
index 000000000..22116c39b
--- /dev/null
+++ b/app/controllers/maintenance/user/password_resets_controller.rb
@@ -0,0 +1,34 @@
+module Maintenance
+ module User
+ class PasswordResetsController < ApplicationController
+ def new
+ @nonce = UserPasswordResetNonce.new
+ end
+
+ def create
+ @nonce = UserPasswordResetNonce.create(params[:nonce])
+ if @nonce.errors.any?
+ redirect_to new_maintenance_user_password_reset_path, :notice => @nonce.errors.full_messages.join("; ")
+ else
+ redirect_to new_maintenance_user_password_reset_path, :notice => "Email request sent"
+ end
+ end
+
+ def edit
+ @nonce = UserPasswordResetNonce.where(:email => params[:email], :key => params[:key]).first
+ end
+
+ def update
+ @nonce = UserPasswordResetNonce.where(:email => params[:email], :key => params[:key]).first
+
+ if @nonce
+ @nonce.reset_user!
+ @nonce.destroy
+ redirect_to new_maintenance_user_password_reset_path, :notice => "Password reset; email delivered with new password"
+ else
+ redirect_to new_maintenance_user_password_reset_path, :notice => "Invalid key"
+ end
+ end
+ end
+ end
+end
diff --git a/app/controllers/user_maintenance_controller.rb b/app/controllers/user_maintenance_controller.rb
deleted file mode 100644
index ea480db65..000000000
--- a/app/controllers/user_maintenance_controller.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-class UserMaintenanceController < ApplicationController
- def login_reminder
- if request.post?
- @user = User.with_email(params[:user][:email]).first
- if @user
- UserMaintenanceMailer.login_reminder(@user).deliver
- flash[:notice] = "Email sent"
- else
- flash[:notice] = "No matching user record found"
- end
- end
- end
-
- def reset_password
- if request.post?
- @user = User.find_for_password_reset(params[:user][:name], params[:user][:email]).first
- if @user
- @user.reset_password_and_deliver_notice
- flash[:notice] = "Email sent"
- else
- flash[:notice] = "No matching user record found"
- end
- end
- end
-end
diff --git a/app/controllers/wiki_page_versions_controller.rb b/app/controllers/wiki_page_versions_controller.rb
index 7645e87d4..935895ecf 100644
--- a/app/controllers/wiki_page_versions_controller.rb
+++ b/app/controllers/wiki_page_versions_controller.rb
@@ -3,7 +3,7 @@ class WikiPageVersionsController < ApplicationController
def index
@search = WikiPageVersion.search(params[:search])
- @wiki_page_versions = @search.paginate(:page => params[:page])
+ @wiki_page_versions = @search.paginate(params[:page])
respond_with(@wiki_page_versions)
end
diff --git a/app/logical/post_sets/artist.rb b/app/logical/post_sets/artist.rb
index cccb64013..cdd822f8f 100644
--- a/app/logical/post_sets/artist.rb
+++ b/app/logical/post_sets/artist.rb
@@ -6,5 +6,13 @@ module PostSets
super(:tags => artist.name)
@artist = artist
end
+
+ def posts
+ ::Post.tag_match(@artist.name)
+ end
+
+ def presenter
+ ::PostSetPresenters::Post.new(self)
+ end
end
end
diff --git a/app/mailers/maintenance/user/login_reminder_mailer.rb b/app/mailers/maintenance/user/login_reminder_mailer.rb
new file mode 100644
index 000000000..76beac06e
--- /dev/null
+++ b/app/mailers/maintenance/user/login_reminder_mailer.rb
@@ -0,0 +1,10 @@
+module Maintenance
+ module User
+ class LoginReminderMailer < ActionMailer::Base
+ def notice(user)
+ @user = user
+ mail(:to => user.email, :subject => "#{Danbooru.config.app_name} login reminder")
+ end
+ end
+ end
+end
diff --git a/app/mailers/maintenance/user/password_reset_mailer.rb b/app/mailers/maintenance/user/password_reset_mailer.rb
new file mode 100644
index 000000000..264248a4e
--- /dev/null
+++ b/app/mailers/maintenance/user/password_reset_mailer.rb
@@ -0,0 +1,15 @@
+module Maintenance
+ module User
+ class PasswordResetMailer < ActionMailer::Base
+ def request(user)
+ @user = user
+ mail(:to => @user.email, :subject => "#{Danbooru.config.app_name} password reset request")
+ end
+
+ def confirmation(user)
+ @user = user
+ mail(:to => @user.email, :subject => "#{Danbooru.config.app_name} password reset confirmation")
+ end
+ end
+ end
+end
diff --git a/app/models/user_maintenance_mailer.rb b/app/models/user_maintenance_mailer.rb
index 78905b4f2..5882caab9 100644
--- a/app/models/user_maintenance_mailer.rb
+++ b/app/models/user_maintenance_mailer.rb
@@ -1,10 +1,6 @@
class UserMaintenanceMailer < ActionMailer::Base
default :from => Danbooru.config.contact_email
- def login_reminder(user)
- @user = user
- mail(:to => user.email, :subject => "#{Danbooru.config.app_name} login reminder")
- end
def reset_password(user, new_password)
@user = user
diff --git a/app/models/user_password_reset_nonce.rb b/app/models/user_password_reset_nonce.rb
new file mode 100644
index 000000000..8877680b1
--- /dev/null
+++ b/app/models/user_password_reset_nonce.rb
@@ -0,0 +1,30 @@
+class UserPasswordResetNonce < ActiveRecord::Base
+ validates_uniqueness_of :email
+ validates_presence_of :email, :key
+ validate :validate_existence_of_email
+ before_validation :initialize_key, :on => :create
+ after_create :deliver_notice
+
+ def deliver_notice
+ Maintenance::User::PasswordResetMailer.request(user).deliver
+ end
+
+ def initialize_key
+ self.key = SecureRandom.hex(16)
+ end
+
+ def validate_existence_of_email
+ if !User.with_email(email).exists?
+ errors[:email] << "is invalid"
+ return false
+ end
+ end
+
+ def reset_user!
+ user.reset_password_and_deliver_notice
+ end
+
+ def user
+ @user ||= User.with_email(email).first
+ end
+end
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 1504f960e..b4cc8da2a 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -82,7 +82,7 @@ class WikiPage < ActiveRecord::Base
end
def post_set
- @post_set ||= PostSets::WikiPage.new(title)
+ @post_set ||= PostSets::Post.new(title)
end
def presenter
diff --git a/app/views/maintenance/user/login_reminder_mailer/notice.html.erb b/app/views/maintenance/user/login_reminder_mailer/notice.html.erb
new file mode 100644
index 000000000..d73075396
--- /dev/null
+++ b/app/views/maintenance/user/login_reminder_mailer/notice.html.erb
@@ -0,0 +1 @@
+
Your username is <%= @user.name %>.
\ No newline at end of file
diff --git a/app/views/maintenance/user/login_reminders/new.html.erb b/app/views/maintenance/user/login_reminders/new.html.erb
new file mode 100644
index 000000000..bde3a3a3e
--- /dev/null
+++ b/app/views/maintenance/user/login_reminders/new.html.erb
@@ -0,0 +1,9 @@
+
+
+
Login Reminder
+
+
If you supplied an email address when signing up, <%= Danbooru.config.app_name %> can email you your login information. Password details will not be provided and will not be changed.
+
+
If you didn't supply a valid email address, you are out of luck.
+
+
diff --git a/app/views/maintenance/user/password_resets/edit.html.erb b/app/views/maintenance/user/password_resets/edit.html.erb
new file mode 100644
index 000000000..d5757696b
--- /dev/null
+++ b/app/views/maintenance/user/password_resets/edit.html.erb
@@ -0,0 +1,14 @@
+
+
+
Reset Password
+
+ <% if @nonce %>
+ <%= form_tag(maintenance_user_password_reset_path, :method => :put) do %>
+
Do you wish to reset your password? A new password will be emailed to you.
+ <%= submit_tag "Reset" %>
+ <% end %>
+ <% else %>
+
Invalid key
+ <% end %>
+
+
\ No newline at end of file
diff --git a/app/views/maintenance/user/password_resets/new.html.erb b/app/views/maintenance/user/password_resets/new.html.erb
new file mode 100644
index 000000000..d26d6bce4
--- /dev/null
+++ b/app/views/maintenance/user/password_resets/new.html.erb
@@ -0,0 +1,14 @@
+
+
+
Reset Password
+
+
If you supplied an email address when signing up, <%= Danbooru.config.app_name %> can reset your password. You will receive an email confirming your request for a new password.
+
+
If you didn't supply a valid email address, you are out of luck.
+
+ <%= form_tag(maintenance_user_password_reset_path, :method => :post) do %>
+ <%= text_field :nonce, :email %>
+ <%= submit_tag "Submit" %>
+ <% end %>
+
+
\ No newline at end of file
diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb
index ca52af5c3..073afcce1 100644
--- a/app/views/users/index.html.erb
+++ b/app/views/users/index.html.erb
@@ -4,12 +4,6 @@
<% simple_form_for(@search) do |f| %>
<%= f.input :name_contains, :label => "Name" %>
- <%= f.input :is_banned_is_true, :label => "Banned" %>
- <%= f.input :is_privileged_is_true, :label => "Privileged" %>
- <%= f.input :is_contributor_is_true, :label => "Contributor" %>
- <%= f.input :is_janitor_is_true, :label => "Janitor" %>
- <%= f.input :is_moderator_is_true, :label => "Moderator" %>
- <%= f.input :is_admin_is_true, :label => "Admin" %>
<%= f.sort_link "Name", :name %>
<%= f.sort_link "Date", :created_at_desc %>
<%= f.button :submit %>
@@ -47,18 +41,18 @@
|
<% end %>
<%= link_to user.note_versions.count, note_versions_path(:search => {:updater_id_eq => user.id}) %> |
- <%= user.pretty_level %> |
+ <%= user.level_string %> |
<%= time_ago_in_words user.created_at %> ago |
<% end %>
-
- <%= will_paginate(@users) %>
+
+ <%= numbered_paginator(@users) %>
- <%= render :partial => "footer" %>
+ <%= render "secondary_links" %>
diff --git a/app/views/wiki_pages/show.html.erb b/app/views/wiki_pages/show.html.erb
index 514c5721a..0d70bb055 100644
--- a/app/views/wiki_pages/show.html.erb
+++ b/app/views/wiki_pages/show.html.erb
@@ -10,7 +10,7 @@
- <%= @wiki_page.post_set.presenter.post_previews_html %>
+ <%= @wiki_page.post_set.presenter.post_previews_html(self) %>
diff --git a/config/application.rb b/config/application.rb
index 956963461..adbdc8097 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -12,7 +12,7 @@ module Danbooru
config.encoding = "utf-8"
config.filter_parameters += [:password]
config.assets.enabled = true
- config.autoload_paths += %W(#{config.root}/app/presenters #{config.root}/app/logical)
+ config.autoload_paths += %W(#{config.root}/app/presenters #{config.root}/app/logical #{config.root}/app/mailers)
config.plugins = [:all]
config.time_zone = 'Eastern Time (US & Canada)'
# config.action_view.javascript_expansions[:defaults] = [
diff --git a/db/development_structure.sql b/db/development_structure.sql
index e59fa525a..d52b2ef10 100644
--- a/db/development_structure.sql
+++ b/db/development_structure.sql
@@ -2422,6 +2422,38 @@ CREATE SEQUENCE user_feedback_id_seq
ALTER SEQUENCE user_feedback_id_seq OWNED BY user_feedback.id;
+--
+-- Name: user_password_reset_nonces; Type: TABLE; Schema: public; Owner: -; Tablespace:
+--
+
+CREATE TABLE user_password_reset_nonces (
+ id integer NOT NULL,
+ key character varying(255) NOT NULL,
+ email character varying(255) NOT NULL,
+ created_at timestamp without time zone,
+ updated_at timestamp without time zone
+);
+
+
+--
+-- Name: user_password_reset_nonces_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE user_password_reset_nonces_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MAXVALUE
+ NO MINVALUE
+ CACHE 1;
+
+
+--
+-- Name: user_password_reset_nonces_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE user_password_reset_nonces_id_seq OWNED BY user_password_reset_nonces.id;
+
+
--
-- Name: users; Type: TABLE; Schema: public; Owner: -; Tablespace:
--
@@ -2758,6 +2790,13 @@ ALTER TABLE uploads ALTER COLUMN id SET DEFAULT nextval('uploads_id_seq'::regcla
ALTER TABLE user_feedback ALTER COLUMN id SET DEFAULT nextval('user_feedback_id_seq'::regclass);
+--
+-- Name: id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE user_password_reset_nonces ALTER COLUMN id SET DEFAULT nextval('user_password_reset_nonces_id_seq'::regclass);
+
+
--
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--
@@ -3027,6 +3066,14 @@ ALTER TABLE ONLY user_feedback
ADD CONSTRAINT user_feedback_pkey PRIMARY KEY (id);
+--
+-- Name: user_password_reset_nonces_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
+--
+
+ALTER TABLE ONLY user_password_reset_nonces
+ ADD CONSTRAINT user_password_reset_nonces_pkey PRIMARY KEY (id);
+
+
--
-- Name: users_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
--
@@ -5122,4 +5169,6 @@ INSERT INTO schema_migrations (version) VALUES ('20110328215652');
INSERT INTO schema_migrations (version) VALUES ('20110328215701');
-INSERT INTO schema_migrations (version) VALUES ('20110607194023');
\ No newline at end of file
+INSERT INTO schema_migrations (version) VALUES ('20110607194023');
+
+INSERT INTO schema_migrations (version) VALUES ('20110717010705');
\ No newline at end of file
diff --git a/db/migrate/20110717010705_create_user_password_reset_nonces.rb b/db/migrate/20110717010705_create_user_password_reset_nonces.rb
new file mode 100644
index 000000000..55e83b0cc
--- /dev/null
+++ b/db/migrate/20110717010705_create_user_password_reset_nonces.rb
@@ -0,0 +1,9 @@
+class CreateUserPasswordResetNonces < ActiveRecord::Migration
+ def change
+ create_table :user_password_reset_nonces do |t|
+ t.column :key, :string, :null => false
+ t.column :email, :string, :null => false
+ t.timestamps
+ end
+ end
+end
diff --git a/test/factories/user_password_reset_nonce.rb b/test/factories/user_password_reset_nonce.rb
new file mode 100644
index 000000000..d1cd0076d
--- /dev/null
+++ b/test/factories/user_password_reset_nonce.rb
@@ -0,0 +1,2 @@
+Factory.define(:user_password_reset_nonce) do |f|
+end
diff --git a/test/fixtures/user_password_reset_nonces.yml b/test/fixtures/user_password_reset_nonces.yml
new file mode 100644
index 000000000..5394fa384
--- /dev/null
+++ b/test/fixtures/user_password_reset_nonces.yml
@@ -0,0 +1,11 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/Fixtures.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
diff --git a/test/functional/maintenance/user/login_reminders_controller_test.rb b/test/functional/maintenance/user/login_reminders_controller_test.rb
new file mode 100644
index 000000000..99e97c1dd
--- /dev/null
+++ b/test/functional/maintenance/user/login_reminders_controller_test.rb
@@ -0,0 +1,44 @@
+require "test_helper"
+
+module Maintenance
+ module User
+ class LoginRemindersControllerTest < ActionController::TestCase
+ context "A login reminder controller" do
+ setup do
+ @user = Factory.create(:user)
+ @blank_email_user = Factory.create(:user, :email => "")
+ CurrentUser.user = nil
+ CurrentUser.ip_addr = "127.0.0.1"
+ ActionMailer::Base.delivery_method = :test
+ ActionMailer::Base.deliveries.clear
+ end
+
+ teardown do
+ CurrentUser.user = nil
+ CurrentUser.ip_addr = nil
+ end
+
+ should "render the new page" do
+ get :new
+ assert_response :success
+ end
+
+ should "deliver an email with the login to the user" do
+ post :create, {:user => {:email => @user.email}}
+ assert_equal(flash[:notice], "Email sent")
+ assert_equal(1, ActionMailer::Base.deliveries.size)
+ end
+
+ context "for a user with a blank email" do
+ should "fail" do
+ post :create, {:user => {:email => ""}}
+ assert_equal("Email address not found", flash[:notice])
+ @blank_email_user.reload
+ assert_equal(@blank_email_user.created_at, @blank_email_user.updated_at)
+ assert_equal(0, ActionMailer::Base.deliveries.size)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/functional/maintenance/user/password_resets_controller_test.rb b/test/functional/maintenance/user/password_resets_controller_test.rb
new file mode 100644
index 000000000..1c43d8f1e
--- /dev/null
+++ b/test/functional/maintenance/user/password_resets_controller_test.rb
@@ -0,0 +1,98 @@
+require "test_helper"
+
+module Maintenance
+ module User
+ class PasswordResetsControllerTest < ActionController::TestCase
+ context "A password resets controller" do
+ setup do
+ @user = Factory.create(:user, :email => "abc@com.net")
+ CurrentUser.user = nil
+ CurrentUser.ip_addr = "127.0.0.1"
+ ActionMailer::Base.delivery_method = :test
+ ActionMailer::Base.deliveries.clear
+ end
+
+ teardown do
+ CurrentUser.user = nil
+ CurrentUser.ip_addr = nil
+ end
+
+ should "render the new page" do
+ get :new
+ assert_response :success
+ end
+
+ context "create action" do
+ context "given invalid parameters" do
+ setup do
+ post :create, {:nonce => {:email => ""}}
+ end
+
+ should "not create a new nonce" do
+ assert_equal(0, UserPasswordResetNonce.count)
+ end
+
+ should "redirect to the new page" do
+ assert_redirected_to new_maintenance_user_password_reset_path
+ end
+
+ should "not deliver an email" do
+ assert_equal(0, ActionMailer::Base.deliveries.size)
+ end
+ end
+
+ context "given valid parameters" do
+ setup do
+ post :create, {:nonce => {:email => @user.email}}
+ end
+
+ should "create a new nonce" do
+ assert_equal(1, UserPasswordResetNonce.where(:email => @user.email).count)
+ end
+
+ should "redirect to the new page" do
+ assert_redirected_to new_maintenance_user_password_reset_path
+ end
+
+ should "deliver an email to the supplied email address" do
+ assert_equal(1, ActionMailer::Base.deliveries.size)
+ end
+ end
+ end
+
+ context "edit action" do
+ context "with invalid parameters" do
+ setup do
+ get :edit, :email => "a@b.c"
+ end
+
+ should "succeed silently" do
+ assert_response :success
+ end
+ end
+
+ context "with valid parameters" do
+ setup do
+ @user = Factory.create(:user)
+ @nonce = Factory.create(:user_password_reset_nonce, :email => @user.email)
+ ActionMailer::Base.deliveries.clear
+ post :update, :email => @nonce.email, :key => @nonce.key
+ end
+
+ should "succeed" do
+ assert_redirected_to new_maintenance_user_password_reset_path
+ end
+
+ should "send an email" do
+ assert_equal(1, ActionMailer::Base.deliveries.size)
+ end
+
+ should "delete the nonce" do
+ assert_equal(0, UserPasswordResetNonce.count)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/functional/wiki_pages_controller_test.rb b/test/functional/wiki_pages_controller_test.rb
index 88911709a..93c826c17 100644
--- a/test/functional/wiki_pages_controller_test.rb
+++ b/test/functional/wiki_pages_controller_test.rb
@@ -15,7 +15,8 @@ class WikiPagesControllerTest < ActionController::TestCase
context "index action" do
setup do
- Factory.create(:wiki_page, :title => "abc")
+ @wiki_page_abc = Factory.create(:wiki_page, :title => "abc")
+ @wiki_page_def = Factory.create(:wiki_page, :title => "def")
end
should "list all wiki_pages" do
@@ -25,7 +26,7 @@ class WikiPagesControllerTest < ActionController::TestCase
should "list all wiki_pages (with search)" do
get :index, {:search => {:title_matches => "abc"}}
- assert_response :success
+ assert_redirected_to(wiki_page_path(@wiki_page_abc))
end
end
diff --git a/test/unit/maintenance/user/login_reminder_mailer_test.rb b/test/unit/maintenance/user/login_reminder_mailer_test.rb
new file mode 100644
index 000000000..5e46a93c1
--- /dev/null
+++ b/test/unit/maintenance/user/login_reminder_mailer_test.rb
@@ -0,0 +1,18 @@
+require "test_helper"
+
+module Maintenance
+ module User
+ class LoginReminderMailerTest < ActionMailer::TestCase
+ context "The login reminder mailer" do
+ setup do
+ @user = Factory.create(:user)
+ end
+
+ should "send the notie" do
+ LoginReminderMailer.notice(@user).deliver
+ assert !ActionMailer::Base.deliveries.empty?
+ end
+ end
+ end
+ end
+end
diff --git a/test/unit/user_password_reset_nonce_test.rb b/test/unit/user_password_reset_nonce_test.rb
new file mode 100644
index 000000000..4133af6c1
--- /dev/null
+++ b/test/unit/user_password_reset_nonce_test.rb
@@ -0,0 +1,48 @@
+require 'test_helper'
+
+class UserPasswordResetNonceTest < ActiveSupport::TestCase
+ context "Creating a new nonce" do
+ context "with a valid email" do
+ setup do
+ @user = Factory.create(:user, :email => "aaa@b.net")
+ @nonce = Factory.create(:user_password_reset_nonce, :email => @user.email)
+ end
+
+ should "validate" do
+ assert_equal([], @nonce.errors.full_messages)
+ end
+
+ should "populate the key with a random string" do
+ assert_equal(32, @nonce.key.size)
+ end
+
+ should "reset the password when reset" do
+ @nonce.user.expects(:reset_password_and_deliver_notice)
+ @nonce.reset_user!
+ end
+ end
+
+ context "with a blank email" do
+ setup do
+ @user = Factory.create(:user, :email => "")
+ @nonce = UserPasswordResetNonce.new(:email => "")
+ end
+
+ should "not validate" do
+ @nonce.save
+ assert_equal(["Email can't be blank", "Email is invalid"], @nonce.errors.full_messages.sort)
+ end
+ end
+
+ context "with an invalid email" do
+ setup do
+ @nonce = UserPasswordResetNonce.new(:email => "z@z.net")
+ end
+
+ should "not validate" do
+ @nonce.save
+ assert_equal(["Email is invalid"], @nonce.errors.full_messages)
+ end
+ end
+ end
+end