Commit f105ea7d authored by Eugen Rochko's avatar Eugen Rochko

Multiple improvements:

- Use redis for caching
- Remove followers, only fetch friends
- Display top instances among friends
- Display "Friends" area after just Twitter sign-in
- Change design of results
parent 12716a45
......@@ -5,7 +5,7 @@ ruby '>= 2.3.0', '< 2.5.0'
gem 'rails', '~> 5.1.0'
gem 'pg', '~> 0.20'
gem 'puma', '~> 3.8'
gem 'puma', '~> 3.10'
gem 'sass-rails', '~> 5.0'
gem 'font-awesome-rails'
gem 'jquery-rails'
......@@ -14,22 +14,30 @@ gem 'uglifier'
gem 'bootsnap'
gem 'mastodon-api', require: 'mastodon'
gem 'twitter', git: 'https://github.com/sferik/twitter'
gem 'devise', '~> 4.2'
gem 'devise', '~> 4.3'
gem 'omniauth-twitter'
gem 'omniauth-mastodon', '>= 0.9.2'
gem 'hamlit-rails', '~> 0.2'
gem 'fast_blank', '~> 1.0'
gem 'dotenv-rails', '~> 2.2'
gem 'http'
gem 'httplog', '~> 0.99'
gem 'hiredis', '~> 0.6'
gem 'redis', '~> 3.3', require: ['redis', 'redis/connection/hiredis']
gem 'sidekiq', '~> 5.0'
gem 'redis-rails', '~> 5.0'
gem 'oj'
group :development, :test do
gem 'pry'
gem 'dotenv-rails', '~> 2.2'
gem 'pry-rails', '~> 0.3'
end
group :development do
gem 'listen', '~> 3.0.5'
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
gem 'better_errors', '~> 2.1'
gem 'better_errors', '~> 2.4'
gem 'binding_of_caller', '~> 0.7'
end
group :production do
......
......@@ -54,21 +54,27 @@ GEM
i18n (~> 0.7)
minitest (~> 5.1)
tzinfo (~> 1.1)
addressable (2.5.1)
public_suffix (~> 2.0, >= 2.0.2)
addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
arel (8.0.0)
bcrypt (3.1.11)
better_errors (2.2.0)
better_errors (2.4.0)
coderay (>= 1.0.0)
erubis (>= 2.6.6)
erubi (>= 1.0.0)
rack (>= 0.9.0)
binding_of_caller (0.7.3)
debug_inspector (>= 0.0.1)
bootsnap (1.1.1)
msgpack (~> 1.0)
buftok (0.2.0)
builder (3.2.3)
coderay (1.1.1)
coderay (1.1.2)
colorize (0.8.1)
concurrent-ruby (1.0.5)
devise (4.3.0)
connection_pool (2.2.1)
crass (1.0.3)
debug_inspector (0.0.3)
devise (4.4.0)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0, < 5.2)
......@@ -81,8 +87,7 @@ GEM
dotenv (= 2.2.1)
railties (>= 3.2, < 5.2)
equalizer (0.0.11)
erubi (1.6.1)
erubis (2.7.0)
erubi (1.7.0)
execjs (2.7.0)
faraday (0.12.2)
multipart-post (>= 1.2, < 3)
......@@ -102,6 +107,7 @@ GEM
hamlit (>= 1.2.0)
railties (>= 4.0.1)
hashie (3.5.6)
hiredis (0.6.1)
http (2.2.2)
addressable (~> 2.3)
http-cookie (~> 1.0)
......@@ -111,7 +117,11 @@ GEM
domain_name (~> 0.5)
http-form_data (1.0.3)
http_parser.rb (0.6.0)
i18n (0.8.6)
httplog (0.99.7)
colorize
rack
i18n (0.9.1)
concurrent-ruby (~> 1.0)
jquery-rails (4.3.1)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
......@@ -124,7 +134,8 @@ GEM
actionpack (>= 4, < 5.2)
activesupport (>= 4, < 5.2)
railties (>= 4, < 5.2)
loofah (2.0.3)
loofah (2.1.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.6.6)
mime-types (>= 1.16, < 4)
......@@ -137,16 +148,16 @@ GEM
mime-types (3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
mini_portile2 (2.2.0)
minitest (5.10.3)
mini_portile2 (2.3.0)
minitest (5.11.1)
msgpack (1.1.0)
multi_json (1.12.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
naught (1.1.0)
nio4r (2.1.0)
nokogiri (1.8.0)
mini_portile2 (~> 2.2.0)
nokogiri (1.8.1)
mini_portile2 (~> 2.3.0)
oauth (0.5.3)
oauth2 (1.4.0)
faraday (>= 0.8, < 0.13)
......@@ -154,6 +165,7 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
oj (3.3.10)
omniauth (1.6.1)
hashie (>= 3.4.6, < 3.6.0)
rack (>= 1.6.2, < 3)
......@@ -171,13 +183,16 @@ GEM
rack
orm_adapter (0.5.0)
pg (0.21.0)
pry (0.10.4)
pry (0.11.0)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
public_suffix (2.0.5)
puma (3.9.1)
pry-rails (0.3.6)
pry (>= 0.10.4)
public_suffix (3.0.1)
puma (3.11.0)
rack (2.0.3)
rack-protection (2.0.0)
rack
rack-test (0.6.3)
rack (>= 1.0)
rails (5.1.2)
......@@ -208,10 +223,27 @@ GEM
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rake (12.0.0)
rake (12.3.0)
rb-fsevent (0.10.2)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
redis (3.3.5)
redis-actionpack (5.0.2)
actionpack (>= 4.0, < 6)
redis-rack (>= 1, < 3)
redis-store (>= 1.1.0, < 2)
redis-activesupport (5.0.4)
activesupport (>= 3, < 6)
redis-store (>= 1.3, < 2)
redis-rack (2.0.4)
rack (>= 1.5, < 3)
redis-store (>= 1.2, < 2)
redis-rails (5.0.2)
redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2)
redis-store (1.4.1)
redis (>= 2.2, < 5)
responders (2.4.0)
actionpack (>= 4.2.0, < 5.3)
railties (>= 4.2.0, < 5.3)
......@@ -226,8 +258,12 @@ GEM
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
sidekiq (5.0.5)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0)
redis (>= 3.3.4, < 5)
simple_oauth (0.3.1)
slop (3.6.0)
spring (2.0.2)
activesupport (>= 4.2)
spring-watcher-listen (2.0.1)
......@@ -241,10 +277,10 @@ GEM
activesupport (>= 4.0)
sprockets (>= 3.0.0)
temple (0.8.0)
thor (0.19.4)
thor (0.20.0)
thread_safe (0.3.6)
tilt (2.0.8)
tzinfo (1.2.3)
tzinfo (1.2.4)
thread_safe (~> 0.1)
uglifier (3.2.0)
execjs (>= 0.3.0, < 3)
......@@ -261,25 +297,33 @@ PLATFORMS
ruby
DEPENDENCIES
better_errors (~> 2.1)
better_errors (~> 2.4)
binding_of_caller (~> 0.7)
bootsnap
devise (~> 4.2)
devise (~> 4.3)
dotenv-rails (~> 2.2)
fast_blank (~> 1.0)
font-awesome-rails
hamlit-rails (~> 0.2)
hiredis (~> 0.6)
http
httplog (~> 0.99)
jquery-rails
listen (~> 3.0.5)
lograge (~> 0.5)
mastodon-api
oj
omniauth-mastodon (>= 0.9.2)
omniauth-twitter
pg (~> 0.20)
pry
puma (~> 3.8)
pry-rails (~> 0.3)
puma (~> 3.10)
rails (~> 5.1.0)
rails_12factor
redis (~> 3.3)
redis-rails (~> 5.0)
sass-rails (~> 5.0)
sidekiq (~> 5.0)
spring
spring-watcher-listen (~> 2.0.0)
twitter!
......@@ -289,4 +333,4 @@ RUBY VERSION
ruby 2.4.1p111
BUNDLED WITH
1.15.3
1.16.1
web: rails s
......@@ -309,6 +309,7 @@ h4 {
background: darken($darkest, 4%);
padding: 20px;
margin-bottom: 30px;
border-radius: 10px;
h3 {
font-family: 'Montserrat', sans-serif;
......@@ -380,6 +381,8 @@ h4 {
color: #fff;
display: flex;
text-decoration: none;
overflow: hidden;
border-radius: 10px;
&:first-child {
margin-right: 10px;
......@@ -430,3 +433,90 @@ h4 {
background: lighten(#3088d4, 4%);
}
}
.grid {
width: 100%;
display: flex;
flex-wrap: wrap;
margin-bottom: 20px;
}
.instance-card {
display: block;
text-decoration: none;
color: inherit;
border-radius: 10px;
background-size: cover;
background-position: center;
padding-top: 110px;
margin-bottom: 20px;
flex: 1 1 auto;
max-width: 300px;
.info {
background: linear-gradient(0deg, rgba(#000, 0.8) 0, rgba(#000, 0.35) 60%, transparent);
padding: 10px;
padding-top: 40px;
border-radius: 0 0 10px 10px;
}
.title {
font-weight: 500;
color: $lightest;
display: block;
}
&:hover,
&:focus,
&:active {
color: $lightest;
.title {
color: #fff;
}
}
}
.user-card {
display: block;
text-decoration: none;
color: inherit;
display: flex;
align-items: center;
flex: 1 1 auto;
max-width: 300px;
margin-bottom: 20px;
.avatar {
width: 60px;
height: 60px;
img {
display: block;
margin: 0;
width: 100%;
height: 100%;
border-radius: 50%;
}
}
.name {
margin-left: 10px;
}
.display-name {
font-weight: 500;
color: $lightest;
display: block;
}
&:hover,
&:focus,
&:active {
color: $lightest;
.display-name {
color: #fff;
}
}
}
......@@ -4,7 +4,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
def after_sign_in_path_for(user)
if user.twitter.nil? || user.mastodon.nil?
if user.twitter.nil?
root_path
else
friends_path
......
......@@ -4,44 +4,34 @@ require 'twitter'
class FriendsController < ApplicationController
before_action :authenticate_user!
before_action :set_friends, only: :index
before_action :set_top_instances, only: :index
rescue_from Twitter::Error do |e|
redirect_to root_path, alert: "Twitter error: #{e}"
end
def index
@tweet_text = URI.encode("I am #{current_user.mastodon.try(:uid)} on Mastodon! Find your Twitter friends in the fediverse")
fetch_twitter_followees
fetch_twitter_followers
fetch_related_mastodons
end
def follow
user = User.find(params[:id])
mastodon_uid = user.authorizations.find_by(provider: :mastodon).uid
mastodon_client.follow_by_uri(mastodon_uid)
redirect_to friends_path, notice: "Successfully followed #{mastodon_uid} from your Mastodon account"
rescue Mastodon::Error::Unauthorized
redirect_to friends_path, alert: 'The access token for your Mastodon account has expired or was revoked'
end
def index; end
private
def fetch_twitter_followees
@twitter_friend_ids = Rails.cache.fetch("#{current_user.id}/twitter-friends", expires_in: 15.minutes) { twitter_client.friend_ids }
def set_friends
@friends = User.where(id: Authorization.where(provider: :twitter, uid: twitter_friend_ids).map(&:user_id))
.includes(:authorizations)
.reject { |user| user.mastodon.nil? }
end
def fetch_twitter_followers
@twitter_follower_ids = Rails.cache.fetch("#{current_user.id}/twitter-followers", expires_in: 15.minutes) { twitter_client.follower_ids }
def set_top_instances
@top_instances = @friends.collect { |user| user&.mastodon&.uid }
.compact
.map { |uid| uid.split('@').last }
.inject(Hash.new(0)) { |h, k| h[k] += 1; h }
.sort_by { |k, v| v }
.map { |k, _| Rails.cache.fetch("instance:#{k}", expires_in: 1.week) { Oj.load(HTTP.get("https://#{k}/api/v1/instance").to_s) } }
end
def fetch_related_mastodons
found_ids1 = Authorization.where(provider: :twitter, uid: @twitter_friend_ids.to_a)
found_ids2 = Authorization.where(provider: :twitter, uid: @twitter_follower_ids.to_a)
@friends = User.where(id: found_ids1.map(&:user_id)).includes(:authorizations)
@followers = User.where(id: found_ids2.map(&:user_id)).includes(:authorizations)
def twitter_friend_ids
Rails.cache.fetch("#{current_user.id}/twitter-friends", expires_in: 15.minutes) { twitter_client.friend_ids.to_a }
end
def twitter_client
......
class ApplicationJob < ActiveJob::Base
end
......@@ -4,4 +4,17 @@ class Authorization < ApplicationRecord
belongs_to :user, inverse_of: :authorizations, required: true
default_scope { order('id asc') }
def info
return @info if defined?(@info)
if provider == 'mastodon'
@info = Rails.cache.fetch("mastodon-user:#{uid}", expires_in: 1.day) do
client = Mastodon::REST::Client.new(base_url: "https://#{uid.split('@').last}", bearer_token: token)
client.verify_credentials.attributes
end
else
@info = nil
end
end
end
.page-heading
%h3
Your friends
%small Here are the results
%p.lead
Are your friends not showing up in the table? Tell them to create a Mastodon account on any instance and then sign in on this website using both Twitter and Mastodon.
%p
%a.twitter-share-button{"data-size" => "large", :href => "https://twitter.com/intent/tweet?text=#{@tweet_text}"} Tweet
%hr/
%h4 You follow
= render 'table', collection: @friends
%hr/
%h4 Followers
= render 'table', collection: @followers
:javascript
window.twttr = (function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0],
t = window.twttr || {};
if (d.getElementById(id)) return t;
js = d.createElement(s);
js.id = id;
js.src = "https://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js, fjs);
t._e = [];
t.ready = function(f) {
t._e.push(f);
};
return t;
}(document, "script", "twitter-wjs"));
%small Here are your Twitter friends who are on Mastodon
.grid
- @top_instances.each do |instance_info|
= link_to "https://#{instance_info['uri']}/about", class: 'instance-card', style: "background-image: url(#{instance_info['thumbnail']})" do
.info
%span.title= instance_info['title']
%span.uri= instance_info['uri']
%span.users
= surround '(', ')' do
= number_with_delimiter instance_info['stats']['user_count']
= 'person'.pluralize(instance_info['stats']['user_count'])
.grid
- @friends.each do |user|
= link_to user.mastodon.info['url'], class: 'user-card' do
.avatar= image_tag user.mastodon.info['avatar']
.name
%span.display-name= user.mastodon.info['display_name'].presence || user.mastodon.info['username']
%span.username= "@#{user.mastodon.uid}"
.page-heading
%h3
Find your friends
%small Login via Twitter and Mastodon to get started
%small Login via Twitter to get started
.login-buttons
= content_tag twitter? ? :div : :a, href: user_twitter_omniauth_authorize_path, class: 'twitter' do
......@@ -31,4 +31,4 @@
%span.login-btn Connect Mastodon
%p
This website uses a database of Twitter users and Mastodon users who signed in here to match them together across multiple Mastodon instances. Are your friends or followers among them?
This website uses a database of Twitter users and Mastodon users who signed in here to match them together across multiple Mastodon instances. Are your friends among them?
......@@ -16,7 +16,7 @@
= image_tag 'logo_full.svg', alt: 'Mastodon Bridge'
%ul.right
- if twitter? && mastodon?
- if twitter?
%li= link_to 'Friends', friends_path
- if user_signed_in?
......
......@@ -21,5 +21,7 @@ module MastodonBridge
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
config.active_job.queue_adapter = :sidekiq
end
end
......@@ -16,7 +16,7 @@ Rails.application.configure do
if Rails.root.join('tmp/caching-dev.txt').exist?
config.action_controller.perform_caching = true
config.cache_store = :memory_store
config.cache_store = :redis_store, ENV['REDIS_URL']
config.public_file_server.headers = {
'Cache-Control' => 'public, max-age=172800'
}
......
......@@ -46,7 +46,7 @@ Rails.application.configure do
config.log_tags = [ :request_id ]
# Use a different cache store in production.
# config.cache_store = :mem_cache_store
config.cache_store = :redis_store, ENV['REDIS_URL']
# Use a real queuing backend for Active Job (and separate queues per environment)
# config.active_job.queue_adapter = :resque
......
HttpLog.configure do |config|
config.logger = Rails.logger
config.color = { color: :yellow }
config.compact_log = true
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment