require 'sinatra/base' require 'redis' require 'json' require_relative 'models' require_relative 'akismet' module Cachai class Middleware < Sinatra::Base # set :database_file, "config/database.yml" # set :public_folder, File.join(settings.root, 'public') # set :protection, true # set :logging, nil # use Rack::CommonLogger, LOGGER # use Rack::Static, :urls => %w(/css /img /js /favicon.ico), :root => 'public' use ActiveRecord::ConnectionAdapters::ConnectionManagement error do 'Oops, nasty error: ' + env['sinatra.error'].message end def initialize(app, opts = {}) domain = opts.delete(:domain) or raise 'Domain required.' redis_host = opts.delete(:redis_host) || 'localhost' @duration = opts.delete(:duration) Cachai.boot(domain, redis_host) if key = opts.delete(:akismet_key) @akismet = Akismet.new(:api_key => key, :blog => "http://#{Cachai.domain}") else puts "No Akismet key found! Will not check comments for spam." end if mailgun_opts = opts.delete(:mailgun) require 'rest-client' @mailgun_domain = mailgun_opts[:domain] @mailgun_api_key = mailgun_opts[:api_key] @recipient = opts.delete(:recipient) or raise "No recipient set!" end super(app) end get '/pingbacks.?:format?' do # TODO end post '/pingbacks.?:format?' do # TODO end get '/comments.?:format?' do check_domain!(params[:domain]) # puts "Comments for: #{params[:domain]}#{params[:path]}" json_list = Cachai.get_comments_for(params[:path], params[:nocache]) if params[:callback] content_type 'application/javascript' "#{params[:callback]}(#{json_list});" else json(json_list) end end post '/comments.?:format?' do begin data = params[:format] == 'json' ? JSON.parse(request.body.read) : params if data[:url] # got single URL with protocol://domain/path url = URI.parse(data[:url]) data['domain'] = url.host data['protocol'] = url.schema data['path'] = url.path end check_domain!(data['domain']) if data['protocol'].blank? or data['path'].blank? return halt(422, "Missing params.") end post = Post.find_or_create_by_path(data['path']) if post.comments_closed?(@duration && @duration.days) return halt(400, "Comments closed.") end permalink = 'http://' + data['domain'] + data['path'] if is_spam?(data, permalink, request) return halt(401, "No spam allowed.") end attrs = { :content => data['content'], :author_name => data['author_name'], :author_email => data['author_email'], :author_url => data['author_url'], :parent_id => data['parent_id'].to_i, :author_ip => request.ip } response = Response.create!(attrs.merge(:post_id => post.id)) Cachai.clear_cache(data['path']) notify_new_response(response, data['path']) if @recipient headers['Access-Control-Allow-Origin'] = data['protocol'] + '//' + data['domain'] json({ :status => 'ok', :comment => response }) rescue JSON::ParserError status 400 and json({ :error => 'Invalid JSON.' }) rescue ActiveRecord::RecordInvalid => e status 422 and json({ :error => e.message }) rescue => e puts e.message puts e.backtrace.join("\n") status 500 and "Something went wrong." end end private =begin def set_cache(timestamp) return if timestamp.nil? last_modified timestamp cache_control :public, :must_revalidate, :max_age => 60 end def prevent_cache cache_control :public, :no_cache, :no_store, :must_revalidate, :max_age => 0 # expires 1.year.ago end =end def check_domain!(domain) halt(400, 'Invalid domain.') unless domain == Cachai.domain end def not_found(message = nil) halt(404, message || 'Not found.') end def json(obj) content_type 'application/json' return obj.is_a?(String) ? obj : obj.to_json end def is_spam?(data, link, request) return false unless @akismet # return true if blacklisted?(name, email, content) comment = { :user_ip => request.ip, :referrer => request.referrer, :user_agent => request.user_agent, :permalink => link, :blog => 'http://' + data['domain'], :comment_type => 'comment', :comment_content => data['content'], :comment_author => data['author_name'], :comment_author_url => data['author_url'], :comment_author_email => data['author_email'] } if resp = @akismet.check_comment(comment) # puts resp.inspect return resp[:spam] end false end def notify_new_response(response, path) RestClient.post "https://api:#{@mailgun_api_key}"\ "@api.mailgun.net/v3/#{@mailgun_domain}/messages", :from => 'comments@' + Cachai.domain, :to => @recipient, :subject => "New comment from #{response.author_name} at #{path}", :text => "#{response.content}\n\n--\nhttp://#{Cachai.domain}#{path}" rescue => e puts "MAIL ERROR: #{e.message}" end end class Admin < Sinatra::Base # set :public_folder, File.join(settings.root, '..', 'public') # set :views, File.join(settings.root, 'views') # use Rack::Static, :urls => %w(/css /img /js /favicon.ico), :root => 'public' enable :method_override use ActiveRecord::ConnectionAdapters::ConnectionManagement def initialize(opts = {}) require 'tilt/erb' Cachai.load_db! super end get '/' do redirect to('/posts') end get '/posts' do @posts = Cachai::Post.all.order(:id => :desc) deliver @posts, :posts end get '/posts/:id' do @post = Cachai::Post.find(params[:id]) @comments = @post.responses.comment deliver @comments, :comments end get '/comments' do @comments = Cachai::Response.all.order(:created_at => :desc) deliver @comments, :comments end get '/comments/:id' do @comment = Cachai::Response.find(params[:id]) deliver @comment, :comment end put '/comments/:id' do @comment = Cachai::Response.find(params[:id]) if @comment.update_attributes(params[:comment]) begin @comment.post.clear_cache rescue => e puts e.message end end redirect back end def deliver(obj, view) if request.xhr? json(obj) else erb view end end def json(obj) content_type 'application/json' return obj.is_a?(String) ? obj : obj.to_json end end end