aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/_html-autoload.js67
-rw-r--r--app/assets/javascripts/tweets.js22
-rw-r--r--app/controllers/apidocs_controller.rb28
-rw-r--r--app/controllers/application_controller.rb71
-rw-r--r--app/controllers/errors_controller.rb1
-rw-r--r--app/controllers/tweets_controller.rb184
-rw-r--r--app/controllers/users_controller.rb59
-rw-r--r--app/helpers/application_helper.rb1
-rw-r--r--app/helpers/tweets_helper.rb10
-rw-r--r--app/models/tweet.rb14
-rw-r--r--app/models/user.rb36
-rw-r--r--app/views/about/api.html.haml107
-rw-r--r--app/views/apidocs/endpoint.html.haml22
-rw-r--r--app/views/apidocs/index.html.haml18
-rw-r--r--app/views/layouts/_base.html.haml4
-rw-r--r--app/views/layouts/tweets.html.haml7
-rw-r--r--app/views/shared/sidebar/_users.html.haml10
-rw-r--r--app/views/tweets/_tweet.html.haml8
-rw-r--r--app/views/tweets/_tweets.atom.builder19
-rw-r--r--app/views/tweets/_tweets.html.haml8
-rw-r--r--app/views/tweets/_tweets.json.jbuilder5
-rw-r--r--app/views/tweets/_tweets.rss.builder20
-rw-r--r--app/views/tweets/all_best.html.haml2
-rw-r--r--app/views/tweets/all_recent.html.haml2
-rw-r--r--app/views/tweets/all_timeline.atom.builder4
-rw-r--r--app/views/tweets/all_timeline.html.haml2
-rw-r--r--app/views/tweets/best.html.haml2
-rw-r--r--app/views/tweets/discovered_by.atom.builder4
-rw-r--r--app/views/tweets/discovered_by.html.haml4
-rw-r--r--app/views/tweets/discoveries.atom.builder4
-rw-r--r--app/views/tweets/discoveries.html.haml2
-rw-r--r--app/views/tweets/favorites.html.haml2
-rw-r--r--app/views/tweets/recent.html.haml2
-rw-r--r--app/views/tweets/retweets.html.haml2
-rw-r--r--app/views/tweets/search.html.haml2
-rw-r--r--app/views/tweets/show.html.haml2
-rw-r--r--app/views/tweets/timeline.atom.builder4
-rw-r--r--app/views/tweets/timeline.html.haml2
-rw-r--r--app/views/users/_user_ranking.html.haml19
-rw-r--r--app/views/users/_users_list.json.jbuilder (renamed from app/views/users/_user_ranking.json.jbuilder)1
-rw-r--r--app/views/users/discovered_by.html.haml14
-rw-r--r--app/views/users/discovered_users.html.haml14
-rw-r--r--app/views/users/stats.html.haml24
-rw-r--r--config/application.rb2
-rw-r--r--config/environments/development.rb2
-rw-r--r--config/routes.rb25
-rw-r--r--lib/aclog/exceptions.rb2
-rw-r--r--lib/apidoc.rb12
-rw-r--r--lib/apidoc/controller_dsl.rb45
-rw-r--r--lib/apidoc/controller_dsl/endpoint.rb30
-rw-r--r--lib/apidoc/controller_dsl/parameters.rb27
-rw-r--r--lib/apidoc/controller_dsl/resources.rb12
-rw-r--r--lib/apidoc/endpoint.rb24
-rw-r--r--lib/apidoc/exceptions.rb17
-rw-r--r--lib/apidoc/parameter.rb40
-rw-r--r--lib/apidoc/railtie.rb9
-rw-r--r--lib/apidoc/resource.rb12
57 files changed, 693 insertions, 401 deletions
diff --git a/app/assets/javascripts/_html-autoload.js b/app/assets/javascripts/_html-autoload.js
index d96b80b..ac96b60 100644
--- a/app/assets/javascripts/_html-autoload.js
+++ b/app/assets/javascripts/_html-autoload.js
@@ -1,55 +1,30 @@
(function($) {
- var window = this,
- options = {},
- content,
- nextUrl,
- page = 1,
- loading = false;
-
- $.autopager = function(_options) {
- var autopager = this.autopager;
+ var loading = false;
+ var opts = null;
+ $.autopager = function(_opts) {
var defaults = {
- content: "#content",
- nextLink: "a[rel=next]",
- onStart: function() {},
- onComplete: function() {}
+ content: $("#content"),
+ link: $("link[rel=next]"),
+ onStart: function() { },
+ onComplete: function() { }
};
+ opts = $.extend({}, defaults, _opts);
- options = $.extend({}, defaults, _options);
- content = $(options.content);
- nextUrl = $(options.nextLink).attr("href");
-
$(window).scroll(function() {
- if (content.offset().top + content.height() < $(document).scrollTop() + $(window).height()) {
- $.autopager.loadNext();
+ if ((opts.content.offset().top + opts.content.height()) < ($(document).scrollTop() + $(window).height())) {
+ if (loading || !opts.link) return;
+
+ opts.onStart();
+ loading = true;
+ $.getJSON(opts.link.attr("href"), function(json, status) {
+ opts.content.append(json.html);
+ opts.link.attr("href", json.next_url);
+ loading = false;
+ opts.onComplete();
+ });
}
});
-
- return this;
- };
-
- $.extend($.autopager, {
- loadNext: function() {
- if (loading || !nextUrl) {
- return;
- }
-
- loading = true;
- options.onStart();
- $.getJSON(nextUrl, insertContent);
- return this;
- }
- });
-
- function insertContent(json, status) {
- var nextPage = $(json.html);
-
- page = page + 1;
- nextUrl = json.next;
- $(options.nextLink).attr("href", nextUrl);
- content.append(nextPage);
- options.onComplete();
- loading = false;
}
-})(jQuery);
+})($);
+
diff --git a/app/assets/javascripts/tweets.js b/app/assets/javascripts/tweets.js
index 5a0b955..8428842 100644
--- a/app/assets/javascripts/tweets.js
+++ b/app/assets/javascripts/tweets.js
@@ -1,15 +1,15 @@
//= require _html-autoload
$(function() {
- $(".pagination").hide();
- $.autopager({
- content: ".tweets",
- nextLink: "a[rel=next]",
- onStart: function() {
- $(".loading").show();
- },
- onComplete: function() {
- $(".loading").hide();
- }
- });
+ $(".pagination").hide();
+ $.autopager({
+ content: $(".tweets"),
+ link: $("link[rel=next]"),
+ onStart: function() {
+ // $(".loading").show();
+ },
+ onComplete: function() {
+ // $(".loading").hide();
+ }
+ });
});
diff --git a/app/controllers/apidocs_controller.rb b/app/controllers/apidocs_controller.rb
new file mode 100644
index 0000000..11c0f34
--- /dev/null
+++ b/app/controllers/apidocs_controller.rb
@@ -0,0 +1,28 @@
+class ApidocsController < ApplicationController
+ before_filter :reload_docs
+
+ def index
+ @resources = Apidoc.resources
+ end
+
+ def endpoint
+ @resource = Apidoc.resources[params[:resource].to_sym]
+
+ unless @resource
+ raise Aclog::Exceptions::DocumentNotFound
+ end
+
+ @endpoint = @resource.endpoints[params[:name].to_sym]
+
+ unless @endpoint
+ raise Aclog::Exceptions::DocumentNotFound
+ end
+ end
+
+ private
+ def reload_docs
+ Apidoc.reload_docs if Rails.env.development?
+ end
+end
+
+
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 37a7820..61a4bf2 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,57 +1,58 @@
-# -*- coding: utf-8 -*-
class ApplicationController < ActionController::Base
include Aclog::TwitterOauthEchoAuthentication::ControllerMethods
protect_from_forgery
before_filter :check_format, :check_session
after_filter :xhtml
- helper_method :logged_in?, :authorized_to_show_user?, :authorized_to_show_best?
+ helper_method :current_user, :logged_in?, :allowed_to_see_user?, :allowed_to_see_best?
protected
- def logged_in?; session[:user_id] && session[:account] end
-
- def authorized_to_show_user?(user)
- @authorized_to_show_user ||= {}
- @authorized_to_show_user[user.id] ||= begin
- if !user.protected?
- true
- elsif session[:user_id] == user.id
- true
- elsif session[:account] && session[:account].following?(user.id)
- true
- elsif request.headers["X-Verify-Credentials-Authorization"]
- # OAuth Echo
- user_id = authenticate_with_twitter_oauth_echo rescue false
- account = Account.find_by(user_id: user_id)
- if account && (account.user_id == user.id || account.following?(user.id))
- true
- else
- false
- end
- else
- false
- end
+ def current_user
+ if session[:user_id]
+ User.find(session[:user_id])
+ elsif request.headers["X-Verify-Credentials-Authorization"]
+ user_id = authenticate_with_twitter_oauth_echo
+ a = Account.find_by(user_id: user_id)
+ a.user
end
+ rescue
+ nil
end
- def authorized_to_show_best?(user)
- authorized_to_show_user?(user) && user.registered? && user.account.active? && (!user.account.private? || user.id == session[:user_id])
+ def logged_in?
+ !!current_user
end
- def authorize_to_show_user!(user)
- authorized_to_show_user?(user) or raise Aclog::Exceptions::UserProtected.new(user)
+ def allowed_to_see_user?(user)
+ !user.protected? ||
+ logged_in? && (current_user == user || current_user.following?(user))
end
- def authorize_to_show_best!(user)
- authorize_to_show_user!(user)
- raise Aclog::Exceptions::UserNotRegistered.new(user) unless user.registered? && user.account.active?
- raise Aclog::Exceptions::AccountPrivate.new(user) if user.account.private? && user.id != session[:user_id]
- true
+ def allowed_to_see_best?(user)
+ !user.private? || current_user == user
+ end
+
+ def require_user(user_id: params[:user_id], screen_name: params[:screen_name], public: false)
+ begin
+ user = User.find(id: user_id, screen_name: screen_name)
+ rescue ActiveRecord::RecordNotFound
+ raise Aclog::Exceptions::UserNotFound
+ end
+
+ if !allowed_to_see_user?(user)
+ raise Aclog::Exceptions::UserProtected, user
+ end
+
+ if public && !allowed_to_see_best?(user)
+ raise Aclog::Exceptions::AccountPrivate, user
+ end
+
+ user
end
private
def check_format
- unless request.format == :html || request.format == :json || request.format == :rss
+ unless request.format == :html || request.format == :json || request.format == :rss || request.format == :atom
if params[:format] == nil
request.format = :html
else
diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb
index bf35c75..a9a88bd 100644
--- a/app/controllers/errors_controller.rb
+++ b/app/controllers/errors_controller.rb
@@ -1,4 +1,3 @@
-# -*- encoding: utf-8 -*-
class ErrorsController < ApplicationController
skip_before_filter :check_format
before_filter :force_format
diff --git a/app/controllers/tweets_controller.rb b/app/controllers/tweets_controller.rb
index 3d74913..b86b55d 100644
--- a/app/controllers/tweets_controller.rb
+++ b/app/controllers/tweets_controller.rb
@@ -1,115 +1,185 @@
class TweetsController < ApplicationController
- def show
- @tweet = Tweet.find(params[:id])
- @user = @tweet.user
- rescue ActiveRecord::RecordNotFound
- raise Aclog::Exceptions::TweetNotFound
+ param_group :pagination_with_page_number do
+ optional :count, :integer, "The number of tweets to retrieve. Must be less than or equal to 200, defaults to 20."
+ optional :page, :integer, "The page number of results to retrieve."
end
- def lookup
- @tweets = Tweet.where(id: params[:id].to_s.split(",").map(&:to_i))
+ param_group :pagination_with_ids do
+ param_group :pagination_with_page_number
+ optional :since_id, :integer, "Returns results with an ID greater than the specified ID."
+ optional :max_id, :integer, "Returns results with an ID less than or equal to the specified ID."
+ end
+
+ param_group :user do
+ optional :user_id, :integer, "The numerical ID of the user for whom to return results for."
+ optional :screen_name, :string, "The username of the user for whom to return results for."
end
def index
- user_required
begin
best
- rescue
+ render :best
+ rescue Aclog::Exceptions::AccountPrivate
timeline
- render "timeline"
- else
- render "best"
+ render :timeline
end
end
+ get "tweets/show"
+ description "Returns a single Tweet, specified by ID."
+ requires :id, :integer, "The numerical ID of the desired Tweet."
+ see "tweets#lookup"
+ def show
+ @tweet = Tweet.find(params[:id])
+ @user = require_user(user_id: @tweet.user_id)
+ end
+
+ get "tweets/lookup"
+ description "Returns Tweets, specified by comma-separated IDs."
+ requires :id, /^\d+(,\d+)*,?$/, "A comma-separated list of Tweet IDs, up to #{Settings.tweets.count.max} are allowed in a single request."
+ see "tweets#show"
+ def lookup
+ @tweets = Tweet.where(id: params[:id].split(",").map(&:to_i))
+ end
+
+ get "tweets/best"
+ description "Returns the best Tweets of a user, specified by username or user ID."
+ param_group :user
+ param_group :pagination_with_page_number
def best
- user_required
- check_public!
- @tweets = @user.tweets.list(params, force_page: true).reacted.order_by_reactions
+ @user = require_user(public: true)
+ @tweets = paginate_with_page_number(@user.tweets.reacted.order_by_reactions)
end
+ # get "tweets/recent"
+ # description "Returns the best Tweets in the recent three days of a user, specified by username or user ID."
+ # param_group :user
+ # param_group :pagination_with_page_number
def recent
- user_required
- check_public!
- @tweets = @user.tweets.list(params, force_page: true).recent.reacted.order_by_reactions
+ @user = require_user(public: true)
+ @tweets = paginate_with_page_number(@user.tweets.reacted.recent.order_by_reactions)
end
+ get "tweets/timeline"
+ description "Returns the newest Tweets of a user, specified by username or user ID."
+ param_group :user
+ param_group :pagination_with_ids
def timeline
- user_required
- @tweets = @user.tweets.list(params).reacted.order_by_id
+ @user = require_user
+ @tweets = paginate(@user.tweets.reacted.order_by_id)
end
+ get "tweets/discoveries"
+ description "Returns the Tweets which a user specified by username or user ID discovered."
+ param_group :user
+ param_group :pagination_with_ids
def discoveries
- user_required
- @tweets = Tweet.list(params, force_page: true).discovered_by(@user).order_by_id
+ @user = require_user
+ @tweets = paginate(Tweet.discovered_by(@user).order_by_id)
end
+ get "tweets/favorites"
+ description "Returns the Tweets which a user specified by username or user ID favorited."
+ param_group :user
+ param_group :pagination_with_ids
def favorites
- user_required
- @tweets = Tweet.list(params, force_page: true).favorited_by(@user).order_by_id
+ @user = require_user
+ @tweets = paginate(Tweet.favorited_by(@user).order_by_id)
end
+ get "tweets/retweets"
+ description "Returns the Tweets which a user specified by username or user ID retweeted."
+ param_group :user
+ param_group :pagination_with_ids
def retweets
- user_required
- @tweets = Tweet.list(params, force_page: true).retweeted_by(@user).order_by_id
+ @user = require_user
+ @tweets = paginate(Tweet.retweeted_by(@user).order_by_id)
end
+ get "tweets/discovered_by"
+ description "Returns the Tweets which a user specified by username or user ID retweeted."
+ param_group :user
+ optional :source_user_id, :integer, "The numerical ID of the subject user."
+ optional :source_screen_name, :string, "The username of the subject user."
+ param_group :pagination_with_ids
def discovered_by
- user_required
- user_b_required
- @tweets = @user.tweets.list(params).discovered_by(@user_b).order_by_id
+ @user = require_user
+ @source_user = require_user(user_id: params[:source_user_id], screen_name: params[:source_screen_name])
+ @tweets = paginate(@user.tweets.discovered_by(@source_user).order_by_id)
end
+ get "tweets/all_best"
+ param_group :pagination_with_page_number
def all_best
- @tweets = Tweet.list(params, force_page: true).reacted.order_by_reactions
+ @tweets = paginate_with_page_number(Tweet.reacted.order_by_reactions)
end
+ get "tweets/all_recent"
+ param_group :pagination_with_page_number
def all_recent
- @tweets = Tweet.list(params, force_page: true).recent.reacted.order_by_reactions
+ @tweets = paginate_with_page_number(Tweet.recent.reacted.order_by_reactions)
end
+ get "tweets/all_timeline"
+ param_group :pagination_with_ids
def all_timeline
- @tweets = Tweet.list(params).reacted.order_by_id
+ @tweets = paginate(Tweet.reacted.order_by_id)
end
+ get "tweets/search"
+ param_group :pagination_with_ids
def search
- @tweets = Tweet.list(params, force_page: true).parse_query(params[:q].to_s || "").reacted.not_protected.order_by_id
- @tweets = @tweets.recent(7) unless @tweets.to_sql.include?("`tweets`.`id`")
+ @tweets = paginate(Tweet.recent(7).parse_query(params[:q].to_s || "").reacted.not_protected.order_by_id)
end
private
- def user_required
- @user = _require_user(params[:user_id], params[:screen_name])
+ def paginate(tweets)
+ if params[:page]
+ paginate_with_page_number(tweets)
+ else
+ tweets = tweets.limit(params_count).max_id(params[:max_id]).since_id(params[:since_id])
+ if tweets.length > 0
+ @prev_url = url_for(params.tap {|h| h.delete(:max_id) }.merge(since_id: tweets.first.id))
+ @next_url = url_for(params.tap {|h| h.delete(:since_id) }.merge(max_id: tweets.last.id - 1))
+ end
+ tweets
+ end
end
- def user_b_required
- @user_b = _require_user(params[:user_id_b], params[:screen_name_b])
+ def paginate_with_page_number(tweets)
+ page = [params[:page].to_i, 1].max
+ @prev_url = page == 1 ? nil : url_for(params.merge(page: page - 1))
+ @next_url = url_for(params.merge(page: page + 1))
+ tweets.limit(params_count).page(page)
end
- def check_public!
- authorize_to_show_best!(@user)
+ def params_count
+ @_count ||= [Settings.tweets.count.max, (params[:count] || Settings.tweets.count.default).to_i].min
end
def render(*args)
- if @tweets && request.xhr?
- html = render_to_string(partial: "tweet", collection: @tweets.includes(:user), as: :tweet, formats: :html)
- n = @tweets.length > 0 ?
- url_for(params[:page] ?
- params.merge(page: params[:page].to_i + 1) :
- params.merge(max_id: @tweets.last.id - 1)) :
- nil
- super json: {html: html, next: n}
- elsif @tweets && !lookup_context.exists?(params[:action], params[:controller]) && request.format == :json
- super("_tweets")
+ if !request.xhr? && request.format == :json
+ # JSON API / Atom
+ begin
+ super(*args)
+ rescue ActionView::MissingTemplate
+ if @tweets
+ super("_tweets")
+ elsif @tweet
+ super("_tweet")
+ else
+ raise
+ end
+ end
else
- super(*args)
+ if @tweets && request.xhr?
+ super(json: { html: render_to_string(partial: "tweet", collection: @tweets, as: :tweet, formats: :html),
+ next_url: @next_url,
+ prev_url: @prev_url })
+ else
+ super(*args)
+ end
end
end
-
- def _require_user(user_id, screen_name)
- user = User.get(user_id, screen_name)
- raise Aclog::Exceptions::UserProtected.new(user) unless authorized_to_show_user?(user)
- user
- end
end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 580e075..71117e7 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1,36 +1,61 @@
class UsersController < ApplicationController
+ param_group :user do
+ optional :id, :integer, "The numerical ID of the user for whom to return results for."
+ optional :screen_name, :string, "The username of the user for whom to return results for."
+ end
+
+ get "/users/stats"
+ description "Returns the stats of a user, specified by username or user ID."
+ param_group :user
def stats
- user_required
- @caption = "Profile"
- @user_stats = @user.stats
- @user_twitter = @user.account.client.user if request.format == :html
+ @user = require_public_user
end
+ get "/users/discovered_by"
+ description "Returns the list of the users who discovored the Tweets of a user, specified by username or user ID."
+ param_group :user
def discovered_by
- user_required
- authorize_to_show_best!(@user)
+ @user = require_public_user
@result = @user.count_discovered_by.take(Settings.users.count)
- @caption = "Discovered By"
- render "_user_ranking"
+
+ respond_to do |format|
+ format.html do
+ @cached_users = User.find(@result.map {|user_id, count| user_id }).map {|user| [user.id, user] }.to_h
+ end
+
+ format.json do
+ render "_users_list"
+ end
+ end
end
+ get "/users/discovered_users"
+ description "Returns the list of the users discovored by a user, specified by username or user ID."
+ param_group :user
def discovered_users
- user_required
- authorize_to_show_best!(@user)
+ @user = require_public_user
@result = @user.count_discovered_users.take(Settings.users.count)
- @caption = "Discovered Users"
- render "_user_ranking"
+
+ respond_to do |format|
+ format.html do
+ @cached_users = User.find(@result.map {|user_id, count| user_id }).map {|user| [user.id, user] }.to_h
+ end
+
+ format.json do
+ render "_users_list"
+ end
+ end
end
+ # get "/users/screen_name"
def screen_name
- user_ids = (params[:id] || params[:user_id]).to_s.split(",").map(&:to_i)
- result = User.where(id: user_ids).pluck(:id, :screen_name).map {|id, screen_name| {id: id, screen_name: screen_name} }
+ user_ids = (params[:id] || params[:ids] || params[:user_id] || params[:user_ids]).split(",").map { |i| i.to_i }
+ result = User.where(id: user_ids).pluck(:id, :screen_name).map { |id, screen_name| { id: id, screen_name: screen_name } }
render json: result
end
private
- def user_required
- @user = User.get(params[:id] || params[:user_id], params[:screen_name])
- raise Aclog::Exceptions::UserNotFound unless @user
+ def require_public_user
+ require_user(user_id: (params[:id] || params[:user_id]), screen_name: params[:screen_name], public: true)
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index e7cb03c..5700099 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,4 +1,3 @@
-# -*- encoding: utf-8 -*-
module ApplicationHelper
def format_time(dt)
dt.to_time.localtime("+09:00").strftime("%Y-%m-%d %H:%M:%S")
diff --git a/app/helpers/tweets_helper.rb b/app/helpers/tweets_helper.rb
index 7c4050a..332380b 100644
--- a/app/helpers/tweets_helper.rb
+++ b/app/helpers/tweets_helper.rb
@@ -6,4 +6,14 @@ module TweetsHelper
def favorites_truncated?(tweet)
(favorites_truncate_count || Float::INFINITY) < [tweet.favorites_count, tweet.retweets_count].max
end
+
+ def link_to_source_text(source)
+ if /^<a href="(.+?)" rel="nofollow">(.+?)<\/a>/ =~ source
+ link_to $2, $1
+ elsif /^<url:(.+?)(?<!\\):(.+?)?>$/ =~ source
+ link_to(*[$2, $1.gsub(/(https?)%3A/, "\\1:")].map {|m| m.gsub("\\:", ":") })
+ else
+ h source
+ end
+ end
end
diff --git a/app/models/tweet.rb b/app/models/tweet.rb
index 2642d3b..d5ad222 100644
--- a/app/models/tweet.rb
+++ b/app/models/tweet.rb
@@ -42,20 +42,6 @@ class Tweet < ActiveRecord::Base
end
end
- def self.list(params, options = {})
- count = params[:count].to_i
- count = Settings.tweets.count.default unless (1..Settings.tweets.count.max) === count
-
- if params[:page] || options[:force_page]
- page = [params[:page].to_i, 1].max
- ret = limit(count).page(page)
- else
- ret = limit(count).max_id(params[:max_id]).since_id(params[:since_id])
- end
-
- ret
- end
-
def self.delete_from_id(id)
return {} if id.is_a?(Array) && id.size == 0
begin
diff --git a/app/models/user.rb b/app/models/user.rb
index 80b4a9d..f41c68c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -9,14 +9,34 @@ class User < ActiveRecord::Base
"https://twitter.com/#{self.screen_name}"
end
- def self.get(id, screen_name)
- if id
- find(id) rescue raise Aclog::Exceptions::UserNotFound
- elsif screen_name
- where(screen_name: screen_name).order(updated_at: :desc).first or raise Aclog::Exceptions::UserNotFound
- else
- Aclog::Exceptions::UserNotFound
+ def following?(user)
+ raise Aclog::Exceptions::UserNotRegistered unless registered?
+ account.following?(user.id)
+ end
+
+ def private?
+ !registered? || registered? && account.private?
+ end
+
+ def self.find(*args)
+ hash = args.first
+ return super(*args) unless hash.is_a?(Hash)
+
+ hash.each do |key, value|
+ next if value.nil?
+
+ if key == :id
+ return super(value)
+ else
+ ret = where(key => value).order(updated_at: :desc).first
+ if ret
+ return ret
+ else
+ raise ActiveRecord::RecordNotFound, "Couldn't find User with #{key}=#{value}"
+ end
+ end
end
+ raise ActiveRecord::RecordNotFound, "Couldn't find User without any parameter"
end
def self.from_receiver(msg)
@@ -117,7 +137,7 @@ class User < ActiveRecord::Base
ret[user_id][i] = count
end
end
- ret.map(&:flatten).sort_by {|user_id, favorites_count, retweets_count| -(favorites_count + retweets_count) }
+ ret.map(&:flatten).sort_by {|user_id, *counts| -counts.sum }
end
end
diff --git a/app/views/about/api.html.haml b/app/views/about/api.html.haml
index 1c5778b..441f34d 100644
--- a/app/views/about/api.html.haml
+++ b/app/views/about/api.html.haml
@@ -2,106 +2,11 @@
- caption :title
- sidebar :i
%p
- **ドキュメントは書き直します**
+ aclog では JSON 形式の API を提供しています。
%p
- すべての API は GET です。
+ = link_to "API ドキュメント", api_docs_path
%p
- 鍵垢のページは OAuth Echo を使います。
- X-Auth-Service-Provider は https://api.twitter.com/1.1/account/verify_credentials.json です
-%h4 /api/tweets/show
-%dl
- %dd ツイート表示
- %dt URL
- %dd /api/tweets/show.json
- %dt パラメータ
- %dd id=(tweet_id)
-%h4 /api/tweets/(best|favorited|retweeted|recent)
-%dl
- %dd ユーザーのベストツイート
- %dd 最近3日間のベストツイート
- %dd /api/tweets/best.json
- %dd /api/tweets/recent.json
- %dt パラメータ
- %dd screen_name=(screen_name)
- %dd user_id=(user_id)
- %dd
- %strong どちらか必須
- %dd count=(1..100)
- %dd 一度に返す上限
- %dd page=(1..)
- %dd ページ番号
-%h4 /api/tweets/timeline
-%dl
- %dd 最新ツイート
- %dd /api/tweets/timeline.json
- %dt パラメータ
- %dd screen_name=(screen_name)
- %dd user_id=(user_id)
- %dd
- %strong どちらか必須
- %dd count=(1..100)
- %dd 一度に返す上限
- %dd page=(1..)
- %dd ページ番号
- %dd max_id / since_id
- %dd Twitter API のものと同じです
-%h4 /api/tweets/(discoveries|favorites|retweets)
-%dl
- %dd ユーザーが反応したツイート
- %dt URL
- %dd /api/tweets/discoveries.json
- %dd /api/tweets/favorites.json
- %dd /api/tweets/retweets.json
- %dt パラメータ
- %dd screen_name=(screen_name)
- %dd user_id=(user_id)
- %dd
- %strong どちらか必須
- %dd count=(1..100)
- %dd 一度に返す上限
- %dd page=(1..)
- %dd ページ番号
- %dd max_id / since_id
- %dd Twitter API のものと同じです
-%h4 /api/tweets/discovered_by
-%dl
- %dd B が反応した A の最新ツイート
- %dt URL
- %dd /api/tweets/discovered_by.json
- %dt パラメータ
- %dd screen_name=(screen_name)
- %dd user_id=(user_id)
- %dd
- %strong どちらか必須
- %dd screen_name_b=(screen_name)
- %dd user_id_b=(user_id)
- %dd
- %strong どちらか必須
- %dd count=(1..100)
- %dd 一度に返す上限
- %dd page=(1..)
- %dd ページ番号
- %dd max_id / since_id
- %dd Twitter API のものと同じです
-%h4 /api/users/stats
-%dl
- %dd ユーザーの統計
- %dt URL
- %dd /api/users/stats.json
- %dt パラメータ
- %dd screen_name=(screen_name)
- %dd user_id=(user_id)
- %dd
- %strong どちらか必須
-%h4 /api/users/discovered_by
-%h4 /api/users/discovered_users
-%dl
- %dd ユーザーがふぁぼ・RTしたユーザー・ユーザーをふぁぼ・RTしたユーザーを多い順に50人まで返します
- %dt URL
- %dd /api/users/discovered_by.json
- %dd /api/users/discovered_users.json
- %dt パラメータ
- %dd screen_name=(screen_name)
- %dd user_id=(user_id)
- %dd
- %strong どちらか必須
+ 非公開アカウントのデータを取得するために OAuth Echo を使う場合はこれを使用してください。<br />
+ X-Auth-Service-Provider: https://api.twitter.com/1.1/account/verify_credentials.json
+
+
diff --git a/app/views/apidocs/endpoint.html.haml b/app/views/apidocs/endpoint.html.haml
new file mode 100644
index 0000000..04a9afd
--- /dev/null
+++ b/app/views/apidocs/endpoint.html.haml
@@ -0,0 +1,22 @@
+- title "API Documentation: "
+- caption nil
+- sidebar :i
+%ul.breadcrumb
+ %li= link_to "Documentation", api_docs_path
+ %li.active= @endpoint
+%h2.page-header= @endpoint
+%div
+ %div= @endpoint.description
+ %h3 Parameters
+ %table.table
+ %tbody
+ - @endpoint.parameters.each do |parameter|
+ %tr
+ %td
+ %strong= parameter.name
+ - if parameter.required?
+ %small required
+ - else
+ %small optional
+ %td
+ %p= parameter.description
diff --git a/app/views/apidocs/index.html.haml b/app/views/apidocs/index.html.haml
new file mode 100644
index 0000000..8a65b8c
--- /dev/null
+++ b/app/views/apidocs/index.html.haml
@@ -0,0 +1,18 @@
+- title "API documentation"
+- caption nil
+- sidebar :i
+%ul.breadcrumb
+ %li.active Documentation
+%h2.page-header Resources
+- @resources.each do |name, resource|
+ %h3= resource.name
+ %table.table
+ %thead
+ %th Resource
+ %th Description
+ %tbody
+ - resource.endpoints.each do |action, endpoint|
+ %tr
+ %td= link_to endpoint, api_docs_endpoint_path(name, action)
+ %td= endpoint.description
+
diff --git a/app/views/layouts/_base.html.haml b/app/views/layouts/_base.html.haml
index 2364b50..16c7c00 100644
--- a/app/views/layouts/_base.html.haml
+++ b/app/views/layouts/_base.html.haml
@@ -7,6 +7,10 @@
= javascript_include_tag "application"
= javascript_include_tag params[:controller]
%meta{name: "viewport", content: "width=600px"}
+ - if @next_url
+ %link{rel: "next", href: @next_url}
+ - if @prev_url
+ %link{rel: "prev", href: @prev_url}
%body
.navbar.navbar-default.navbar-static-top
.container
diff --git a/app/views/layouts/tweets.html.haml b/app/views/layouts/tweets.html.haml
new file mode 100644
index 0000000..bdf8fa0
--- /dev/null
+++ b/app/views/layouts/tweets.html.haml
@@ -0,0 +1,7 @@
+= render layout: "layouts/base" do
+ .row
+ .col-sm-3
+ .sidebar= yield :sidebar
+ .col-sm-9
+ %h1= yield :caption
+ .tweets= yield
diff --git a/app/views/shared/sidebar/_users.html.haml b/app/views/shared/sidebar/_users.html.haml
index 9c4c1a9..7a36208 100644
--- a/app/views/shared/sidebar/_users.html.haml
+++ b/app/views/shared/sidebar/_users.html.haml
@@ -18,12 +18,10 @@
= "@#{@user.screen_name} #{t("views.sidebar.user_not_registered")}"
.user_nav
.list-group
- - if authorized_to_show_best?(@user)
+ - if allowed_to_see_best?(@user)
= link_to "best", user_path(@user.screen_name), class: "list-group-item"
- = link_to "timeline", user_timeline_path(@user.screen_name), class: "list-group-item"
- = link_to "discoveries", user_discoveries_path(@user.screen_name), class: "list-group-item"
+ = link_to "timeline", user_timeline_path(@user.screen_name), class: "list-group-item"
+ = link_to "discoveries", user_discoveries_path(@user.screen_name), class: "list-group-item"
+ - if allowed_to_see_best?(@user)
= link_to "discovered by", user_discovered_by_path(@user.screen_name), class: "list-group-item"
= link_to "discovered users", user_discovered_users_path(@user.screen_name), class: "list-group-item"
- - else
- = link_to "timeline", user_path(@user.screen_name), class: "list-group-item"
- = link_to "discoveries", user_discoveries_path(@user.screen_name), class: "list-group-item"
diff --git a/app/views/tweets/_tweet.html.haml b/app/views/tweets/_tweet.html.haml
index 40ca4ca..73e6ab9 100644
--- a/app/views/tweets/_tweet.html.haml
+++ b/app/views/tweets/_tweet.html.haml
@@ -17,11 +17,7 @@
- if tweet.in_reply_to
%span.in_reply_to= link_to "in reply to @" + tweet.in_reply_to.user_screen_name, tweet_path(tweet.in_reply_to.id)
.text
- - if authorized_to_show_user?(tweet.user)
- = simple_format(format_tweet_text(tweet.text))
- - else
- %span.quiet
- ユーザーは非公開です
+ = simple_format(format_tweet_text(tweet.text))
.meta.clearfix
%span.twitter_bird
= link_to image_tag("bird_gray_16.png", alt: "Twitter"), tweet.twitter_url, target: "_blank"
@@ -30,7 +26,7 @@
- if favorites_truncated?(tweet)
%span.show-full= link_to "show full", tweet_path(tweet.id, full: true)
%span.source
- = raw "via " + link_to(*tweet.source_link_to_params)
+ = raw "via " + link_to_source_text(tweet.source)
.stats
%dl
- [["favs", tweet.favorites_count, tweet.favoriters], ["retweets", tweet.retweets_count, tweet.retweeters]].each do |title, count, users|
diff --git a/app/views/tweets/_tweets.atom.builder b/app/views/tweets/_tweets.atom.builder
new file mode 100644
index 0000000..11dea91
--- /dev/null
+++ b/app/views/tweets/_tweets.atom.builder
@@ -0,0 +1,19 @@
+atom_feed do |feed|
+ feed.title yield :title
+ feed.subtitle yield :caption
+ feed.updated DateTime.now
+
+ @tweets.each do |tweet|
+ feed.entry(tweet) do |entry|
+ entry.title "#{tweet.favorites_count}/#{tweet.retweets_count}: #{CGI.unescapeHTML(strip_tags(format_tweet_text(tweet.text)))}"
+ entry.updated Time.now.iso8601
+ entry.summary "Has been favorited by #{tweet.favorites_count} #{tweet.favorites_count != 1 ? "people" : "person"}, " +
+ "retweeted by #{tweet.retweets_count} #{tweet.retweets_count != 1 ? "people" : "person"}."
+ entry.author do |author|
+ author.name "#{tweet.user.name} (@#{tweet.user.screen_name})"
+ author.uri tweet.user.twitter_url
+ end
+ end
+ end
+end
+
diff --git a/app/views/tweets/_tweets.html.haml b/app/views/tweets/_tweets.html.haml
deleted file mode 100644
index 2889984..0000000
--- a/app/views/tweets/_tweets.html.haml
+++ /dev/null
@@ -1,8 +0,0 @@
-.tweets= render @tweets.includes(:user)
-- if @tweets.count > 0
- .loading= image_tag "loading.gif", alt: "loading...", title: nil
- .pagination
- - if @tweets.offset_value != nil
- = link_to raw("Next &#8250;"), params.merge(page: [params[:page].to_i, 1].max + 1), rel: :next
- - else
- = link_to raw("Next &#8250;"), params.merge(max_id: @tweets.last.id - 1), rel: :next
diff --git a/app/views/tweets/_tweets.json.jbuilder b/app/views/tweets/_tweets.json.jbuilder
index f3acf04..c5bcd47 100644
--- a/app/views/tweets/_tweets.json.jbuilder
+++ b/app/views/tweets/_tweets.json.jbuilder
@@ -1,4 +1 @@
-json.array! @tweets do |json, tweet|
- json.partial! "tweet", tweet: tweet
-end
-
+json.array!(@tweets, partial: "tweet", as: :tweet)
diff --git a/app/views/tweets/_tweets.rss.builder b/app/views/tweets/_tweets.rss.builder
deleted file mode 100644
index 2805ace..0000000
--- a/app/views/tweets/_tweets.rss.builder
+++ /dev/null
@@ -1,20 +0,0 @@
-xml.instruct! :xml
-xml.rss version: "2.0", "xmlns:atom" => "http://www.w3.org/2005/Atom" do
- xml.channel do
- xml.title title
- xml.description caption
- xml.link url_for(rss: nil, only_path: false)
- xml.__send__(:"atom:link", rel: "self", href: request.url, type: "application/rss+xml")
- @tweets.each_with_index do |tweet, i|
- xml.item do
- xml.title "#{tweet.favorites_count}/#{tweet.retweets_count}: #{CGI.unescapeHTML(strip_tags(format_tweet_text(tweet.text)))}"
- xml.description "Has been favorited by #{tweet.favorites_count} #{tweet.favorites_count != 1 ? "people" : "person"}, " +
- "retweeted by #{tweet.retweets_count} #{tweet.retweets_count != 1 ? "people" : "person"}."
- xml.pubDate tweet.tweeted_at.rfc2822
- xml.link tweet_url(tweet.id)
- xml.guid tweet_url(tweet.id) + "#" + tweet.reactions_count.to_s
- end
- end
- end
-end
-
diff --git a/app/views/tweets/all_best.html.haml b/app/views/tweets/all_best.html.haml
index 7473321..581e592 100644
--- a/app/views/tweets/all_best.html.haml
+++ b/app/views/tweets/all_best.html.haml
@@ -1,4 +1,4 @@
- title "Top Tweets"
- caption :title
- sidebar :i
-= render "tweets"
+= render @tweets
diff --git a/app/views/tweets/all_recent.html.haml b/app/views/tweets/all_recent.html.haml
index 25f901d..b2ceb40 100644
--- a/app/views/tweets/all_recent.html.haml
+++ b/app/views/tweets/all_recent.html.haml
@@ -1,4 +1,4 @@
- title "Top Recent Tweets"
- caption :title
- sidebar :i
-= render "tweets"
+= render @tweets
diff --git a/app/views/tweets/all_timeline.atom.builder b/app/views/tweets/all_timeline.atom.builder
new file mode 100644
index 0000000..2e93b93
--- /dev/null
+++ b/app/views/tweets/all_timeline.atom.builder
@@ -0,0 +1,4 @@
+title "Newest"
+caption :title
+xml << render("tweets")
+
diff --git a/app/views/tweets/all_timeline.html.haml b/app/views/tweets/all_timeline.html.haml
index 67a56d9..3ee3b23 100644
--- a/app/views/tweets/all_timeline.html.haml
+++ b/app/views/tweets/all_timeline.html.haml
@@ -1,4 +1,4 @@
- title "Newest"
- caption :title
- sidebar :i
-= render "tweets"
+= render @tweets
diff --git a/app/views/tweets/best.html.haml b/app/views/tweets/best.html.haml
index ff1d111..6c12ef4 100644
--- a/app/views/tweets/best.html.haml
+++ b/app/views/tweets/best.html.haml
@@ -1,4 +1,4 @@
- title @user.screen_name + "'s Best Tweets"
- caption :title
- sidebar :users
-= render "tweets"
+= render @tweets
diff --git a/app/views/tweets/discovered_by.atom.builder b/app/views/tweets/discovered_by.atom.builder
new file mode 100644
index 0000000..18597db
--- /dev/null
+++ b/app/views/tweets/discovered_by.atom.builder
@@ -0,0 +1,4 @@
+title @user.screen_name + "'s Discovered by " + @source_user.screen_name
+caption :title
+xml << render("tweets")
+
diff --git a/app/views/tweets/discovered_by.html.haml b/app/views/tweets/discovered_by.html.haml
index db190b6..63b0aeb 100644
--- a/app/views/tweets/discovered_by.html.haml
+++ b/app/views/tweets/discovered_by.html.haml
@@ -1,4 +1,4 @@
-- title @user.screen_name + "'s Discovered by " + @user_b.screen_name
+- title @user.screen_name + "'s Discovered by " + @source_user.screen_name
- caption :title
- sidebar :users
-= render "tweets"
+= render @tweets
diff --git a/app/views/tweets/discoveries.atom.builder b/app/views/tweets/discoveries.atom.builder
new file mode 100644
index 0000000..c4ecede
--- /dev/null
+++ b/app/views/tweets/discoveries.atom.builder
@@ -0,0 +1,4 @@
+title @user.screen_name + "'s Discoveries"
+caption :title
+xml << render("tweets")
+
diff --git a/app/views/tweets/discoveries.html.haml b/app/views/tweets/discoveries.html.haml
index 07c741e..bebf34a 100644
--- a/app/views/tweets/discoveries.html.haml
+++ b/app/views/tweets/discoveries.html.haml
@@ -1,4 +1,4 @@
- title @user.screen_name + "'s Discoveries"
- caption :title
- sidebar :users
-= render "tweets"
+= render @tweets
diff --git a/app/views/tweets/favorites.html.haml b/app/views/tweets/favorites.html.haml
index 07c741e..bebf34a 100644
--- a/app/views/tweets/favorites.html.haml
+++ b/app/views/tweets/favorites.html.haml
@@ -1,4 +1,4 @@
- title @user.screen_name + "'s Discoveries"
- caption :title
- sidebar :users
-= render "tweets"
+= render @tweets
diff --git a/app/views/tweets/recent.html.haml b/app/views/tweets/recent.html.haml
index d9b8d28..46d6d34 100644
--- a/app/views/tweets/recent.html.haml
+++ b/app/views/tweets/recent.html.haml
@@ -1,4 +1,4 @@
- title @user.screen_name + "'s Recent Best Tweets"
- caption :title
- sidebar :users
-= render "tweets"
+= render @tweets
diff --git a/app/views/tweets/retweets.html.haml b/app/views/tweets/retweets.html.haml
index 07c741e..bebf34a 100644
--- a/app/views/tweets/retweets.html.haml
+++ b/app/views/tweets/retweets.html.haml
@@ -1,4 +1,4 @@
- title @user.screen_name + "'s Discoveries"
- caption :title
- sidebar :users
-= render "tweets"
+= render @tweets
diff --git a/app/views/tweets/search.html.haml b/app/views/tweets/search.html.haml
index abf10a2..0763b22 100644
--- a/app/views/tweets/search.html.haml
+++ b/app/views/tweets/search.html.haml
@@ -6,5 +6,5 @@
= field_set_tag do
= text_field_tag :q, params[:q]
= submit_tag "search", class: "btn", name: nil
-= render "tweets"
+= render @tweets
diff --git a/app/views/tweets/show.html.haml b/app/views/tweets/show.html.haml
index e086f17..bd7dc10 100644
--- a/app/views/tweets/show.html.haml
+++ b/app/views/tweets/show.html.haml
@@ -1,4 +1,4 @@
- title CGI.unescapeHTML(strip_tags(format_tweet_text(@tweet.text))) + " from @" + @user.screen_name
- caption @user.screen_name + "'s Tweet"
- sidebar :users
-.tweets= render @tweet
+= render @tweet
diff --git a/app/views/tweets/timeline.atom.builder b/app/views/tweets/timeline.atom.builder
new file mode 100644
index 0000000..ca1ad59
--- /dev/null
+++ b/app/views/tweets/timeline.atom.builder
@@ -0,0 +1,4 @@
+title @user.screen_name + "'s Newest"
+caption :title
+xml << render("tweets")
+
diff --git a/app/views/tweets/timeline.html.haml b/app/views/tweets/timeline.html.haml
index 04769c4..c326087 100644
--- a/app/views/tweets/timeline.html.haml
+++ b/app/views/tweets/timeline.html.haml
@@ -1,4 +1,4 @@
- title @user.screen_name + "'s Newest"
- caption :title
- sidebar :users
-= render "tweets"
+= render @tweets
diff --git a/app/views/users/_user_ranking.html.haml b/app/views/users/_user_ranking.html.haml
deleted file mode 100644
index 2d4021e..0000000
--- a/app/views/users/_user_ranking.html.haml
+++ /dev/null
@@ -1,19 +0,0 @@
-- title "User ranking"
-- caption :title
-- sidebar :users
-.users
- %ul.list-inline
- - @result.each do |user_id, favorites_count, retweets_count|
- - target = User.find(user_id)
- %li
- .avatar
- = link_to user_path(target.screen_name) do
- = image_tag target.profile_image_url, alt: target.screen_name, title: target.name
- .data
- .count
- - if params[:action] == "discovered_by"
- = link_to favorites_count + retweets_count, user_discovered_by_user_path(@user.screen_name, target.screen_name)
- - elsif params[:action] == "discovered_users"
- = link_to favorites_count + retweets_count, user_discovered_by_user_path(target.screen_name, @user.screen_name)
- - else
- - raise Exception
diff --git a/app/views/users/_user_ranking.json.jbuilder b/app/views/users/_users_list.json.jbuilder
index e544fc6..57bcb32 100644
--- a/app/views/users/_user_ranking.json.jbuilder
+++ b/app/views/users/_users_list.json.jbuilder
@@ -3,3 +3,4 @@ json.array! @result do |user_id, favorites_count, retweets_count|
json.favorites_count favorites_count
json.retweets_count retweets_count
end
+
diff --git a/app/views/users/discovered_by.html.haml b/app/views/users/discovered_by.html.haml
new file mode 100644
index 0000000..c1986bf
--- /dev/null
+++ b/app/views/users/discovered_by.html.haml
@@ -0,0 +1,14 @@
+- title @user.screen_name + " is Discovered by ..."
+- caption :title
+- sidebar :users
+.users
+ %ul.list-inline
+ - @result.each do |user_id, reactions_count|
+ - user = @cached_users[user_id]
+ %li
+ .avatar
+ = link_to user_path(user.screen_name) do
+ = image_tag user.profile_image_url, alt: user.screen_name, title: user.name
+ .data
+ .count
+ = link_to reactions_count, user_discovered_by_user_path(@user.screen_name, user.screen_name)
diff --git a/app/views/users/discovered_users.html.haml b/app/views/users/discovered_users.html.haml
new file mode 100644
index 0000000..3d11f25
--- /dev/null
+++ b/app/views/users/discovered_users.html.haml
@@ -0,0 +1,14 @@
+- title @user.screen_name + " is Discovering ..."
+- caption :title
+- sidebar :users
+.users
+ %ul.list-inline
+ - @result.each do |user_id, reactions_count|
+ - user = @cached_users[user_id]
+ %li
+ .avatar
+ = link_to user_path(user.screen_name) do
+ = image_tag user.profile_image_url, alt: user.screen_name, title: user.name
+ .data
+ .count
+ = link_to reactions_count, user_discovered_by_user_path(user.screen_name, @user.screen_name)
diff --git a/app/views/users/stats.html.haml b/app/views/users/stats.html.haml
index 285da92..9bb08c1 100644
--- a/app/views/users/stats.html.haml
+++ b/app/views/users/stats.html.haml
@@ -3,31 +3,11 @@
- sidebar :users
.avatar
= image_tag @user.profile_image_url_original, alt: @user.screen_name, width: 128, height: 128
-%p profile
-%dl.dl-horizontal
- %dt Username
- %dd= @user_twitter.screen_name
- %dt Name
- %dd= raw @user_twitter.name.gsub(/&(?!(amp;|gt;|lt;|quot;|apos;))/, "&amp;")
- %dt tweets
- %dd= @user_twitter.statuses_count
- %dt Following
- %dd= @user_twitter.friends_count
- %dt Followers
- %dd= @user_twitter.followers_count
- %dt Favorites
- %dd= @user_twitter.favourites_count
- %dt Listed
- %dd= @user_twitter.listed_count
- %dt Bio
- %dd= raw @user_twitter.description.gsub(/&(?!(amp;|gt;|lt;|quot;|apos;))/, "&amp;")
- if @user.registered? && @user.account.active?
%p records
%dl.dl-horizontal
- %dt Favorited
- %dd= @user.stats.favorited_count
- %dt Retweeted
- %dd= @user.stats.retweeted_count
+ %dt Received reactions
+ %dd= @user.stats.reactions_count
%dt Registered
%dd= format_time @user.account.created_at
%dt Last update
diff --git a/config/application.rb b/config/application.rb
index 38fa741..e13c899 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -1,10 +1,10 @@
require File.expand_path('../boot', __FILE__)
require 'rails/all'
-
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(:default, Rails.env)
+require "./lib/apidoc"
module Aclog
class Application < Rails::Application
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 37cf220..eeee0e9 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -10,7 +10,7 @@ Aclog::Application.configure do
config.eager_load = false
# Show full error reports and disable caching.
- config.consider_all_requests_local = false#true
+ config.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Don't care if the mailer can't send.
diff --git a/config/routes.rb b/config/routes.rb
index 838d222..9e761ec 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -18,7 +18,7 @@ Aclog::Application.routes.draw do
scope "/i/settings", controller: "settings" do
get "/", action: "index", as: "settings"
- post "/update", action: "update"
+ post "/update", action: "update"
get "/confirm_deactivation", action: "confirm_deactivation"
post "/deactivate", action: "deactivate"
end
@@ -26,6 +26,10 @@ Aclog::Application.routes.draw do
scope "/about", controller: "about" do
get "/", action: "about", as: "about"
get "/api", action: "api", as: "about_api"
+ scope "/api/docs", controller: "apidocs" do
+ get "/", action: "index", as: "api_docs"
+ get "/:resource/:name", action: "endpoint", as: "api_docs_endpoint"
+ end
end
scope "/help", controller: "help" do
@@ -36,6 +40,7 @@ Aclog::Application.routes.draw do
scope "/:screen_name", controller: "users" do
get "/discovered_by", action: "discovered_by", as: "user_discovered_by"
get "/discovered_users", action: "discovered_users", as: "user_discovered_users"
+ get "/stats", action: "stats", as: "user_stats"
end
# Twitter redirect
@@ -43,7 +48,7 @@ Aclog::Application.routes.draw do
end
# HTML or RSS
- scope controller: "tweets", constraints: {format: /(html|rss)/} do
+ scope controller: "tweets", constraints: {format: /(html|atom)/} do
scope "i" do
get "/best", action: "all_best", as: "best"
get "/recent", action: "all_recent", as: "recent"
@@ -53,14 +58,14 @@ Aclog::Application.routes.draw do
# TweetController / Tweets
scope "/:screen_name" do
- get "/", action: "index", as: "user"
- get "/best", action: "best", as: "user_best"
- get "/recent", action: "recent", as: "user_recent"
- get "/timeline", action: "timeline", as: "user_timeline"
- get "/discoveries", action: "discoveries", as: "user_discoveries"
- get "/favorites", action: "favorites", as: "user_favorites"
- get "/retweets", action: "retweets", as: "user_retweets"
- get "/discovered_by/:screen_name_b", action: "discovered_by", as: "user_discovered_by_user"
+ get "/", action: "index", as: "user"
+ get "/best", action: "best", as: "user_best"
+ get "/recent", action: "recent", as: "user_recent"
+ get "/timeline", action: "timeline", as: "user_timeline"
+ get "/discoveries", action: "discoveries", as: "user_discoveries"
+ get "/favorites", action: "favorites", as: "user_favorites"
+ get "/retweets", action: "retweets", as: "user_retweets"
+ get "/discovered_by/:source_screen_name", action: "discovered_by", as: "user_discovered_by_user"
end
end
end
diff --git a/lib/aclog/exceptions.rb b/lib/aclog/exceptions.rb
index e9ea212..9b7078f 100644
--- a/lib/aclog/exceptions.rb
+++ b/lib/aclog/exceptions.rb
@@ -15,5 +15,7 @@ module Aclog
class UserNotRegistered < UserError; end
class UserProtected < UserError; end
class AccountPrivate < UserError; end
+
+ class DocumentNotFound < StandardError; end
end
end
diff --git a/lib/apidoc.rb b/lib/apidoc.rb
new file mode 100644
index 0000000..fe64f6b
--- /dev/null
+++ b/lib/apidoc.rb
@@ -0,0 +1,12 @@
+Dir.glob(File.expand_path("../apidoc/**/*.rb", __FILE__)) {|file| require file }
+
+module Apidoc
+ extend self
+
+ def resources
+ @@resources ||= {}
+ end
+
+ def reload_docs
+ end
+end
diff --git a/lib/apidoc/controller_dsl.rb b/lib/apidoc/controller_dsl.rb
new file mode 100644
index 0000000..61164ca
--- /dev/null
+++ b/lib/apidoc/controller_dsl.rb
@@ -0,0 +1,45 @@
+module Apidoc
+ module ControllerDsl
+ include Resources
+ include Endpoints
+ include Parameters
+
+ private
+ def method_added(method_name)
+ super(method_name)
+
+ if _apidoc_endpoint_started?
+ orig_method = self.instance_method(method_name)
+ current_endpoint = _apidoc_current_endpoint
+ _apidoc_resource.endpoints[method_name] = _apidoc_current_endpoint
+ self._apidoc_current_endpoint = nil
+
+ define_method(method_name) do |*args|
+ current_endpoint.validate!(params)
+ orig_method.bind(self).call(*args)
+ end
+ end
+ end
+
+ def _apidoc_resource
+ name = self.name.sub(/Controller$/, "").underscore
+ Apidoc.resources[name.to_sym] ||= Resource.new(name.titleize)
+ end
+
+ def _apidoc_current_endpoint
+ @_apidoc_current_endpoint || raise(DslError, "Endpoint definition is not started.")
+ end
+
+ def _apidoc_current_endpoint=(value)
+ @_apidoc_current_endpoint = value
+ end
+
+ def _apidoc_endpoint_started?
+ @_apidoc_current_endpoint.present?
+ end
+
+ def _apidoc_param_groups
+ @_apidoc_param_groups ||= {}
+ end
+ end
+end
diff --git a/lib/apidoc/controller_dsl/endpoint.rb b/lib/apidoc/controller_dsl/endpoint.rb
new file mode 100644
index 0000000..5056920
--- /dev/null
+++ b/lib/apidoc/controller_dsl/endpoint.rb
@@ -0,0 +1,30 @@
+module Apidoc
+ module ControllerDsl
+ module Endpoints
+ def get(endpoint)
+ _apidoc_endpoint(:get, endpoint)
+ end
+
+ def post(endpoint)
+ _apidoc_endpoint(:post, endpoint)
+ end
+
+ def _apidoc_endpoint(method, endpoint)
+ if _apidoc_endpoint_started?
+ raise DslError, "Previous endpoint #{_apidoc_current_endpoint} definition is not completed."
+ end
+
+ self._apidoc_current_endpoint = Endpoint.new(method, endpoint)
+ end
+
+ def description(description)
+ _apidoc_current_endpoint.description = description
+ end
+
+ def see(action_name)
+ _apidoc_current_endpoint.sees << action_name.to_sym
+ end
+ end
+ end
+end
+
diff --git a/lib/apidoc/controller_dsl/parameters.rb b/lib/apidoc/controller_dsl/parameters.rb
new file mode 100644
index 0000000..39533b6
--- /dev/null
+++ b/lib/apidoc/controller_dsl/parameters.rb
@@ -0,0 +1,27 @@
+module Apidoc
+ module ControllerDsl
+ module Parameters
+ def requires(name, validation, description)
+ _apidoc_current_endpoint.parameters << Parameter.new(name, validation, description, required: true)
+ end
+
+ def optional(name, validation, description)
+ _apidoc_current_endpoint.parameters << Parameter.new(name, validation, description, required: false)
+ end
+
+ def param_group(name, &blk)
+ if block_given?
+ _apidoc_param_groups[name] = blk
+ else
+ blk = _apidoc_param_groups[name]
+ if blk
+ blk.call
+ else
+ raise DslError, "Parameters group #{name} is not defined."
+ end
+ end
+ end
+ end
+ end
+end
+
diff --git a/lib/apidoc/controller_dsl/resources.rb b/lib/apidoc/controller_dsl/resources.rb
new file mode 100644
index 0000000..110b564
--- /dev/null
+++ b/lib/apidoc/controller_dsl/resources.rb
@@ -0,0 +1,12 @@
+module Apidoc
+ module ControllerDsl
+ module Resources
+ private
+ def resource_description(description)
+ _apidoc_resource.description = description
+ end
+ end
+ end
+end
+
+
diff --git a/lib/apidoc/endpoint.rb b/lib/apidoc/endpoint.rb
new file mode 100644
index 0000000..7d44007
--- /dev/null
+++ b/lib/apidoc/endpoint.rb
@@ -0,0 +1,24 @@
+module Apidoc
+ class Endpoint
+ attr_reader :method, :name, :parameters, :sees
+ attr_accessor :description
+
+ def initialize(method, name)
+ @method = method
+ @name = name
+ @parameters = []
+ @sees = []
+ @description = nil
+ end
+
+ def to_s
+ "#{method.to_s.upcase} #{name}"
+ end
+
+ def validate!(params)
+ parameters.each do |parameter|
+ parameter.validate!(params)
+ end
+ end
+ end
+end
diff --git a/lib/apidoc/exceptions.rb b/lib/apidoc/exceptions.rb
new file mode 100644
index 0000000..ee4685f
--- /dev/null
+++ b/lib/apidoc/exceptions.rb
@@ -0,0 +1,17 @@
+module Apidoc
+ class Error < StandardError; end
+
+ class ParameterMissing < Error
+ def initialize(param)
+ super("Parameter is missing or the value is empty: #{param}")
+ end
+ end
+
+ class ParameterInvalid < Error
+ def initialize(param)
+ super("Parameter is invalid: #{param}")
+ end
+ end
+
+ class DslError < SyntaxError; end
+end
diff --git a/lib/apidoc/parameter.rb b/lib/apidoc/parameter.rb
new file mode 100644
index 0000000..50bdb75
--- /dev/null
+++ b/lib/apidoc/parameter.rb
@@ -0,0 +1,40 @@
+module Apidoc
+ class Parameter
+ attr_reader :name, :validator, :description, :required
+ alias required? required
+
+ def initialize(name, validation, description, required: false)
+ @name = name
+ @validator = parse_validation(validation)
+ @description = description
+ @required = required
+ end
+
+ def parse_validation(validation)
+ case validation
+ when :integer
+ ->(value) { /^[1-9][0-9]*$/ =~ value }
+ when :string
+ ->(value) { true }
+ when Regexp
+ ->(value) { validation =~ value }
+ else
+ if validation.is_a?(Range) && validation.begin.is_a?(Integer)
+ ->(value) { /^[1-9][0-9]*$/ =~ value && validation === value.to_i }
+ else
+ raise DslError, "Not implemented validation type: #{validation}."
+ end
+ end
+ end
+
+ def validate!(params)
+ if self.required && params[self.name].blank?
+ raise ParameterMissing, self.name
+ end
+
+ if !params[self.name].nil? && !self.validator.call(params[self.name])
+ raise ParameterInvalid, self.name
+ end
+ end
+ end
+end
diff --git a/lib/apidoc/railtie.rb b/lib/apidoc/railtie.rb
new file mode 100644
index 0000000..9e992a3
--- /dev/null
+++ b/lib/apidoc/railtie.rb
@@ -0,0 +1,9 @@
+module Apidoc
+ class Railtie < Rails::Railtie
+ initializer "apidoc.controller_injections" do
+ ActiveSupport.on_load :action_controller do
+ extend ControllerDsl
+ end
+ end
+ end
+end
diff --git a/lib/apidoc/resource.rb b/lib/apidoc/resource.rb
new file mode 100644
index 0000000..b9afdf3
--- /dev/null
+++ b/lib/apidoc/resource.rb
@@ -0,0 +1,12 @@
+module Apidoc
+ class Resource
+ attr_reader :endpoints, :name
+ attr_accessor :description
+
+ def initialize(name)
+ @name = name
+ @description = nil
+ @endpoints = {}
+ end
+ end
+end