lines-engine 0.5 → 0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/CHANGELOG.md +21 -0
  5. data/Gemfile +2 -2
  6. data/Gemfile.lock +100 -70
  7. data/Guardfile +33 -0
  8. data/README.rdoc +12 -8
  9. data/app/assets/stylesheets/lines/article.scss +55 -31
  10. data/app/assets/stylesheets/lines/fonts.scss +1 -34
  11. data/app/assets/stylesheets/lines/general.scss +2 -0
  12. data/app/assets/stylesheets/lines/media_queries.scss +11 -5
  13. data/app/assets/stylesheets/lines/variables_and_mixins.scss +6 -0
  14. data/app/controllers/lines/articles_controller.rb +3 -2
  15. data/app/controllers/lines/password_resets_controller.rb +84 -0
  16. data/app/helpers/lines/application_helper.rb +2 -2
  17. data/app/mailers/lines/user_mailer.rb +11 -0
  18. data/app/models/lines/user.rb +41 -5
  19. data/app/views/layouts/lines/application.html.erb +1 -0
  20. data/app/views/lines/password_resets/edit.html.erb +26 -0
  21. data/app/views/lines/password_resets/new.html.erb +8 -0
  22. data/app/views/lines/sessions/new.html.erb +4 -0
  23. data/app/views/lines/shared/_flash.html.erb +8 -0
  24. data/app/views/lines/user_mailer/password_reset.html.erb +10 -0
  25. data/app/views/lines/user_mailer/password_reset.text.erb +8 -0
  26. data/certs/lines-engine.pem +21 -0
  27. data/config/lines_config.yml +4 -1
  28. data/config/routes.rb +5 -0
  29. data/db/migrate/20150421093311_add_reset_password_fields_to_users.rb +6 -0
  30. data/lib/generators/lines/copy_styles_generator.rb +13 -0
  31. data/lib/lines/version.rb +1 -1
  32. data/lines.gemspec +5 -2
  33. data/spec/dummy/config/environments/development.rb +5 -0
  34. data/spec/dummy/config/environments/test.rb +2 -0
  35. data/spec/dummy/config/lines_config.yml +4 -1
  36. data/spec/dummy/db/schema.rb +3 -1
  37. data/spec/dummy/test/mailers/previews/user_mailer_preview.rb +11 -0
  38. data/spec/features/article_spec.rb +0 -1
  39. data/spec/features/password_reset_spec.rb +48 -0
  40. data/spec/mailers/lines/user_mailer_spec.rb +24 -0
  41. metadata +51 -12
  42. metadata.gz.sig +0 -0
@@ -1,37 +1,4 @@
1
1
  @import url(//fonts.googleapis.com/css?family=Merriweather:400,400italic,700);
2
2
  @import url(//fonts.googleapis.com/css?family=Merriweather+Sans:400,700);
3
3
  @import url(//fonts.googleapis.com/css?family=Source+Sans+Pro:200,300,400,600,700,700italic,900);
4
- @import url(//fonts.googleapis.com/css?family=Ubuntu+Mono:400,700,400italic,700italic&subset=latin,latin-ext);
5
-
6
- @font-face {
7
- font-family: 'BPmono';
8
- src: url('/assets/BPmono-webfont.eot');
9
- src: url('/assets/BPmono-webfont.eot?#iefix') format('embedded-opentype'),
10
- url('/assets/BPmono-webfont.woff') format('woff'),
11
- url('/assets/BPmono-webfont.ttf') format('truetype'),
12
- url('/assets/BPmono-webfont.svg#BPmono-webfont') format('svg');
13
- font-weight: normal;
14
- font-style: normal;
15
- }
16
-
17
- @font-face {
18
- font-family: 'BPmono';
19
- src: url('/assets/BPmonoItalics-webfont.eot');
20
- src: url('/assets/BPmonoItalics-webfont.eot?#iefix') format('embedded-opentype'),
21
- url('/assets/BPmonoItalics-webfont.woff') format('woff'),
22
- url('/assets/BPmonoItalics-webfont.ttf') format('truetype'),
23
- url('/assets/BPmonoItalics-webfont.svg#BPmonoItalics-webfont') format('svg');
24
- font-weight: normal;
25
- font-style: italic;
26
- }
27
-
28
- @font-face {
29
- font-family: 'BPmono';
30
- src: url('/assets/BPmonoBold-webfont.eot');
31
- src: url('/assets/BPmonoBold-webfont.eot?#iefix') format('embedded-opentype'),
32
- url('/assets/BPmonoBold-webfont.woff') format('woff'),
33
- url('/assets/BPmonoBold-webfont.ttf') format('truetype'),
34
- url('/assets/BPmonoBold-webfont.svg#BPmonoBold-webfont') format('svg');
35
- font-weight: bold;
36
- font-style: normal;
37
- }
4
+ @import url(//fonts.googleapis.com/css?family=Fira+Mono:400,700&subset=latin,latin-ext);
@@ -5,6 +5,8 @@
5
5
  box-sizing: border-box;
6
6
  }
7
7
 
8
+ html, body { font-size: 16px }
9
+
8
10
  body { height: 100%; background-color: #fff; }
9
11
 
10
12
  img, div { border: 0; display: block; }
@@ -8,11 +8,11 @@ smaller than 975px
8
8
  smaller than 800px
9
9
  *************************************************************************************/
10
10
  @media screen and (max-width: 800px) {
11
- html { font-size: 16px; }
11
+ html, body { font-size: 16px; }
12
12
  .article {
13
- .article_header, .article_teaser, .article_content {
14
- & > * { padding: 0 8%; }
15
- }
13
+ .article_header, .article_teaser, .article_content {
14
+ & > * { padding: 0 8%; }
15
+ }
16
16
  }
17
17
  }
18
18
 
@@ -20,7 +20,7 @@ smaller than 800px
20
20
  smaller than 600px
21
21
  *************************************************************************************/
22
22
  @media screen and (max-width: 600px) {
23
- html { font-size: 14px; }
23
+ html, body { font-size: 14px; }
24
24
  .article.small { width: 100%; max-width: 100%; }
25
25
  }
26
26
 
@@ -28,4 +28,10 @@ smaller than 600px
28
28
  smaller than 480px
29
29
  *************************************************************************************/
30
30
  @media screen and (max-width: 480px) {
31
+ html, body { font-size: 14px; }
32
+ .article {
33
+ .article_header h1 {
34
+ font-size: 2.5em;
35
+ }
36
+ }
31
37
  }
@@ -4,9 +4,15 @@ $darkgrey: #333;
4
4
  $lightgrey: #666;
5
5
  $orange: #ee4422;
6
6
 
7
+ $red: #c10020;
8
+ $black: #100719;
9
+ $grey: #9793a0;
10
+ $white: #fff;
11
+
7
12
  $SourceSansPro: 'Source Sans Pro', 'Helvetica', 'Arial', sans-serif;
8
13
  $MerriweatherSans: 'Merriweather Sans', 'Helvetica', 'Arial', sans-serif;
9
14
  $Merriweather: 'Merriweather', 'Georgia', serif;
15
+ $Monospace: 'Fira mono', monospace;
10
16
 
11
17
  @mixin addTransition($x) {
12
18
  transition: all $x+s ease;
@@ -24,8 +24,9 @@ module Lines
24
24
  end
25
25
 
26
26
  if @articles.first_page?
27
- @first_article = Article.published.first
28
- @first_article.teaser = nil unless @first_article.teaser.present?
27
+ if @first_article = Article.published.first
28
+ @first_article.teaser = nil unless @first_article.teaser.present?
29
+ end
29
30
  end
30
31
 
31
32
  set_meta_tags title: SITE_TITLE,
@@ -0,0 +1,84 @@
1
+ require_dependency "lines/application_controller"
2
+
3
+ module Lines
4
+ class PasswordResetsController < ApplicationController
5
+
6
+ layout 'lines/admin'
7
+
8
+ before_action :get_user, only: [:edit, :update]
9
+ before_action :valid_user, only: [:edit, :update]
10
+
11
+ def new
12
+ end
13
+
14
+ def create
15
+ @user = User.find_by(email: params[:password_reset][:email].downcase)
16
+ if @user
17
+ @user.create_reset_digest
18
+ @user.send_password_reset_email
19
+ flash[:info] = "Email sent with password reset instructions"
20
+ redirect_to root_url
21
+ else
22
+ flash.now[:error] = "Email address not found"
23
+ render 'new'
24
+ end
25
+ end
26
+
27
+ def edit
28
+ end
29
+
30
+ def update
31
+ if password_blank?
32
+ flash.now[:error] = "Password can't be blank"
33
+ render 'edit'
34
+ elsif wrong_password_confirmation?
35
+ flash.now[:error] = "Password confirmation does not match"
36
+ render 'edit'
37
+ elsif @user.update_attributes(user_params)
38
+ # deletr reset_digest and reset_sent_at
39
+ @user.update_attributes(reset_digest: nil, reset_sent_at: nil)
40
+ flash[:success] = "Password has been reset. You can now log in with your new password."
41
+ redirect_to new_session_path
42
+ else
43
+ render 'edit'
44
+ end
45
+ end
46
+
47
+
48
+ private
49
+
50
+ # Use strong_params
51
+ def user_params
52
+ params.require(:user).permit(:email, :password, :password_confirmation)
53
+ end
54
+
55
+ # Returns true if password is blank.
56
+ def password_blank?
57
+ params[:user][:password].blank?
58
+ end
59
+
60
+ def wrong_password_confirmation?
61
+ params[:user][:password] != params[:user][:password_confirmation]
62
+ end
63
+
64
+ # Checks expiration of reset token.
65
+ def check_expiration
66
+ if @user.password_reset_expired?
67
+ flash[:error] = "Password reset has expired."
68
+ redirect_to new_password_reset_url
69
+ end
70
+ end
71
+
72
+ def get_user
73
+ @user = User.find_by(email: params[:email])
74
+ end
75
+
76
+ # Confirms a valid user.
77
+ def valid_user
78
+ unless (@user && @user.authenticated?(:reset, params[:id]))
79
+ redirect_to root_url
80
+ end
81
+ end
82
+
83
+ end
84
+ end
@@ -8,9 +8,9 @@ module Lines
8
8
  # Renders the teaser for an article.
9
9
  def render_teaser(article, article_counter=0)
10
10
  if article_counter < 0
11
- teaser = article.teaser.present? ? markdown(article.teaser) : nil
11
+ teaser = article.teaser && article.teaser.present? ? markdown(article.teaser) : nil
12
12
  else
13
- teaser = article.teaser.present? ? format_code(article.teaser) : format_code(article.content)
13
+ teaser = article.teaser && article.teaser.present? ? format_code(article.teaser) : format_code(article.content)
14
14
  end
15
15
  teaser
16
16
  end
@@ -0,0 +1,11 @@
1
+ module Lines
2
+ class UserMailer < ActionMailer::Base
3
+ default from: CONFIG[:production]['from_email']
4
+
5
+ def password_reset(user)
6
+ @user = user
7
+ mail to: user.email, subject: "Password reset"
8
+ end
9
+
10
+ end
11
+ end
@@ -15,16 +15,53 @@
15
15
  module Lines
16
16
 
17
17
  class User < ActiveRecord::Base
18
- # use bcrypt-ruby to encrypt passwords
18
+ # Use bcrypt-ruby to encrypt passwords
19
19
  has_secure_password validations: false
20
- validates :password, length: { minimum: 6 }, allow_blank: false
21
- validates :password, presence: true, on: :create
20
+
21
+ attr_accessor :reset_token
22
22
 
23
23
  # Validations
24
24
  validates :password, length: { minimum: 6 }, if: :validate_password?
25
+ validates :password, presence: true, on: :create
25
26
  validates :email, uniqueness: true, presence: true
26
27
 
27
-
28
+ # Sets +rest_digest+ and +reset_sent_at+ for password reset.
29
+ def create_reset_digest
30
+ self.reset_token = User.generate_token
31
+ update_attribute(:reset_digest, User.digest(reset_token))
32
+ update_attribute(:reset_sent_at, Time.zone.now)
33
+ end
34
+
35
+ # Sends email with instructions how to reset password.
36
+ def send_password_reset_email
37
+ UserMailer.password_reset(self).deliver
38
+ end
39
+
40
+ # Returns true if the given token matches the digest.
41
+ def authenticated?(attribute, token)
42
+ digest = send("#{attribute}_digest")
43
+ return false if digest.nil?
44
+ BCrypt::Password.new(digest).is_password?(token)
45
+ end
46
+
47
+
48
+ # Generate a random token.
49
+ def User.generate_token
50
+ SecureRandom.urlsafe_base64
51
+ end
52
+
53
+ # Returns the hash digest of the given string.
54
+ def User.digest(string)
55
+ cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
56
+ BCrypt::Password.create(string, cost: cost)
57
+ end
58
+
59
+ # Returns true if a password reset has expired.
60
+ def password_reset_expired?
61
+ reset_sent_at < 2.hours.ago
62
+ end
63
+
64
+
28
65
  private
29
66
  # Returns +true+ if a password is submitted
30
67
  def validate_password?
@@ -32,5 +69,4 @@ module Lines
32
69
  end
33
70
 
34
71
  end
35
-
36
72
  end
@@ -32,6 +32,7 @@
32
32
  </header>
33
33
 
34
34
  <div class="container">
35
+ <%= render 'lines/shared/flash' %>
35
36
  <%= yield %>
36
37
  </div>
37
38
 
@@ -0,0 +1,26 @@
1
+ <h1>Reset password</h1>
2
+
3
+ <%= form_for(@user, url: password_reset_path(params[:id])) do |f| %>
4
+ <% if @user.errors.any? %>
5
+ <div id="error_explanation">
6
+ <div class="alert errors">
7
+ The form contains <%= pluralize(@user.errors.count, "error") %>.
8
+ </div>
9
+ <ul>
10
+ <% @user.errors.full_messages.each do |msg| %>
11
+ <li><%= msg %></li>
12
+ <% end %>
13
+ </ul>
14
+ </div>
15
+ <% end %>
16
+
17
+ <%= hidden_field_tag :email, @user.email %>
18
+
19
+ <%= f.label :password %>
20
+ <%= f.password_field :password %>
21
+
22
+ <%= f.label :password_confirmation, "Confirmation" %>
23
+ <%= f.password_field :password_confirmation %>
24
+
25
+ <%= f.submit "Update password" %>
26
+ <% end %>
@@ -0,0 +1,8 @@
1
+ <h1>Reset Password</h1>
2
+
3
+ <%= form_for(:password_reset, url: password_resets_path) do |f| %>
4
+ <%= f.label :email %>
5
+ <%= f.email_field :email %>
6
+
7
+ <%= f.submit "Submit" %>
8
+ <% end %>
@@ -18,5 +18,9 @@
18
18
  <%= submit_tag "Login" %>
19
19
  <a href="/">or go back to your blog.</a>
20
20
  </div>
21
+ <br style="clear: both;" />
22
+ <div class="additional_actions">
23
+ <%= link_to "Forgot password?", new_password_reset_path %>
24
+ </div>
21
25
  <% end %>
22
26
  </div>
@@ -0,0 +1,8 @@
1
+ <% flash.each do |name, msg| %>
2
+ <% if msg.is_a?(String) %>
3
+ <div class="alert alert-<%= name == :notice ? "success" : "error" %>">
4
+ <a class="close" data-dismiss="alert">&#215;</a>
5
+ <%= content_tag :div, msg, id: "flash_#{name}" %>
6
+ </div>
7
+ <% end %>
8
+ <% end %>
@@ -0,0 +1,10 @@
1
+ <h1>Password reset</h1>
2
+
3
+ <p>To reset your password click the link below:</p>
4
+
5
+ <p><%= edit_password_reset_url(@user.reset_token, email: @user.email) %></p>
6
+
7
+ <p>This link will expire in two hours.</p>
8
+
9
+ <p>If you did not request your password to be reset, please ignore this email and
10
+ your password will stay as it is.</p>
@@ -0,0 +1,8 @@
1
+ To reset your password click the link below:
2
+
3
+ <%= edit_password_reset_url(@user.reset_token, email: @user.email) %>
4
+
5
+ This link will expire in two hours.
6
+
7
+ If you did not request your password to be reset, please ignore this email and
8
+ your password will stay as it is.
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDYDCCAkigAwIBAgIBATANBgkqhkiG9w0BAQUFADA7MQ0wCwYDVQQDDARpbmZv
3
+ MRYwFAYKCZImiZPyLGQBGRYGb3BvbG9vMRIwEAYKCZImiZPyLGQBGRYCZGUwHhcN
4
+ MTUwNDIxMTIyMzQ5WhcNMTYwNDIwMTIyMzQ5WjA7MQ0wCwYDVQQDDARpbmZvMRYw
5
+ FAYKCZImiZPyLGQBGRYGb3BvbG9vMRIwEAYKCZImiZPyLGQBGRYCZGUwggEiMA0G
6
+ CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDxEm+2kvkvFdAyi6VKiSPvEbFLgvC7
7
+ +MnhXzkFGLOb4UFNB0uSK2xGdBf2QVDWmK8W19UQNWoXHv5Pfni0wmUJ5yoP7lNg
8
+ qMqZFK0tkDImsQRrfOndqNylVyy9vBn/YtyAYG/v+IGxEW59OoL1uvX4FnxJ3C07
9
+ P8I0gWPUlw4/UFSpzTj1r4IEeN21oYlgCSz98O5vZVkRZAVNmotxcn/+vcpERjZ+
10
+ ugolMzyEqC5Uztos/D4pSrtum/UIhDbYfzkoI78hZjsaKQQwbmJd9/WzI5FUqEhS
11
+ ucANoSTcibahVQ5Qg1AedJCTgO5Z2NBdCSXce8q5wx5W7BqNPNjML5VpAgMBAAGj
12
+ bzBtMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBSB4W9llLnHPI5e
13
+ OUL+IbfDGahfYjAZBgNVHREEEjAQgQ5pbmZvQG9wb2xvby5kZTAZBgNVHRIEEjAQ
14
+ gQ5pbmZvQG9wb2xvby5kZTANBgkqhkiG9w0BAQUFAAOCAQEAxSYCQ1c3Am0fpQZW
15
+ NNOiHDAcn8k8B4rFp0/llzsIH0WOttTnhYYHqKdnpWJms2xPOPppb9MD8d+HpBn4
16
+ mCIzpjfzE0RQ0RzzLUdfDXLJPjOaJ/vtKZMMBxfMivLfx8HZTGrVVVE3l8wPav/2
17
+ CMUyx9n4Yn6xztVzRbadZp3Q1O/Bsg7dFz7xVkxUEaM1oHGFR+HrWRX2ZyDpaUHJ
18
+ fNEZV3vwM6Ye3VRzeM3R7Z1R9e9FbUK6awQt/x4gAaI9m3LS85lbwuibKKpdYupp
19
+ 5BNWA8E1pTLFIGuj2vWGF3LmLHATeyBGXNDBRf6XZUG124zSK5pxkJ5zm2uBhu00
20
+ +pn0Gw==
21
+ -----END CERTIFICATE-----
@@ -49,7 +49,10 @@ secret_token: "afa4a436180a2febe9b814c6a57b6f3ad298aa82ce0ab4c9e8238713764b665e9
49
49
  # Environment dependent configuration. Place your URLs here
50
50
  development:
51
51
  host: "localhost:3000"
52
+ from_email: 'noreply@example.com'
52
53
  test:
53
54
  host: "test.local"
55
+ from_email: 'noreply@example.com'
54
56
  production:
55
- host: "blog.opoloo.com"
57
+ host: "blog.opoloo.com"
58
+ from_email: 'noreply@example.com'
@@ -1,10 +1,15 @@
1
1
  Lines::Engine.routes.draw do
2
2
 
3
+ get 'password_resets/new'
4
+
5
+ get 'password_resets/edit'
6
+
3
7
  get 'login', to: 'sessions#new', as: 'login'
4
8
  get 'logout', to: 'sessions#destroy', as: 'logout'
5
9
  get 'tags/:tag', to: 'articles#index', as: :tag
6
10
 
7
11
  resources :sessions
12
+ resources :password_resets, only: [:new, :create, :edit, :update]
8
13
 
9
14
  resources :articles, only: [:index, :show] do
10
15
  get 'page/:page', action: :index, on: :collection
@@ -0,0 +1,6 @@
1
+ class AddResetPasswordFieldsToUsers < ActiveRecord::Migration
2
+ def change
3
+ add_column :lines_users, :reset_digest, :string
4
+ add_column :lines_users, :reset_sent_at, :datetime
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ require 'rails/generators'
2
+
3
+ module Lines
4
+ class CopyStylesGenerator < Rails::Generators::Base
5
+ source_root File.expand_path('..', __FILE__)
6
+
7
+ def copy_stylesheets
8
+ FileUtils.mkdir_p "app/assets/stylesheets/lines"
9
+ directory('../../../app/assets/stylesheets/lines/', 'app/assets/stylesheets/lines/', {:exclude_pattern => /admin/} )
10
+ end
11
+
12
+ end
13
+ end