cachai 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7d0befb3a839b4323421a53cd76689d3b17f631c
4
+ data.tar.gz: 53f40303c117b425d195244f8b1fe541125dd505
5
+ SHA512:
6
+ metadata.gz: 58bb54f64b485bf09c84db2afc4663640edb5e7e934508f377afe7c3bcecacff9ff9914e418bcb900fed6cafe07954bf57418964e6e0a723320151750bccc768
7
+ data.tar.gz: bd4b23febe2c4e8c36b061aaa5f3d939287f335461b6b35d5ab7ca2055f2e681a1719b050a7ab41ebbe939a3787104b10b7a362a13890d46bce5420669cf64f6
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ db/*.sqlite3
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,63 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ cachai (0.0.1)
5
+ rake
6
+ sinatra
7
+ sinatra-activerecord
8
+ sqlite3
9
+
10
+ GEM
11
+ remote: http://rubygems.org/
12
+ specs:
13
+ activemodel (4.2.1)
14
+ activesupport (= 4.2.1)
15
+ builder (~> 3.1)
16
+ activerecord (4.2.1)
17
+ activemodel (= 4.2.1)
18
+ activesupport (= 4.2.1)
19
+ arel (~> 6.0)
20
+ activesupport (4.2.1)
21
+ i18n (~> 0.7)
22
+ json (~> 1.7, >= 1.7.7)
23
+ minitest (~> 5.1)
24
+ thread_safe (~> 0.3, >= 0.3.4)
25
+ tzinfo (~> 1.1)
26
+ arel (6.0.0)
27
+ builder (3.2.2)
28
+ diff-lcs (1.2.5)
29
+ i18n (0.7.0)
30
+ json (1.8.3)
31
+ minitest (5.7.0)
32
+ rack (1.6.1)
33
+ rack-protection (1.5.3)
34
+ rack
35
+ rake (10.4.2)
36
+ rspec (2.99.0)
37
+ rspec-core (~> 2.99.0)
38
+ rspec-expectations (~> 2.99.0)
39
+ rspec-mocks (~> 2.99.0)
40
+ rspec-core (2.99.2)
41
+ rspec-expectations (2.99.2)
42
+ diff-lcs (>= 1.1.3, < 2.0)
43
+ rspec-mocks (2.99.3)
44
+ sinatra (1.4.6)
45
+ rack (~> 1.4)
46
+ rack-protection (~> 1.4)
47
+ tilt (>= 1.3, < 3)
48
+ sinatra-activerecord (2.0.6)
49
+ activerecord (>= 3.2)
50
+ sinatra (~> 1.0)
51
+ sqlite3 (1.3.10)
52
+ thread_safe (0.3.5)
53
+ tilt (2.0.1)
54
+ tzinfo (1.2.2)
55
+ thread_safe (~> 0.1)
56
+
57
+ PLATFORMS
58
+ ruby
59
+
60
+ DEPENDENCIES
61
+ bundler (~> 1.3)
62
+ cachai!
63
+ rspec (~> 2.6)
data/Procfile ADDED
@@ -0,0 +1,2 @@
1
+ # web: bundle exec ruby ./app.rb -p 4567
2
+ web: bundle exec puma -p 4567
data/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # Commentary
2
+
3
+ Add comments to your blog.
4
+
5
+ ## Setup
6
+ * Edit `config/production.yml` to add your site. Specify a name and the domain where the site will be hosted. Example configuration will be like this:
7
+
8
+ ```
9
+ name: Blog
10
+ domain: blog.sdqali.in
11
+ ```
12
+
13
+ * Run `RACK_ENV=production setup.rb`
14
+ * Start the server with `RACK_ENV=production app.rb`
15
+ * Add the following to your HTML pages or templates. `selector` is the CSS selector for the DOM element where comments will be rendered.
16
+
17
+ ```html
18
+ <script type="text/javascript" src="<server>/jquery-1.10.2.min.js"></script>
19
+ <script type="text/javascript" src="<server>//commentary.js"></script>
20
+ <script type="text/javascript">
21
+ $(document).ready(function() {
22
+ Commentary.initialize("<server>", "<selector>");
23
+ });
24
+ </script>
25
+
26
+ ```
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
data/cachai.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "cachai"
7
+ spec.version = '0.0.1'
8
+ spec.authors = ["Tomás Pollak"]
9
+ spec.email = ["tomas@forkhq.com"]
10
+ spec.description = %q{Middleware for embedabble comments.}
11
+ spec.summary = %q{Middleware for embedabble comments.}
12
+ spec.homepage = ""
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.3"
21
+ spec.add_development_dependency "rspec", "~> 2.6"
22
+
23
+ spec.add_dependency "sinatra"
24
+ spec.add_dependency "sinatra-activerecord"
25
+ spec.add_dependency "sqlite3"
26
+ spec.add_dependency "rake"
27
+ end
data/db/schema.rb ADDED
@@ -0,0 +1,25 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # Note that this schema.rb definition is the authoritative source for your
6
+ # database schema. If you need to create the application database on another
7
+ # system, you should be using db:schema:load, not running all the migrations
8
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema.define(version: 20130901232253) do
14
+ create_table "comments", force: true do |t|
15
+ t.string "author_name", :null => false
16
+ t.string "author_email", :null => false
17
+ t.string "author_url"
18
+ t.text "content", :null => false
19
+ t.text "path", :null => false
20
+ t.datetime "created_at"
21
+ t.datetime "updated_at"
22
+ end
23
+
24
+ add_index :comments, :path
25
+ end
data/lib/akismet.rb ADDED
@@ -0,0 +1,154 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'set'
4
+
5
+ class Akismet
6
+
7
+ attr_accessor :options, :valid_responses, :normal_responses, :standard_headers, :host, :port
8
+
9
+ HOST = 'rest.akismet.com'
10
+ PORT = 80
11
+ TIMEOUT_THRESHOLD = 10
12
+ VALID_RESPONSES = Set.new(['false', ''])
13
+ NORMAL_RESPONSES = VALID_RESPONSES.dup << 'true'
14
+ STANDARD_HEADERS = {
15
+ 'User-Agent' => "Akismet Checker",
16
+ 'Content-Type' => 'application/x-www-form-urlencoded'
17
+ }
18
+
19
+ # Create a new instance of the Akismet class
20
+ #
21
+ # ==== Arguments
22
+ # Arguments are provided in the form of a Hash with the following keys
23
+ # (as Symbols) available:
24
+ #
25
+ # +api_key+:: your Akismet API key
26
+ # +blog+:: the blog associated with your api key
27
+ #
28
+ # The following keys are available and are entirely optional. They are
29
+ # available incase communication with Akismet's servers requires a
30
+ # proxy port and/or host:
31
+ #
32
+ # * +proxy_port+
33
+ # * +proxy_host+
34
+ def initialize(options)
35
+ @options = options
36
+ self.verified_key = false
37
+ end
38
+
39
+ # Returns +true+ if the API key has been verified, +false+ otherwise
40
+ def verified?
41
+ (@verified_key ||= verify_api_key) != :false
42
+ end
43
+
44
+ def invalid_options?
45
+ false
46
+ end
47
+
48
+ def check_comment(options={})
49
+ return false if invalid_options?
50
+ message = call_akismet('comment-check', options)
51
+ {:spam => !VALID_RESPONSES.include?(message), :message => message}
52
+ end
53
+
54
+ def spam?(options = {})
55
+ if resp = check_comment(options)
56
+ return resp[:spam]
57
+ end
58
+ false
59
+ end
60
+
61
+ # This call is for submitting comments that weren't marked as spam but
62
+ # should have been (i.e. false negatives). It takes identical arguments as
63
+ # +check_comment+.
64
+ def mark_as_spam(options={})
65
+ return false if invalid_options?
66
+ {:message => call_akismet('submit-spam', options)}
67
+ end
68
+
69
+ # This call is intended for the marking of false positives, things that
70
+ # were incorrectly marked as spam. It takes identical arguments as
71
+ # +check_comment+ and +mark_as_spam+.
72
+ def mark_as_ham(options={})
73
+ return false if invalid_options?
74
+ {:message => call_akismet('submit-ham', options)}
75
+ end
76
+
77
+ # Returns the URL for an Akismet request
78
+ #
79
+ # ==== Arguments
80
+ # +action+ <~to_s>:: a valid Akismet function name
81
+ #
82
+ # ==== Returns
83
+ # String
84
+ def self.url(action)
85
+ "/1.1/#{action}"
86
+ end
87
+
88
+ protected
89
+ # Internal call to Akismet. Prepares the data for posting to the Akismet
90
+ # service.
91
+ #
92
+ # ==== Arguments
93
+ # +akismet_function+ <String>::
94
+ # the Akismet function that should be called
95
+ #
96
+ # The following keys are available to configure a given call to Akismet:
97
+ #
98
+ # +user_ip+ (*required*)::
99
+ # IP address of the comment submitter.
100
+ # +user_agent+ (*required*)::
101
+ # user agent information.
102
+ # +referrer+ (<i>note spelling</i>)::
103
+ # the content of the HTTP_REFERER header should be sent here.
104
+ # +permalink+::
105
+ # the permanent location of the entry the comment was submitted to.
106
+ # +comment_type+::
107
+ # may be blank, comment, trackback, pingback, or a made up value like
108
+ # "registration".
109
+ # +comment_author+::
110
+ # submitted name with the comment
111
+ # +comment_author_email+::
112
+ # submitted email address
113
+ # +comment_author_url+::
114
+ # commenter URL
115
+ # +comment_content+::
116
+ # the content that was submitted
117
+ def call_akismet(akismet_function, options={})
118
+ http_post http_instance, akismet_function, options.update(:blog => options[:blog])
119
+ end
120
+
121
+ # Call to check and verify your API key. You may then call the
122
+ # <tt>verified?</tt> method to see if your key has been validated
123
+ def verify_api_key
124
+ return :false if invalid_options?
125
+ value = http_post http_instance, 'verify-key', :key => options[:api_key], :blog => options[:blog]
126
+ self.verified_key = (value == "valid") ? true : :false
127
+ end
128
+
129
+ def http_post(http, action, options = {})
130
+ params = options.map{ |key, val| "#{key}=#{URI.encode(val || '')}"}.join('&')
131
+ resp = http.post(self.url(action), params, STANDARD_HEADERS)
132
+ log_request(self.url(action), params, resp)
133
+ resp.body
134
+ end
135
+
136
+ def url(action)
137
+ "/1.1/#{action}"
138
+ end
139
+
140
+ private
141
+
142
+ def log_request(url, data, resp)
143
+ #
144
+ end
145
+
146
+ attr_accessor :verified_key
147
+
148
+ def http_instance
149
+ http = Net::HTTP.new([options[:api_key], HOST].join("."), options[:proxy_host], options[:proxy_port])
150
+ http.read_timeout = http.open_timeout = TIMEOUT_THRESHOLD
151
+ http
152
+ end
153
+
154
+ end
data/lib/cachai.rb ADDED
@@ -0,0 +1,134 @@
1
+ require 'sinatra/base'
2
+ require 'json'
3
+ require 'rake'
4
+ require_relative 'comment'
5
+ require_relative 'akismet'
6
+
7
+ module Cachai
8
+
9
+ class Middleware < Sinatra::Base
10
+
11
+ # set :database_file, "config/database.yml"
12
+ # set :public_folder, File.join(settings.root, 'public')
13
+ # set :protection, true
14
+ use ActiveRecord::ConnectionAdapters::ConnectionManagement
15
+
16
+ def initialize(app, opts = nil)
17
+ opts = opts || {}
18
+ @domain = opts.delete(:domain) or raise 'Domain required.'
19
+
20
+ load_schema unless schema_loaded?
21
+
22
+ if key = opts.delete(:akismet_key)
23
+ @akismet = Akismet.new(:api_key => key, :blog => "http://#{@domain}")
24
+ else
25
+ puts "No Akismet key found! Will not check comments for spam."
26
+ end
27
+
28
+ super(app)
29
+ end
30
+
31
+ get '/comments.?:format?' do
32
+ check_domain!(params[:domain])
33
+
34
+ # puts "Comments for: #{params[:domain]}#{params[:path]}"
35
+ list = get_comments(params[:path])
36
+
37
+ if params[:callback]
38
+ content_type 'application/javascript'
39
+ "#{params[:callback]}(#{list.to_json});"
40
+ else
41
+ json(list)
42
+ end
43
+ end
44
+
45
+ post '/comments.?:format?' do
46
+
47
+ begin
48
+ data = JSON.parse(request.body.read)
49
+ check_domain!(data['domain'])
50
+
51
+ headers['Access-Control-Allow-Origin'] = data['protocol'] + '//' + data['domain']
52
+
53
+ permalink = 'http://' + data['domain'] + data['path']
54
+ halt(400, "No spam allowed") if is_spam?(data, permalink, request)
55
+
56
+ attrs = {
57
+ :path => data['path'],
58
+ :content => data['content'],
59
+ :author_name => data['author_name'],
60
+ :author_email => data['author_email'],
61
+ :author_url => data['author_url']
62
+ }
63
+
64
+ comment = Comment.create!(attrs)
65
+ json({ :status => 'ok', :comment => comment })
66
+
67
+ rescue JSON::ParserError
68
+ status 400 and json({ :error => 'Invalid JSON.' })
69
+ rescue ActiveRecord::RecordInvalid => e
70
+ status 422 and json({ :error => e.message })
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def load_schema
77
+ require 'sinatra/activerecord/rake'
78
+ # Rake::Task['db:schema:load'].invoke
79
+ require_relative '../db/schema.rb'
80
+ end
81
+
82
+ def schema_loaded?
83
+ Comment.first
84
+ true
85
+ rescue ActiveRecord::StatementInvalid => e
86
+ # SQLite3::SQLException => e
87
+ # return !e.message['no such table']
88
+ false
89
+ end
90
+
91
+ def check_domain!(domain)
92
+ halt(400, 'Invalid domain.') unless domain == @domain
93
+ end
94
+
95
+ def not_found(message = nil)
96
+ halt(404, message || 'Not found.')
97
+ end
98
+
99
+ def json(obj)
100
+ content_type 'application/json'
101
+ obj.to_json
102
+ end
103
+
104
+ def get_comments(document_path)
105
+ Comment.where({ :path => document_path })
106
+ end
107
+
108
+ def is_spam?(data, link, request)
109
+ return false unless @akismet
110
+ # return true if blacklisted?(name, email, content)
111
+
112
+ comment = {
113
+ :user_ip => request.ip,
114
+ :referrer => request.referrer,
115
+ :user_agent => request.user_agent,
116
+ :permalink => link,
117
+ :comment_type => 'comment',
118
+ :comment_content => data['content'],
119
+ :comment_author => data['author_name'],
120
+ :comment_author_url => data['author_url'],
121
+ :comment_author_email => data['author_email']
122
+ }
123
+
124
+ if resp = @akismet.check_comment(comment)
125
+ # puts resp.inspect
126
+ return resp[:spam]
127
+ end
128
+
129
+ false
130
+ end
131
+
132
+ end
133
+
134
+ end
data/lib/comment.rb ADDED
@@ -0,0 +1,33 @@
1
+ require 'sinatra/activerecord'
2
+ require 'digest/md5'
3
+ require 'sqlite3'
4
+ require_relative 'time_ago'
5
+
6
+ ENV_NAME = ENV['RACK_ENV'] || 'development'
7
+
8
+ module Cachai
9
+ class Comment < ActiveRecord::Base
10
+
11
+ validates_presence_of :author_name, :author_name, :author_email, :content, :path
12
+
13
+ def as_json(options = {})
14
+ {
15
+ :id => id,
16
+ :author_name => author_name,
17
+ # :author_email => author_email,
18
+ :author_img => author_img,
19
+ :author_url => author_url,
20
+ :content => content,
21
+ :timestamp => created_at.to_i,
22
+ # :created_at => created_at,
23
+ :created_ago => Timeago.since(created_at)
24
+ }
25
+ end
26
+
27
+ def author_img(size = 50)
28
+ id = Digest::MD5::hexdigest(author_email.strip.downcase)
29
+ "https://www.gravatar.com/avatar/#{id}.jpg?s=#{size}"
30
+ end
31
+
32
+ end
33
+ end
data/lib/time_ago.rb ADDED
@@ -0,0 +1,31 @@
1
+ module Timeago
2
+
3
+ def self.in_words(time)
4
+ minutes = (((Time.now - time).abs)/60).round
5
+ return nil if minutes < 0
6
+
7
+ case minutes
8
+ when 0..1 then 'less than a minute'
9
+ when 2..4 then 'less than 5 minutes'
10
+ when 5..14 then 'less than 15 minutes'
11
+ when 15..29 then "half an hour"
12
+ when 30..59 then "#{minutes} minutes"
13
+ when 60..119 then '1 hour'
14
+ when 120..239 then '2 hours'
15
+ when 240..479 then '4 hours'
16
+ when 480..719 then '8 hours'
17
+ when 720..1439 then '12 hours'
18
+ when 1440..11519 then "#{(minutes/1440).floor} days"
19
+ when 11520..43199 then "#{(minutes/11520).floor} weeks"
20
+ when 43200..525599 then "#{(minutes/43200).floor} months"
21
+ else "#{(minutes/525600).floor} years"
22
+ end
23
+ end
24
+
25
+ def self.since(time)
26
+ if str = in_words(time)
27
+ "#{str} ago"
28
+ end
29
+ end
30
+
31
+ end
data/test/app_test.rb ADDED
@@ -0,0 +1,114 @@
1
+ ENV["RACK_ENV"] = "test"
2
+
3
+ require "rubygems"
4
+ require "test/unit"
5
+ require "json"
6
+ require "rack"
7
+ require "rack/test"
8
+ require_relative "../app"
9
+
10
+ class AppTest < Test::Unit::TestCase
11
+ include Rack::Test::Methods
12
+ def app
13
+ Sinatra::Application
14
+ end
15
+
16
+ def teardown
17
+ Comment.delete_all
18
+ Site.delete_all
19
+ end
20
+
21
+ def test_knows_how_to_retrieve_comments
22
+ blog = Site.create!({
23
+ :name => "Blog",
24
+ :domain => "blog.example.com"
25
+ })
26
+ 4.times do
27
+ Comment.create!({:nickname => "foo",
28
+ :content => "Test comment",
29
+ :document_path => "about/us",
30
+ :site_id => blog.id})
31
+ end
32
+
33
+ get "/comments.json", {:domain => "blog.example.com", :document_path => "about/us"}
34
+ assert last_response.ok?
35
+ output = JSON.parse(last_response.body)
36
+ assert_equal 4, output.size
37
+ assert_equal "foo", output.first["nickname"]
38
+ assert_equal "Test comment", output.first["content"]
39
+ end
40
+
41
+ def test_responds_with_422_if_domain_not_specified
42
+ get "/comments.json", {:document_path => "about/us"}
43
+ assert_equal 422, last_response.status
44
+ assert_match "The domain and document_path must be specified", last_response.body
45
+ end
46
+
47
+ def test_responds_with_422_if_document_path_not_specified
48
+ get "/comments.json", {:domain => "example.com"}
49
+ assert_equal 422, last_response.status
50
+ assert_match "The domain and document_path must be specified", last_response.body
51
+ end
52
+
53
+ def test_know_how_to_add_comment
54
+ blog = Site.create!({
55
+ :name => "Blog",
56
+ :domain => "blog.example.com"
57
+ })
58
+ request_body = {
59
+ :nickname => "foo",
60
+ :content => "Test comment",
61
+ :domain => "blog.example.com",
62
+ :document_path => "about/us"
63
+ }.to_json
64
+ post "/comments.json", request_body
65
+ assert_equal 201, last_response.status
66
+ assert_equal 1, Comment.all.size
67
+ end
68
+
69
+ def test_does_not_create_comment_if_params_not_sent
70
+ post "/comments.json"
71
+ assert_equal 422, last_response.status
72
+ assert_match "The JSON provided is not valid.", last_response.body
73
+ assert_equal 0, Comment.all.size
74
+ end
75
+
76
+ def test_does_not_create_comment_if_not_valid_data
77
+ blog = Site.create!({
78
+ :name => "Blog",
79
+ :domain => "blog.example.com"
80
+ })
81
+
82
+ request_body = {
83
+ :content => "Test comment",
84
+ :document_path => "about/us",
85
+ :nickname => "foo",
86
+ :domain => "foo.example.com"
87
+ }.to_json
88
+ post "/comments.json", request_body
89
+ assert_equal 422, last_response.status
90
+ assert_match "The Site provided is not valid.", last_response.body
91
+ assert_equal 0, Comment.all.size
92
+ end
93
+
94
+ def test_serves_static_files
95
+ get "/commentary.js"
96
+ assert last_response.ok?
97
+ end
98
+
99
+ def test_ensure_correct_content_type
100
+ post "/comments.json", {:foo => :bar}
101
+ assert_equal "application/json", last_response.headers["Content-Type"]
102
+ end
103
+
104
+ def test_ensure_correct_content_type
105
+ get "/comments.json", {:foo => :bar}
106
+ end
107
+
108
+ def test_renders_comment_frame_as_a_template_with_values
109
+ get "/comment_frame", {:domain => "foo", :document_path => "/bar"}
110
+ assert_match /domain: \"foo\"/, last_response.body
111
+ assert_match /documentPath: \"\/bar\"/, last_response.body
112
+ assert_equal "*", last_response.headers["Access-Control-Allow-Origin"]
113
+ end
114
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cachai
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tomás Pollak
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.6'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sinatra
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sinatra-activerecord
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Middleware for embedabble comments.
98
+ email:
99
+ - tomas@forkhq.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - Gemfile
106
+ - Gemfile.lock
107
+ - Procfile
108
+ - README.md
109
+ - Rakefile
110
+ - cachai.gemspec
111
+ - db/schema.rb
112
+ - lib/akismet.rb
113
+ - lib/cachai.rb
114
+ - lib/comment.rb
115
+ - lib/time_ago.rb
116
+ - test/app_test.rb
117
+ homepage: ''
118
+ licenses:
119
+ - MIT
120
+ metadata: {}
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubyforge_project:
137
+ rubygems_version: 2.2.0
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: Middleware for embedabble comments.
141
+ test_files:
142
+ - test/app_test.rb
143
+ has_rdoc: