Skip to content

Commit

Permalink
74 auth via email link gem passwordless (#75)
Browse files Browse the repository at this point in the history
* add passwordless

* add gem letter-opener

* add passwordless views

* make sign in work

* users#show, basic views work

* Update new.html.erb

* no more need to load insta user in session

* insta user belongs to user

* add passwordless initializer

* prepare for sending emails in production

* require user for users controller, i18n

* Update new.html.erb

* associate insta user with user

* display insta users of a user

* no need for user partial in insta users index

* validation for import action

* move import action from ig_posts to ig_user

* Update show.html.erb

* Update show.html.erb

* fix import

* depreciate insta_user#show in favour of insta posts#index

* Update new.html.erb

* after connect insta account redirect to user path

* Update users_controller.rb

* annotate routes

* update tests

* update AWS SES creds for prod

* Update user.rb
  • Loading branch information
yshmarov authored Nov 9, 2022
1 parent 6a2a443 commit c933291
Show file tree
Hide file tree
Showing 39 changed files with 370 additions and 183 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ group :development do

# Speed up commands on slow machines / big apps [https://github.com/rails/spring]
# gem "spring"
gem 'letter_opener'
end

group :test do
Expand All @@ -84,3 +85,4 @@ gem 'data_migrate', '~> 8.1'

gem 'meta-tags', '~> 2.18'
gem 'honeybadger', '~> 4.0'
gem 'passwordless'
10 changes: 10 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ GEM
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2)
bcrypt (3.1.18)
better_html (2.0.1)
actionview (>= 6.0)
activesupport (>= 6.0)
Expand Down Expand Up @@ -130,6 +131,10 @@ GEM
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
json (2.6.2)
launchy (2.5.0)
addressable (~> 2.7)
letter_opener (1.8.1)
launchy (>= 2.2, < 3)
loofah (2.19.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
Expand Down Expand Up @@ -159,6 +164,9 @@ GEM
parallel (1.22.1)
parser (3.1.2.1)
ast (~> 2.4.1)
passwordless (0.11.0)
bcrypt (~> 3.1.11)
rails (>= 5.1.4)
pg (1.4.4)
public_suffix (5.0.0)
puma (5.6.5)
Expand Down Expand Up @@ -283,7 +291,9 @@ DEPENDENCIES
honeybadger (~> 4.0)
importmap-rails
jbuilder
letter_opener
meta-tags (~> 2.18)
passwordless
pg (~> 1.1)
puma (~> 5.0)
rails (~> 7.0.4)
Expand Down
17 changes: 13 additions & 4 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
class ApplicationController < ActionController::Base
include Passwordless::ControllerHelpers

helper_method :current_user

private

def current_user
@current_user ||= InstaUser.find(session[:insta_user_id]) if session[:insta_user_id]
rescue ActiveRecord::RecordNotFound
nil
@current_user ||= authenticate_by_session(User)
end

def require_user!
return if current_user

redirect_to root_path, flash: { alert: t('notifications.unauthorized') }
end
helper_method :current_user
end
12 changes: 4 additions & 8 deletions app/controllers/insta_posts_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class InstaPostsController < ApplicationController
before_action :set_user

# GET /u/:id/p
def index
cookies[:view] = params[:view] if params[:view].present? && %w[grid list].include?(params[:view])

Expand All @@ -12,21 +13,16 @@ def index
end
end

def import
# TODO: should (also) trigger by a job
InstaMediaService.new(@insta_user).call
redirect_to insta_user_posts_path(@insta_user), notice: t('.success')
end

# GET /u/:id/p/:post_id
def show
@post = @insta_user.insta_posts.find(params[:post_id])
@post = @insta_user.insta_posts.find(params[:id])
@posts = @insta_user.insta_posts.without(@post).order('RANDOM()').limit(6)
end

private

def set_user
@insta_user = InstaUser.find(params[:id])
@insta_user = InstaUser.find(params[:user_id])
seo_tags
end

Expand Down
19 changes: 15 additions & 4 deletions app/controllers/insta_users_controller.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
class InstaUsersController < ApplicationController
before_action :set_user, only: %i[show]
before_action :set_user, only: %i[show import]
before_action :require_user!, only: :import

# GET /u
def index
@insta_users = InstaUser.all.order(insta_posts_count: :desc)
@insta_users = InstaUser.where.not(insta_posts_count: 0).order(insta_posts_count: :desc)
set_meta_tags title: 'Blogs',
description: 'all instagram pages converted into blogs'
end

# GET /u/:id
def show
set_meta_tags title: @insta_user.username,
description: "#{@insta_user.username} blog website"
redirect_to insta_user_posts_path(@insta_user)
end

# POST /u/:id/import
def import
return unless @insta_user.user == current_user

# TODO: should trigger a job
InstaMediaService.new(@insta_user).call
redirect_to insta_user_posts_path(@insta_user), notice: t('.success')
end

private
Expand Down
9 changes: 5 additions & 4 deletions app/controllers/instagram_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@ def callback
insta_user_id = InstaAuthService.new(code, redirect_uri).call
return head :bad_request unless insta_user_id

session[:insta_user_id] = insta_user_id
insta_user = InstaUser.find(insta_user_id)
insta_user.update(user: current_user)

redirect_to insta_user_path(insta_user_id)
redirect_to user_path
end

# GET /instagram/delete
def delete
render plain: 'Please contact [email protected] to delete your data'
end

# GET /instagram/deauthorize
def deauthorize
render plain: 'Please contact [email protected] to deauthorize the app'
end
Expand All @@ -36,10 +39,8 @@ def deauthorize

def redirect_uri
if Rails.env.production?
# Rails.application.routes.url_helpers.instagram_callback_url
instagram_callback_url
else
# staging
# 'localhost:3000/instagram/callback/'
# 'https://insta2blog.com/instagram/callback'
'https://insta2site.herokuapp.com/'
Expand Down
6 changes: 0 additions & 6 deletions app/controllers/sessions_controller.rb

This file was deleted.

7 changes: 7 additions & 0 deletions app/controllers/static_pages_controller.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
class StaticPagesController < ApplicationController
before_action :seo_tags, only: %i[terms privacy pricing]

# GET /
def landing_page
count = InstaUser.count
random_offset = rand(count)
@random_user = InstaUser.offset(random_offset).first
end

# GET /terms
def terms; end

# GET /privacy
def privacy; end

# GET /pricing
def pricing; end

private
Expand Down
9 changes: 9 additions & 0 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class UsersController < ApplicationController
before_action :require_user!

# GET /me
def show
@user = current_user
@insta_users = @user.insta_users.order(created_at: :desc)
end
end
2 changes: 1 addition & 1 deletion app/mailers/application_mailer.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class ApplicationMailer < ActionMailer::Base
default from: 'from@example.com'
default from: 'hello@insta2blog.com'
layout 'mailer'
end
1 change: 1 addition & 0 deletions app/models/insta_user.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
class InstaUser < ApplicationRecord
belongs_to :user, optional: true
has_many :insta_posts, dependent: :destroy
has_many :insta_access_tokens, dependent: :destroy
validates :username, presence: true, uniqueness: true
Expand Down
18 changes: 18 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class User < ApplicationRecord
has_many :insta_users, dependent: :destroy

validates :email,
presence: true,
uniqueness: { case_sensitive: false },
format: { with: URI::MailTo::EMAIL_REGEXP }

passwordless_with :email

def self.fetch_resource_for_passwordless(email)
find_or_create_by(email:)
end

def username
email.split('@').first
end
end
6 changes: 1 addition & 5 deletions app/views/insta_posts/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
<section class='flex flex-col space-y-4 items-center'>
<div class='text-sm text-gray-700 font-semibold'>
<%= link_to_unless_current @insta_user.at_username, insta_user_posts_path(@insta_user) %>
</div>

<section class='flex flex-col items-center'>
<%= render partial: 'insta_posts/insta_post', locals: { post: @post } %>
</section>

Expand Down
43 changes: 42 additions & 1 deletion app/views/insta_users/_insta_user.html.erb
Original file line number Diff line number Diff line change
@@ -1 +1,42 @@
<%= link_to insta_user.at_username, insta_user_posts_path(insta_user), class: 'bg-slate-50 border border-slate-300 rounded-md p-4 lg:w-1/3 md:w-2/3 sm:w-full hover:bg-slate-100' %>
<article class='bg-slate-50 border border-slate-300 max-w-md rounded-md'>
<div class='p-2 border-b border-y-slate-300 text-center'>
<h4 class='text-lg text-gray-700 font-semibold'>
<%= link_to insta_user.at_username, insta_user_posts_path(insta_user), class: 'hover:text-violet-700' %>
</h4>
</div>

<div class='p-2 border-b border-y-slate-300'>
<div class='text-center space-y-2'>
<div class='text-sm'>
<b><%= insta_user.media_count %></b>
posts detected
</div>

<div class='text-sm'>
<b><%= insta_user.insta_posts_count %></b>
posts imported
</div>

<%= button_to import_insta_user_path(insta_user), class: 'bg-violet-50 border border-dotted border-violet-300 rounded-md p-2 hover:bg-violet-100 text-violet-700' do %>
<i class="fa-solid fa-cloud-arrow-down text-violet-700"></i>
<span>Import posts</span>
<% end %>
<% if insta_user.last_import_at.present? %>
<span class='text-xs text-slate-500'>
Last import:
<%= insta_user.last_import_at.to_fs(:long) %>
</span>
<% end %>
</div>
</div>

<div class='p-2'>
<div>
<%= link_to 'Disconnect', '/instagram/deauthorize', class: 'text-blue-600 hover:text-red-600', target: '_blank', rel: 'noopener' %>
</div>
<div>
<%= link_to 'Delete all data', '/instagram/delete', class: 'text-blue-600 hover:text-red-600', target: '_blank', rel: 'noopener' %>
</div>
</div>
</article>
6 changes: 4 additions & 2 deletions app/views/insta_users/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
<h2>
🦄
<%= pluralize(@insta_users.length, 'person') %>
successfully converted their instas' to blogs:
successfully converted their instagram to a blog
</h2>

<% @insta_users.each do |insta_user| %>
<%= render partial: 'insta_users/insta_user', locals: { insta_user: } %>
<%= link_to insta_user.at_username,
insta_user_posts_path(insta_user),
class: 'flex rounded-md p-2 space-x-2 bg-slate-50 border border-slate-300 hover:bg-slate-100' %>
<% end %>
</section>
63 changes: 0 additions & 63 deletions app/views/insta_users/show.html.erb

This file was deleted.

1 change: 1 addition & 0 deletions app/views/passwordless/mailer/magic_link.text.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= I18n.t('passwordless.mailer.magic_link', link: @magic_link) %>
1 change: 1 addition & 0 deletions app/views/passwordless/sessions/create.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p><%= I18n.t('passwordless.sessions.create.email_sent_if_record_found') %></p>
22 changes: 22 additions & 0 deletions app/views/passwordless/sessions/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<div class='text-center space-y-4'>
<h1 class='font-semibold text-4xl'>
<i class="fa-solid fa-wand-magic-sparkles"></i>
<i class="fa-regular fa-envelope-open"></i>
</h1>
<h4 class='font-semibold text-lg'>
Enter your email to continue.
</h4>
<h4 class='font-semibold text-md'>
You will receive a magic link to log in.
</h4>
</div>

<%= form_with model: @session, url: send(Passwordless.mounted_as).sign_in_path, data: { turbo: 'false' }, class: 'text-center space-y-4' do |f| %>
<div>
<% email_field_name = :"passwordless[#{@email_field}]" %>
<%= text_field_tag email_field_name, params.fetch(email_field_name, nil), required: true, class: 'rounded-md w-full' %>
</div>
<div>
<%= f.submit I18n.t('passwordless.sessions.new.submit'), class: 'cursor-pointer w-full bg-violet-700 hover:bg-violet-900 text-fuchsia-50 p-2 rounded-md font-bold' %>
</div>
<% end %>
Loading

0 comments on commit c933291

Please sign in to comment.