aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorre4k <re4k@re4k.info>2013-03-29 23:00:11 +0900
committerre4k <re4k@re4k.info>2013-03-29 23:00:11 +0900
commitb1cd0b45b761132e47cbc01e99acd728f047b9ae (patch)
tree9bedd48e7a5c53056721fde3987b7fb27499fbc6
parent61087e2460e60b08b554b600cdebc6efb2456dd6 (diff)
downloadaclog-b1cd0b45b761132e47cbc01e99acd728f047b9ae.tar.gz
Refactor
Add search (partial)
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock6
-rw-r--r--app/assets/javascripts/errors.js.coffee3
-rw-r--r--app/assets/javascripts/i.js.coffee3
-rw-r--r--app/assets/javascripts/main.js.coffee3
-rw-r--r--app/assets/javascripts/sessions.js.coffee3
-rw-r--r--app/assets/javascripts/users.js.coffee3
-rw-r--r--app/assets/stylesheets/_sidebar.css.sass15
-rw-r--r--app/controllers/application_controller.rb8
-rw-r--r--app/controllers/search_controller.rb85
-rw-r--r--app/controllers/sessions_controller.rb4
-rw-r--r--app/controllers/users_controller.rb13
-rw-r--r--app/helpers/application_helper.rb24
-rw-r--r--app/helpers/search_helper.rb2
-rw-r--r--app/models/account.rb15
-rw-r--r--app/models/tweet.rb9
-rw-r--r--app/models/user.rb70
-rw-r--r--app/views/layouts/_base.html.haml6
-rw-r--r--app/views/main/api.html.haml7
-rw-r--r--app/views/main/index.html.haml18
-rw-r--r--app/views/shared/_search.html.haml4
-rw-r--r--app/views/shared/_sidebar_search.html.haml12
-rw-r--r--app/views/shared/_sidebar_users.html.haml31
-rw-r--r--app/views/shared/_tweet.html.haml11
-rw-r--r--app/views/shared/tweets.html.haml2
-rw-r--r--app/views/shared/users.html.haml8
-rw-r--r--app/views/users/info.html.haml28
-rw-r--r--client/worker.rb11
-rw-r--r--config/initializers/session_store.rb2
-rw-r--r--config/routes.rb3
-rw-r--r--public/assets/application-252e3d8b1032ad10b8a6615bb347aad0.js57
-rw-r--r--public/assets/application-252e3d8b1032ad10b8a6615bb347aad0.js.gzbin0 -> 925 bytes
-rw-r--r--public/assets/application-38ff4e9e27d0ec3d917e7d43fcec9197.css (renamed from public/assets/application-eb873c58ea6dc20a9ecb48c9bd070d3c.css)37
-rw-r--r--public/assets/application-38ff4e9e27d0ec3d917e7d43fcec9197.css.gz (renamed from public/assets/application-eb873c58ea6dc20a9ecb48c9bd070d3c.css.gz)bin19349 -> 19466 bytes
-rw-r--r--public/assets/application-c0feb24db0871514682a56320c7b2b2b.js34
-rw-r--r--public/assets/application-c0feb24db0871514682a56320c7b2b2b.js.gzbin410 -> 0 bytes
-rw-r--r--public/assets/favorite-f705dbd122eeb6b0a17f75306600f33c.pngbin0 -> 1329 bytes
-rw-r--r--public/assets/manifest-48ac099aa1a004ba8c6f9a787cc5fe11.json1
-rw-r--r--public/assets/manifest-b6e16b8d09a3fc9041a0a623c7b857af.json1
-rw-r--r--public/assets/protected_profile_image-d5d708680693a18615dfb8f5b1a5a96c.pngbin0 -> 9350 bytes
-rw-r--r--public/assets/reply-aded44c637c7902c47d64921125bec96.pngbin0 -> 1153 bytes
-rw-r--r--public/assets/retweet-d214a384fd3a031f48046b77e5c15d8d.pngbin0 -> 1095 bytes
-rw-r--r--test/controllers/search_controller_test.rb7
-rw-r--r--test/helpers/search_helper_test.rb4
44 files changed, 412 insertions, 139 deletions
diff --git a/Gemfile b/Gemfile
index 896d68e..56d1ccd 100644
--- a/Gemfile
+++ b/Gemfile
@@ -6,6 +6,7 @@ gem 'mysql2'
gem 'dalli'
gem 'unicorn'
+gem 'thin'
gem 'daemon-spawn', :require => 'daemon_spawn'
gem 'rails_config'
diff --git a/Gemfile.lock b/Gemfile.lock
index b8ad380..0ff8dbb 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -46,6 +46,7 @@ GEM
execjs
coffee-script-source (1.6.2)
daemon-spawn (0.4.2)
+ daemons (1.1.9)
dalli (2.6.2)
em-twitter (0.2.1)
eventmachine (~> 1.0)
@@ -134,6 +135,10 @@ GEM
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (~> 2.8)
+ thin (1.5.1)
+ daemons (>= 1.0.9)
+ eventmachine (>= 0.12.6)
+ rack (>= 1.0.0)
thor (0.17.0)
thread_safe (0.1.0)
atomic
@@ -173,6 +178,7 @@ DEPENDENCIES
rails (= 4.0.0.beta1)
rails_config
sass-rails (~> 4.0.0.beta1)
+ thin
twitter
uglifier (>= 1.0.3)
unicorn
diff --git a/app/assets/javascripts/errors.js.coffee b/app/assets/javascripts/errors.js.coffee
deleted file mode 100644
index 24f83d1..0000000
--- a/app/assets/javascripts/errors.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/javascripts/i.js.coffee b/app/assets/javascripts/i.js.coffee
deleted file mode 100644
index 7615679..0000000
--- a/app/assets/javascripts/i.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/app/assets/javascripts/main.js.coffee b/app/assets/javascripts/main.js.coffee
deleted file mode 100644
index 7615679..0000000
--- a/app/assets/javascripts/main.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/app/assets/javascripts/sessions.js.coffee b/app/assets/javascripts/sessions.js.coffee
deleted file mode 100644
index 7615679..0000000
--- a/app/assets/javascripts/sessions.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/app/assets/javascripts/users.js.coffee b/app/assets/javascripts/users.js.coffee
deleted file mode 100644
index 7615679..0000000
--- a/app/assets/javascripts/users.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/app/assets/stylesheets/_sidebar.css.sass b/app/assets/stylesheets/_sidebar.css.sass
index a61fd50..5734004 100644
--- a/app/assets/stylesheets/_sidebar.css.sass
+++ b/app/assets/stylesheets/_sidebar.css.sass
@@ -7,6 +7,15 @@
.avatar
:text-align center
.records
- .data
- :text-align right
-
+ :list-style none
+ :margin 0 0 20px
+ li
+ @include clearfix
+ :border-top 1px solid #dddddd
+ span
+ :display block
+ :float left
+ :line-height 20px
+ :padding 4px 5px
+ .data
+ :float right
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index c65c21c..ef1106f 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,7 +1,8 @@
+# -*- coding: utf-8 -*-
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :set_format, :get_include_user, :get_include_user_stats
- after_filter :set_content_type
+ after_filter :xhtml
def set_format
unless request.format == :json || request.format == :html
@@ -9,9 +10,12 @@ class ApplicationController < ActionController::Base
end
end
- def set_content_type
+ def xhtml
if request.format == :html
response.content_type = "application/xhtml+xml"
+
+ # remove invalid charactors
+ response.body = response.body.gsub(/[\x0-\x8\xb\xc\xe-\x1f]/, "")
end
end
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
new file mode 100644
index 0000000..b5933d7
--- /dev/null
+++ b/app/controllers/search_controller.rb
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+require "shellwords"
+
+class SearchController < ApplicationController
+ def search
+ @show_search = true
+ # TODO: OR とか () とか対応したいよね
+ unless params[:query]
+ render_tweets(Tweet.where(:id => -1))
+ return
+ end
+ p words = Shellwords.shellwords(params[:query])
+
+ result = words.inject(Tweet.order_by_id) do |tweets, word|
+ case word
+ when /^-?[a-z]+:.+$/
+ # 特殊
+ key, value = word.split(":", 2)
+ case key.downcase
+ when /^user$/
+ user = User.cached(value)
+ tweets.where(:user_id => user ? user.id : -1)
+ when /^-?fav/
+ search_unless_zero(tweets, "favorites_count", key[0], value)
+ when /^-?re?t/
+ search_unless_zero(tweets, "retweets_count", key[0], value)
+ when /^-?reactions?$/
+ search_unless_zero(tweets, "favorites_count + retweets_count", key[0], value)
+ when "order"
+ case value
+ when "old", /^asc/
+ tweets.order("id ASC")
+ when "reaction"
+ tweets.order_by_reactions
+ when /^fav/
+ tweets.order_by_favorites
+ when /^re?t/
+ tweets.order_by_retweets
+ else
+ tweets
+ end
+ when "text"
+ sourcetext = word.split(":", 2).last.gsub("%", "\\%").gsub("*", "%")
+ sourcetext = "%#{sourcetext}%".gsub(/%+/, "%")
+ op = key[0] == "-" ? " NOT LIKE " : " LIKE "
+ tweets.where("text #{op} ?", sourcetext)
+ when /^-?source$/
+ sourcetext = word.split(":", 2).last.gsub("%", "\\%").gsub("*", "%")
+ op = key[0] == "-" ? " NOT LIKE " : " LIKE "
+ tweets.where("source #{op} ? OR source #{op} ?", "<url:%:#{sourcetext.gsub(":", "%3A")}>", "<url:%:#{CGI.escape(sourcetext)}>")
+ else
+ # unknown command
+ tweets
+ end
+ when /^20[0-9]{6}\.\.20[0-9]{6}$/
+ since, to = word.split(/\.\./)
+ since = Time.utc(since[0...4].to_i, since[4...6].to_i, since[6...8].to_i) - 9 * 60 * 60
+ to = Time.utc(to[0...4].to_i, to[4...6].to_i, to[6...8].to_i + 1) - 9 * 60 * 60
+
+ tweets.where(:id => first_id_of_time(since)...first_id_of_time(to))
+ else
+ # TODO: ツイート検索
+ tweets
+ end
+ end
+
+ render_tweets(result)
+ end
+
+ private
+ def first_id_of_time(time)
+ p (time.to_i * 1000 - 1288834974657) << 22
+ end
+
+ def search_unless_zero(tweets, column, flag, value)
+ num = Integer(value) rescue 0
+ n = flag == "-"
+
+ unless num == 0
+ tweets.where("#{column}#{n ? "<" : ">="} ?", num)
+ else
+ tweets
+ end
+ end
+end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index cc0a9a6..d16175a 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -3,13 +3,15 @@ require "socket"
class SessionsController < ApplicationController
def callback
auth = request.env["omniauth.auth"]
+
account = Account.find_or_initialize_by(:user_id => auth["uid"])
account.oauth_token = auth["credentials"]["token"]
account.oauth_token_secret = auth["credentials"]["secret"]
account.consumer_version = Settings.consumer_version
account.save!
+
+ session[:account] = account
session[:user_id] = account.user_id
- session[:screen_name] = auth["info"]["nickname"]
begin
UNIXSocket.open(Settings.register_server_path) do |socket|
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 0e82b89..e8ce485 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -58,16 +58,12 @@ class UsersController < ApplicationController
end
def info
- account = Account.where(:user_id => @user.id).first
- raise Aclog::Exceptions::UserNotRegistered unless account
+ raise Aclog::Exceptions::UserNotRegistered unless @user.account
@title = "@#{@user.screen_name} (#{@user.name})'s Profile"
-
respond_to do |format|
- format.html do
- @twitter_user = account.twitter_user
- end
+ format.html
format.json do
@include_user_stats = true
end
@@ -170,6 +166,7 @@ class UsersController < ApplicationController
.order_by_id
.limit(500)
.map{|e| Tweet.cached(e.tweet_id)}
+ .compact
.inject(Hash.new(0)){|hash, tweet| hash[tweet.user_id] += 1; hash}
.sort_by{|user_id, count| -count}
@@ -186,11 +183,13 @@ class UsersController < ApplicationController
end
if params[:user_id]
+ #@user = User.cached(params[:user_id].to_i)
@user = User.cached(params[:user_id].to_i)
end
if !@user && params[:screen_name]
- @user = User.where(:screen_name => params[:screen_name]).first
+ #@user = User.where(:screen_name => params[:screen_name]).first
+ @user = User.cached(params[:screen_name])
end
raise Aclog::Exceptions::UserNotFound unless @user
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index a41f15d..7b41dcc 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,10 +1,14 @@
require "time"
module ApplicationHelper
- def format_tweet_created_at(dt)
+ def format_time(dt)
dt.to_time.localtime("+09:00").strftime("%Y-%m-%d %H:%M:%S")
end
+ def format_date_ago(dt)
+ "#{(DateTime.now.utc - dt.to_datetime).to_i}d ago"
+ end
+
def format_tweet_text(text)
text
.gsub(/<url:(.+?):(.+?)>/){link_to(CGI.unescape($2), CGI.unescape($1), :target => "_blank")}
@@ -21,4 +25,22 @@ module ApplicationHelper
def twitter_status_url(tweet)
"https://twitter.com/#{tweet.user.screen_name}/status/#{tweet.id}"
end
+
+ def twitter_user_url(screen_name)
+ "https://twitter.com/#{screen_name}"
+ end
+
+ def link_to_user_page(screen_name, &blk)
+ if block_given?
+ body = capture(&blk)
+ end
+
+ body ||= "@#{screen_name}"
+ link_to(body, :controller => "users", :action => "best", :screen_name => screen_name)
+ end
+
+ # utf8
+ def utf8_enforcer_tag
+ ""
+ end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
new file mode 100644
index 0000000..b3ce20a
--- /dev/null
+++ b/app/helpers/search_helper.rb
@@ -0,0 +1,2 @@
+module SearchHelper
+end
diff --git a/app/models/account.rb b/app/models/account.rb
index 14b9da9..bd655db 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -5,7 +5,7 @@ class Account < ActiveRecord::Base
def twitter_user
Rails.cache.fetch("twitter_user/#{user_id}", :expires_in => 1.hour) do
- client.user(user_id)
+ client.user(user_id) rescue nil
end
end
@@ -16,4 +16,17 @@ class Account < ActiveRecord::Base
:oauth_token => oauth_token,
:oauth_token_secret => oauth_token_secret)
end
+
+ def stats_api
+ return {} unless twitter_user
+ {
+ favorites_count: twitter_user.favourites_count,
+ listed_count: twitter_user.listed_count,
+ followers_count: twitter_user.followers_count,
+ tweets_count: twitter_user.statuses_count,
+ friends_count: twitter_user.friends_count,
+ listed_count: twitter_user.listed_count,
+ bio: twitter_user.description
+ }
+ end
end
diff --git a/app/models/tweet.rb b/app/models/tweet.rb
index d30ff4e..3df7c3c 100644
--- a/app/models/tweet.rb
+++ b/app/models/tweet.rb
@@ -36,11 +36,10 @@ class Tweet < ActiveRecord::Base
end
scope :discovered_by, -> user do
- where("id IN (" +
- "SELECT tweet_id FROM favorites WHERE user_id = ?" +
- " UNION ALL " +
- "SELECT tweet_id FROM retweets WHERE user_id = ?" +
- ")", user.id, user.id)
+ where("id IN (SELECT tweet_id FROM favorites WHERE user_id = ?)" +
+ " OR " +
+ "id IN (SELECT tweet_id FROM retweets WHERE user_id = ?)",
+ user.id, user.id)
end
def self.cached(id)
diff --git a/app/models/user.rb b/app/models/user.rb
index 9598236..58597cc 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,38 +1,70 @@
class User < ActiveRecord::Base
- def protected?
- protected
- end
-
has_many :tweets, :dependent => :delete_all
has_many :favorites, :dependent => :delete_all
has_many :retweets, :dependent => :delete_all
- def self.cached(uid)
- Rails.cache.fetch("user/#{uid}", :expires_in => 1.hour) do
- where(:id => uid).first
+ def self.cached(identity)
+ if identity.is_a?(Fixnum) || identity.is_a?(Bignum)
+ Rails.cache.fetch("user/#{identity}", :expires_in => 3.hour) do
+ where(:id => identity).first
+ end
+ elsif identity.is_a?(String)
+ if /^[A-Za-z0-9_]{1,15}$/ =~ identity
+ uid = Rails.cache.fetch("screen_name/#{identity}", :expires_in => 3.hour) do
+ if user = where(:screen_name => identity).first
+ user.id
+ end
+ end
+
+ cached(uid)
+ end
+ elsif identity.is_a?(NilClass)
+ nil
+ else
+ raise Exception, "Invalid identity: #{identity}"
end
end
+ def protected?
+ protected
+ end
+
def registered?
- Account.exists?(:user_id => id)
+ account
+ end
+
+ def account
+ Account.where(:user_id => id).first
end
def profile_image_url_original
profile_image_url.sub(/_normal((\.(png|jpeg|gif))?)/, "\\1")
end
- def stats
- return @stats_cache if @stats_cache
-
- hash = tweets.inject(Hash.new(0)) do |hash, m|
- hash[:favorited_count] += m.favorites_count
- hash[:retweeted_count] += m.retweets_count
- hash
+ def twitter_user
+ if registered?
+ account.twitter_user
+ else
+ raise Exception, "why??"
end
- hash[:favorites_count] = favorites.count
- hash[:retweets_count] = retweets.count
- hash[:tweets_count] = tweets.count
+ end
+
+ def stats
+ @stats_cache ||= ->{
+ raise Aclog::Exceptions::UserNotRegistered unless account
- @stats_cache = hash
+ tweets.inject(
+ {favorites_count: favorites.count,
+ retweets_count: retweets.count,
+ tweets_count: tweets.length, # cache: tweets.inject calls "SELECT `tweets`.*"
+ favorited_count: 0,
+ retweeted_count: 0,
+ stats_api: account.stats_api}
+ ) do |hash, m|
+ hash[:favorited_count] += m.favorites_count
+ hash[:retweeted_count] += m.retweets_count
+ hash
+ end
+ }.call
end
end
diff --git a/app/views/layouts/_base.html.haml b/app/views/layouts/_base.html.haml
index 8997bd5..e3cee24 100644
--- a/app/views/layouts/_base.html.haml
+++ b/app/views/layouts/_base.html.haml
@@ -12,10 +12,12 @@
= link_to "aclog", :controller => "main", :action => "index"
%ul.nav.pull-right
%li
+ = link_to "search", :controller => "search", :action => "search"
+ %li
= link_to "about", :controller => "main", :action => "about"
- - if session[:screen_name]
+ - if session[:user_id]
%li= link_to "logout", :controller => "sessions", :action => "destroy"
- %li= link_to "@#{session[:screen_name]}", :controller => "users", :action => "best", :screen_name => session[:screen_name]
+ %li= link_to_user_page session[:account].user.screen_name
- else
%li= link_to "login", "/i/login"
.container
diff --git a/app/views/main/api.html.haml b/app/views/main/api.html.haml
index 8d5adef..8a42042 100644
--- a/app/views/main/api.html.haml
+++ b/app/views/main/api.html.haml
@@ -1,6 +1,8 @@
%h1 API
%p
ドキュメントは今度もっとわかりやすく書き直しますね
+%p
+ aclog のweb版で見れるものすべて JSON で取れるようにしたい
%h3 URL
%p
全部 GET です
@@ -45,11 +47,6 @@
%p 1-
%p default: 1
%p リストのみ。ページ
- %dt all
- %dd
- %p true or false
- %p default: false
- %p timeline のみ。反応がなかったツイートも含めるかどうか
%dt tweets
%dd
%p favorite or retweet or all
diff --git a/app/views/main/index.html.haml b/app/views/main/index.html.haml
index a0ab627..c5b5446 100644
--- a/app/views/main/index.html.haml
+++ b/app/views/main/index.html.haml
@@ -33,4 +33,20 @@
%dd= link_to "/i/timeline", "/i/timeline"
%p
Favstar のパスにアクセスされた場合リダイレクトするようになっているはず…
-
+%h3 検索クエリ
+半角スペース区切りで、すべて AND 検索になります。
+%dl
+ %dt ユーザーの
+ %dt user:sg4k
+ %dt 反応数 〜〜以上
+ %dd reaction:30
+ %dd favorite:30
+ %dd retweet:30
+ %dt 並び替え
+ %dd order:old
+ %dd order:reaction
+ %dd order:favorite
+ %dd order:retweet
+ %dt 期間
+ %dd 20130328..20130330
+ %dt ツイート本文の検索はできません。付けるかはわかんない
diff --git a/app/views/shared/_search.html.haml b/app/views/shared/_search.html.haml
new file mode 100644
index 0000000..2eeb390
--- /dev/null
+++ b/app/views/shared/_search.html.haml
@@ -0,0 +1,4 @@
+.search
+ = form_tag({:controller => "search", :action => "search"}, {:method => :get}) do
+ = text_field_tag :query, params[:query]
+ = submit_tag "search", :name => nil
diff --git a/app/views/shared/_sidebar_search.html.haml b/app/views/shared/_sidebar_search.html.haml
new file mode 100644
index 0000000..098b35c
--- /dev/null
+++ b/app/views/shared/_sidebar_search.html.haml
@@ -0,0 +1,12 @@
+%ul.nav.nav-tabs.nav-stacked
+ %li
+ = link_to "about", :controller => "main", :action => "about"
+ %li
+ = link_to "api", :controller => "main", :action => "api"
+%ul.nav.nav-tabs.nav-stacked
+ %li
+ = link_to "best", :controller => "i", :action => "best"
+ %li
+ = link_to "recent", :controller => "i", :action => "recent"
+ %li
+ = link_to "timeline", :controller => "i", :action => "timeline"
diff --git a/app/views/shared/_sidebar_users.html.haml b/app/views/shared/_sidebar_users.html.haml
index bfd4b4d..05e2377 100644
--- a/app/views/shared/_sidebar_users.html.haml
+++ b/app/views/shared/_sidebar_users.html.haml
@@ -1,17 +1,22 @@
.sidebar
- .avatar= link_to (image_tag @user.profile_image_url_original, :alt => @user.screen_name, :width => 64, :height => 64, :class => "icon img-rounded"), :controller => "users", :action => "best", :screen_name => @user.screen_name
- .screen_name= link_to "@#{@user.screen_name}", :controller => "users", :action => "best", :screen_name => @user.screen_name
+ .avatar
+ = link_to_user_page @user.screen_name do
+ = image_tag @user.profile_image_url_original, :alt => @user.screen_name, :width => 64, :height => 64, :class => "icon img-rounded"
+ .screen_name= link_to @user.screen_name, twitter_user_url(@user.screen_name)
- if @user.registered?
- %table.table.table-condensed.records
- %tr
- %td favorited
- %td.data= @user.stats[:favorited_count]
- %tr
- %td retweeted
- %td.data= @user.stats[:retweeted_count]
- %tr
- %td avg. fav
- %td.data= ((@user.stats[:favorited_count] + 0.0) / @user.stats[:tweets_count]).round(2)
+ %ul.records
+ %li
+ %span favorited
+ %span.data= @user.stats[:favorited_count]
+ %li
+ %span retweeted
+ %span.data= @user.stats[:retweeted_count]
+ %li
+ %span avg. fav
+ %span.data= ((@user.stats[:favorited_count] + 0.0) / @user.stats[:tweets_count]).round(2)
+ %li
+ %span joined
+ %span.data= format_date_ago(@user.created_at)
- else
.alert.alert-info
= "@#{@user.screen_name} has never signed in to aclog"
@@ -23,8 +28,6 @@
%li
= link_to "best", :controller => "users", :action => "best", :screen_name => @user.screen_name
%li
- = link_to "recent", :controller => "users", :action => "recent", :screen_name => @user.screen_name
- %li
= link_to "timeline", :controller => "users", :action => "timeline", :screen_name => @user.screen_name
%li
= link_to "discovered", :controller => "users", :action => "discovered", :screen_name => @user.screen_name
diff --git a/app/views/shared/_tweet.html.haml b/app/views/shared/_tweet.html.haml
index 97408e5..6c822eb 100644
--- a/app/views/shared/_tweet.html.haml
+++ b/app/views/shared/_tweet.html.haml
@@ -12,16 +12,16 @@
.tweet_content
.user
%span.name
- = link_to item.user.name, :controller => "users", :action => "best", :screen_name => item.user.screen_name
- %span.screen_name
- = link_to "@#{item.user.screen_name}", :controller => "users", :action => "best", :screen_name => item.user.screen_name
+ = link_to_user_page item.user.screen_name do
+ = item.user.name
+ %span.screen_name= link_to_user_page item.user.screen_name
.text
= raw format_tweet_text(item.text)
.meta.clearfix
%span.twitter_bird
= link_to image_tag("bird_gray_16.png", :alt => "Twitter"), twitter_status_url(item), :target => "_blank"
%span.created_at
- = link_to format_tweet_created_at(item.tweeted_at), :controller => "users", :action => "show", :id => item.id
+ = link_to format_time(item.tweeted_at), :controller => "users", :action => "show", :id => item.id
%span.source
= raw format_source_text(item.source)
.stats
@@ -36,6 +36,7 @@
- m = a.user
%li
- if m
- = link_to image_tag(m.profile_image_url, :alt => m.screen_name, :title => m.name), :controller => "users", :action => "best", :screen_name => m.screen_name
+ = link_to_user_page m.screen_name do
+ = image_tag m.profile_image_url, :alt => m.screen_name, :title => m.name
- else
= image_tag asset_path("missing_profile_image.png"), :alt => "Missing User: #{a.user_id}", :title => "Missing User: #{a.user_id}"
diff --git a/app/views/shared/tweets.html.haml b/app/views/shared/tweets.html.haml
index f6a3f12..6ceb720 100644
--- a/app/views/shared/tweets.html.haml
+++ b/app/views/shared/tweets.html.haml
@@ -1,3 +1,5 @@
+- if @show_search
+ = render :partial => "shared/search"
.items
= render :partial => "shared/tweet", :collection => @items, :as => :item
- if @items
diff --git a/app/views/shared/users.html.haml b/app/views/shared/users.html.haml
index 6393630..776a193 100644
--- a/app/views/shared/users.html.haml
+++ b/app/views/shared/users.html.haml
@@ -4,10 +4,12 @@
- user = User.cached(user_id)
%li
- if user
- %a{:href => url_for(params.merge(:screen_name_b => user.screen_name))}
+ = link_to_user_page user.screen_name do
.avatar= image_tag user.profile_image_url, :alt => user.screen_name, :title => user.name
- else
.avatar= image_tag asset_path("missing_profile_image.png"), :alt => "Missing User: #{user_id}", :title => "Missing User: #{user_id}"
- .count= count
- .type= @event_type
+ .data
+ = link_to url_for(params.merge(:screen_name_b => user.screen_name)) do
+ .count= count
+ .type= @event_type
diff --git a/app/views/users/info.html.haml b/app/views/users/info.html.haml
index 1beb940..2ca31d9 100644
--- a/app/views/users/info.html.haml
+++ b/app/views/users/info.html.haml
@@ -1,17 +1,29 @@
.avatar
- = image_tag @user.profile_image_url_original, :alt => @user.screen_name
+ = image_tag @user.profile_image_url_original, alt: @user.screen_name, width: 128, height: 128
+%p profile
%dl.dl-horizontal
%dt Username
%dd= @user.screen_name
%dt Name
- %dd= raw @twitter_user.name
- %dt Tweets
- %dd= @twitter_user.statuses_count
+ %dd= raw @user.name
+ %dt tweets
+ %dd= @user.stats[:stats_api][:tweets_count]
%dt Following
- %dd= @twitter_user.friends_count
+ %dd= @user.stats[:stats_api][:friends_count]
%dt Followers
- %dd= @twitter_user.followers_count
+ %dd= @user.stats[:stats_api][:followers_count]
%dt Favorites
- %dd= @twitter_user.favourites_count
+ %dd= @user.stats[:stats_api][:favorites_count]
+ %dt Listed
+ %dd= @user.stats[:stats_api][:listed_count]
%dt Bio
- %dd= raw @twitter_user.description
+ %dd= raw @user.stats[:stats_api][:bio]
+- if @user.registered?
+ %p records
+ %dl.dl-horizontal
+ %dt Favorited
+ %dd= @user.stats[:favorited_count]
+ %dt Retweeted
+ %dd= @user.stats[:retweeted_count]
+ %dt Since
+ %dd= @user.account.created_at
diff --git a/client/worker.rb b/client/worker.rb
index eeb7bf9..951b8f7 100644
--- a/client/worker.rb
+++ b/client/worker.rb
@@ -24,13 +24,13 @@ class Worker
last_index = entities.inject(0) do |last_index, entity|
result << chars[last_index...entity[:indices].first]
result << if entity[:url]
- "<url:#{CGI.escape(entity[:expanded_url])}:#{CGI.escape(entity[:display_url])}>"
+ "<url:#{escape_colon(entity[:expanded_url])}:#{escape_colon(entity[:display_url])}>"
elsif entity[:text]
- "<hashtag:#{CGI.escape(entity[:text])}>"
+ "<hashtag:#{escape_colon(entity[:text])}>"
elsif entity[:screen_name]
- "<mention:#{CGI.escape(entity[:screen_name])}>"
+ "<mention:#{escape_colon(entity[:screen_name])}>"
elsif entity[:cashtag]
- "<cashtag:#{CGI.escape(entity[:cashtag])}>"
+ "<cashtag:#{escape_colon(entity[:cashtag])}>"
end
entity[:indices].last
end
@@ -39,6 +39,8 @@ class Worker
result.flatten.join
end
+ def escape_colon(str); str.gsub(":", "%3A").gsub("<", "%3C").gsub(">", "%3E"); end
+
def format_source(status)
if status[:source].index("<a")
url = status[:source].scan(/href="(.+?)"/).flatten.first
@@ -192,6 +194,7 @@ class Worker
send_retweet.call(hash)
end
elsif hash[:user][:id] == user_id
+ # update: exclude not favorited tweet
send_tweet.call(hash)
end
elsif hash[:friends]
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index 0a965bc..2bbd73d 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -1,3 +1,3 @@
# Be sure to restart your server when you modify this file.
-Aclog::Application.config.session_store :encrypted_cookie_store, key: '_Aclog_session'
+Aclog::Application.config.session_store ActionDispatch::Session::CacheStore, :expire_after => 3.days
diff --git a/config/routes.rb b/config/routes.rb
index 73abb83..09e0b43 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -28,6 +28,8 @@ Aclog::Application.routes.draw do
get "/i/show" => "users#show"
get "/i/:id" => "users#show", :constraints => constraints
+ get "/search" => "search#search"
+
# i -- end
get "/i(/:none)" => redirect("/")
@@ -48,7 +50,6 @@ Aclog::Application.routes.draw do
get "/:screen_name/recent(/:page)" => "users#recent", :constraints => constraints
get "/:screen_name/recent/:order(/:page)" => "users#recent", :constraints => constraints
get "/:screen_name/timeline(/:page)" => "users#timeline", :constraints => constraints
- get "/:screen_name/timeline/all(/:page)" => "users#timeline", :constraints => constraints, :defaults => {:all => "true"}
get "/:screen_name/discovered(/:page)" => "users#discovered", :constraints => constraints
get "/:screen_name/discovered/:tweets(/:page)" => "users#discovered", :constraints => constraints
get "/:screen_name/info" => "users#info", :constraints => constraints
diff --git a/public/assets/application-252e3d8b1032ad10b8a6615bb347aad0.js b/public/assets/application-252e3d8b1032ad10b8a6615bb347aad0.js
new file mode 100644
index 0000000..3355de2
--- /dev/null
+++ b/public/assets/application-252e3d8b1032ad10b8a6615bb347aad0.js
@@ -0,0 +1,57 @@
+(function() {
+ if (window.__twitterIntentHandler) return;
+ var intentRegex = /twitter\.com(\:\d{2,4})?\/intent\/(\w+)/,
+ windowOptions = 'scrollbars=yes,resizable=yes,toolbar=no,location=yes',
+ width = 550,
+ height = 420,
+ winHeight = screen.height,
+ winWidth = screen.width;
+
+ function handleIntent(e) {
+ e = e || window.event;
+ var target = e.target || e.srcElement,
+ m, left, top;
+
+ while (target && target.nodeName.toLowerCase() !== 'a') {
+ target = target.parentNode;
+ }
+
+ if (target && target.nodeName.toLowerCase() === 'a' && target.href) {
+ m = target.href.match(intentRegex);
+ if (m) {
+ left = Math.round((winWidth / 2) - (width / 2));
+ top = 0;
+
+ if (winHeight > height) {
+ top = Math.round((winHeight / 2) - (height / 2));
+ }
+
+ window.open(target.href, 'intent', windowOptions + ',width=' + width +
+ ',height=' + height + ',left=' + left + ',top=' + top);
+ e.returnValue = false;
+ e.preventDefault && e.preventDefault();
+ }
+ }
+ }
+
+ if (document.addEventListener) {
+ document.addEventListener('click', handleIntent, false);
+ } else if (document.attachEvent) {
+ document.attachEvent('onclick', handleIntent);
+ }
+ window.__twitterIntentHandler = true;
+}());
+// This is a manifest file that'll be compiled into application.js, which will include all the files
+// listed below.
+//
+// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
+// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
+//
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// the compiled file.
+//
+// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
+// GO AFTER THE REQUIRES BELOW.
+//
+
+;
diff --git a/public/assets/application-252e3d8b1032ad10b8a6615bb347aad0.js.gz b/public/assets/application-252e3d8b1032ad10b8a6615bb347aad0.js.gz
new file mode 100644
index 0000000..fb5c3ab
--- /dev/null
+++ b/public/assets/application-252e3d8b1032ad10b8a6615bb347aad0.js.gz
Binary files differ
diff --git a/public/assets/application-eb873c58ea6dc20a9ecb48c9bd070d3c.css b/public/assets/application-38ff4e9e27d0ec3d917e7d43fcec9197.css
index 3259ade..97f786b 100644
--- a/public/assets/application-eb873c58ea6dc20a9ecb48c9bd070d3c.css
+++ b/public/assets/application-38ff4e9e27d0ec3d917e7d43fcec9197.css
@@ -4854,12 +4854,20 @@ a.label:hover, a.label:focus, a.badge:hover, a.badge:focus {
border: 1px solid #c1c5cb;
border-width: 1px 0;
padding: 15px; }
- .items .item .tweet .avatar {
- width: 60px;
- float: left; }
- .items .item .tweet .avatar img {
+ .items .item .tweet .left {
+ float: left;
+ width: 60px; }
+ .items .item .tweet .left .avatar img {
width: 48px;
height: 48px; }
+ .items .item .tweet .left ul.actions li {
+ padding: 0; }
+ .items .item .tweet .left ul.actions li a {
+ float: left; }
+ .items .item .tweet .left ul.actions li a img {
+ width: 16px;
+ height: 16px;
+ vertical-align: bottom; }
.items .item .tweet .tweet_content_fix {
float: left;
width: 0;
@@ -4965,8 +4973,25 @@ a.label:hover, a.label:focus, a.badge:hover, a.badge:focus {
margin: 10px 0; }
.sidebar .avatar {
text-align: center; }
- .sidebar .records .data {
- text-align: right; }
+ .sidebar .records {
+ list-style: none;
+ margin: 0 0 20px; }
+ .sidebar .records li {
+ *zoom: 1;
+ border-top: 1px solid #dddddd; }
+ .sidebar .records li:before, .sidebar .records li:after {
+ display: table;
+ content: "";
+ line-height: 0; }
+ .sidebar .records li:after {
+ clear: both; }
+ .sidebar .records li span {
+ display: block;
+ float: left;
+ line-height: 20px;
+ padding: 4px 5px; }
+ .sidebar .records li .data {
+ float: right; }
a {
text-decoration: none;
diff --git a/public/assets/application-eb873c58ea6dc20a9ecb48c9bd070d3c.css.gz b/public/assets/application-38ff4e9e27d0ec3d917e7d43fcec9197.css.gz
index 7cf8835..7140e98 100644
--- a/public/assets/application-eb873c58ea6dc20a9ecb48c9bd070d3c.css.gz
+++ b/public/assets/application-38ff4e9e27d0ec3d917e7d43fcec9197.css.gz
Binary files differ
diff --git a/public/assets/application-c0feb24db0871514682a56320c7b2b2b.js b/public/assets/application-c0feb24db0871514682a56320c7b2b2b.js
deleted file mode 100644
index d287f32..0000000
--- a/public/assets/application-c0feb24db0871514682a56320c7b2b2b.js
+++ /dev/null
@@ -1,34 +0,0 @@
-(function() {
-
-
-}).call(this);
-(function() {
-
-
-}).call(this);
-(function() {
-
-
-}).call(this);
-(function() {
-
-
-}).call(this);
-(function() {
-
-
-}).call(this);
-// This is a manifest file that'll be compiled into application.js, which will include all the files
-// listed below.
-//
-// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
-// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
-//
-// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-// the compiled file.
-//
-// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
-// GO AFTER THE REQUIRES BELOW.
-//
-
-;
diff --git a/public/assets/application-c0feb24db0871514682a56320c7b2b2b.js.gz b/public/assets/application-c0feb24db0871514682a56320c7b2b2b.js.gz
deleted file mode 100644
index b820f5f..0000000
--- a/public/assets/application-c0feb24db0871514682a56320c7b2b2b.js.gz
+++ /dev/null
Binary files differ
diff --git a/public/assets/favorite-f705dbd122eeb6b0a17f75306600f33c.png b/public/assets/favorite-f705dbd122eeb6b0a17f75306600f33c.png
new file mode 100644
index 0000000..d4c53be
--- /dev/null
+++ b/public/assets/favorite-f705dbd122eeb6b0a17f75306600f33c.png
Binary files differ
diff --git a/public/assets/manifest-48ac099aa1a004ba8c6f9a787cc5fe11.json b/public/assets/manifest-48ac099aa1a004ba8c6f9a787cc5fe11.json
new file mode 100644
index 0000000..e030656
--- /dev/null
+++ b/public/assets/manifest-48ac099aa1a004ba8c6f9a787cc5fe11.json
@@ -0,0 +1 @@
+{"files":{"bird_gray_16-93f51980875017e26744056b62aaac22.png":{"logical_path":"bird_gray_16.png","mtime":"2013-03-09T14:41:39+09:00","size":1106,"digest":"93f51980875017e26744056b62aaac22"},"favorite-f705dbd122eeb6b0a17f75306600f33c.png":{"logical_path":"favorite.png","mtime":"2013-03-28T13:53:46+09:00","size":1329,"digest":"f705dbd122eeb6b0a17f75306600f33c"},"missing_profile_image-d5d708680693a18615dfb8f5b1a5a96c.png":{"logical_path":"missing_profile_image.png","mtime":"2013-03-16T16:45:26+09:00","size":9350,"digest":"d5d708680693a18615dfb8f5b1a5a96c"},"protected_profile_image-d5d708680693a18615dfb8f5b1a5a96c.png":{"logical_path":"protected_profile_image.png","mtime":"2013-03-28T01:43:45+09:00","size":9350,"digest":"d5d708680693a18615dfb8f5b1a5a96c"},"rails-bc7d436ef8afbf0f88829742a43ba3a4.png":{"logical_path":"rails.png","mtime":"2013-02-25T21:20:52+09:00","size":6646,"digest":"bc7d436ef8afbf0f88829742a43ba3a4"},"reply-aded44c637c7902c47d64921125bec96.png":{"logical_path":"reply.png","mtime":"2013-03-28T15:24:04+09:00","size":1153,"digest":"aded44c637c7902c47d64921125bec96"},"retweet-d214a384fd3a031f48046b77e5c15d8d.png":{"logical_path":"retweet.png","mtime":"2013-03-28T13:56:02+09:00","size":1095,"digest":"d214a384fd3a031f48046b77e5c15d8d"},"application-252e3d8b1032ad10b8a6615bb347aad0.js":{"logical_path":"application.js","mtime":"2013-03-28T15:50:43+09:00","size":1939,"digest":"252e3d8b1032ad10b8a6615bb347aad0"},"application-38ff4e9e27d0ec3d917e7d43fcec9197.css":{"logical_path":"application.css","mtime":"2013-03-29T03:09:23+09:00","size":132403,"digest":"38ff4e9e27d0ec3d917e7d43fcec9197"},"glyphicons-halflings-white-0e5e72373e37a02757721decf4d90757.png":{"logical_path":"glyphicons-halflings-white.png","mtime":"2013-03-26T17:49:47+09:00","size":8777,"digest":"0e5e72373e37a02757721decf4d90757"},"glyphicons-halflings-f358b5f4bb585b4c519a838dba74111a.png":{"logical_path":"glyphicons-halflings.png","mtime":"2013-03-26T17:49:47+09:00","size":12799,"digest":"f358b5f4bb585b4c519a838dba74111a"}},"assets":{"bird_gray_16.png":"bird_gray_16-93f51980875017e26744056b62aaac22.png","favorite.png":"favorite-f705dbd122eeb6b0a17f75306600f33c.png","missing_profile_image.png":"missing_profile_image-d5d708680693a18615dfb8f5b1a5a96c.png","protected_profile_image.png":"protected_profile_image-d5d708680693a18615dfb8f5b1a5a96c.png","rails.png":"rails-bc7d436ef8afbf0f88829742a43ba3a4.png","reply.png":"reply-aded44c637c7902c47d64921125bec96.png","retweet.png":"retweet-d214a384fd3a031f48046b77e5c15d8d.png","application.js":"application-252e3d8b1032ad10b8a6615bb347aad0.js","application.css":"application-38ff4e9e27d0ec3d917e7d43fcec9197.css","glyphicons-halflings-white.png":"glyphicons-halflings-white-0e5e72373e37a02757721decf4d90757.png","glyphicons-halflings.png":"glyphicons-halflings-f358b5f4bb585b4c519a838dba74111a.png"}} \ No newline at end of file
diff --git a/public/assets/manifest-b6e16b8d09a3fc9041a0a623c7b857af.json b/public/assets/manifest-b6e16b8d09a3fc9041a0a623c7b857af.json
deleted file mode 100644
index 5b52f0e..0000000
--- a/public/assets/manifest-b6e16b8d09a3fc9041a0a623c7b857af.json
+++ /dev/null
@@ -1 +0,0 @@
-{"files":{"bird_gray_16-93f51980875017e26744056b62aaac22.png":{"logical_path":"bird_gray_16.png","mtime":"2013-03-09T14:41:39+09:00","size":1106,"digest":"93f51980875017e26744056b62aaac22"},"missing_profile_image-d5d708680693a18615dfb8f5b1a5a96c.png":{"logical_path":"missing_profile_image.png","mtime":"2013-03-16T16:45:26+09:00","size":9350,"digest":"d5d708680693a18615dfb8f5b1a5a96c"},"rails-bc7d436ef8afbf0f88829742a43ba3a4.png":{"logical_path":"rails.png","mtime":"2013-02-25T21:20:52+09:00","size":6646,"digest":"bc7d436ef8afbf0f88829742a43ba3a4"},"application-c0feb24db0871514682a56320c7b2b2b.js":{"logical_path":"application.js","mtime":"2013-03-09T01:51:23+09:00","size":738,"digest":"c0feb24db0871514682a56320c7b2b2b"},"application-eb873c58ea6dc20a9ecb48c9bd070d3c.css":{"logical_path":"application.css","mtime":"2013-03-27T17:43:10+09:00","size":131606,"digest":"eb873c58ea6dc20a9ecb48c9bd070d3c"},"glyphicons-halflings-white-0e5e72373e37a02757721decf4d90757.png":{"logical_path":"glyphicons-halflings-white.png","mtime":"2013-03-26T17:49:47+09:00","size":8777,"digest":"0e5e72373e37a02757721decf4d90757"},"glyphicons-halflings-f358b5f4bb585b4c519a838dba74111a.png":{"logical_path":"glyphicons-halflings.png","mtime":"2013-03-26T17:49:47+09:00","size":12799,"digest":"f358b5f4bb585b4c519a838dba74111a"}},"assets":{"bird_gray_16.png":"bird_gray_16-93f51980875017e26744056b62aaac22.png","missing_profile_image.png":"missing_profile_image-d5d708680693a18615dfb8f5b1a5a96c.png","rails.png":"rails-bc7d436ef8afbf0f88829742a43ba3a4.png","application.js":"application-c0feb24db0871514682a56320c7b2b2b.js","application.css":"application-eb873c58ea6dc20a9ecb48c9bd070d3c.css","glyphicons-halflings-white.png":"glyphicons-halflings-white-0e5e72373e37a02757721decf4d90757.png","glyphicons-halflings.png":"glyphicons-halflings-f358b5f4bb585b4c519a838dba74111a.png"}} \ No newline at end of file
diff --git a/public/assets/protected_profile_image-d5d708680693a18615dfb8f5b1a5a96c.png b/public/assets/protected_profile_image-d5d708680693a18615dfb8f5b1a5a96c.png
new file mode 100644
index 0000000..3d1e0f9
--- /dev/null
+++ b/public/assets/protected_profile_image-d5d708680693a18615dfb8f5b1a5a96c.png
Binary files differ
diff --git a/public/assets/reply-aded44c637c7902c47d64921125bec96.png b/public/assets/reply-aded44c637c7902c47d64921125bec96.png
new file mode 100644
index 0000000..5493c72
--- /dev/null
+++ b/public/assets/reply-aded44c637c7902c47d64921125bec96.png
Binary files differ
diff --git a/public/assets/retweet-d214a384fd3a031f48046b77e5c15d8d.png b/public/assets/retweet-d214a384fd3a031f48046b77e5c15d8d.png
new file mode 100644
index 0000000..94e3595
--- /dev/null
+++ b/public/assets/retweet-d214a384fd3a031f48046b77e5c15d8d.png
Binary files differ
diff --git a/test/controllers/search_controller_test.rb b/test/controllers/search_controller_test.rb
new file mode 100644
index 0000000..bfbf22d
--- /dev/null
+++ b/test/controllers/search_controller_test.rb
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class SearchControllerTest < ActionController::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
diff --git a/test/helpers/search_helper_test.rb b/test/helpers/search_helper_test.rb
new file mode 100644
index 0000000..3034163
--- /dev/null
+++ b/test/helpers/search_helper_test.rb
@@ -0,0 +1,4 @@
+require 'test_helper'
+
+class SearchHelperTest < ActionView::TestCase
+end