vines-mongodb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2010-2013 Negative Code
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,53 @@
1
+ # Welcome to Vines
2
+
3
+ Vines is a scalable XMPP chat server, using EventMachine for asynchronous IO.
4
+ This gem provides support for storing user data in
5
+ [MongoDB](http://www.mongodb.org/).
6
+
7
+ Additional documentation can be found at [getvines.org](http://www.getvines.org/).
8
+
9
+ ## Usage
10
+
11
+ ```
12
+ $ gem install vines vines-mongodb
13
+ $ vines init wonderland.lit
14
+ $ cd wonderland.lit && vines start
15
+ ```
16
+
17
+ ## Configuration
18
+
19
+ Add the following configuration block to a virtual host definition in
20
+ the server's `conf/config.rb` file.
21
+
22
+ ```ruby
23
+ storage 'mongodb' do
24
+ host 'localhost', 27017
25
+ host 'localhost', 27018 # optional, connects to replica set
26
+ database 'xmpp'
27
+ tls true
28
+ username ''
29
+ password ''
30
+ pool 5
31
+ end
32
+ ```
33
+
34
+ ## Dependencies
35
+
36
+ Vines requires Ruby 1.9.3 or better. Instructions for installing the
37
+ needed OS packages, as well as Ruby itself, are available at
38
+ [getvines.org/ruby](http://www.getvines.org/ruby).
39
+
40
+ ## Development
41
+
42
+ ```
43
+ $ script/bootstrap
44
+ $ script/tests
45
+ ```
46
+
47
+ ## Contact
48
+
49
+ * David Graham <david@negativecode.com>
50
+
51
+ ## License
52
+
53
+ Vines is released under the MIT license. Check the LICENSE file for details.
@@ -0,0 +1,20 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/testtask'
4
+
5
+ CLOBBER.include('pkg')
6
+
7
+ directory 'pkg'
8
+
9
+ desc 'Build distributable packages'
10
+ task :build => [:pkg] do
11
+ system 'gem build vines-mongodb.gemspec && mv vines-*.gem pkg/'
12
+ end
13
+
14
+ Rake::TestTask.new(:test) do |test|
15
+ test.libs << 'spec'
16
+ test.pattern = 'spec/**/*_spec.rb'
17
+ test.warning = false
18
+ end
19
+
20
+ task :default => [:clobber, :test, :build]
@@ -0,0 +1,132 @@
1
+ require 'mongo'
2
+
3
+ module Vines
4
+ class Storage
5
+ class MongoDB < Storage
6
+ register :mongodb
7
+
8
+ %w[database tls username password pool].each do |name|
9
+ define_method(name) do |*args|
10
+ if args.first
11
+ @config[name.to_sym] = args.first
12
+ else
13
+ @config[name.to_sym]
14
+ end
15
+ end
16
+ end
17
+
18
+ def initialize(&block)
19
+ @config, @hosts = {}, []
20
+ instance_eval(&block)
21
+ raise "Must provide database" unless @config[:database]
22
+ raise "Must provide at least one host connection" if @hosts.empty?
23
+ end
24
+
25
+ def host(name, port)
26
+ pair = [name, port]
27
+ raise "duplicate hosts not allowed: #{name}:#{port}" if @hosts.include?(pair)
28
+ @hosts << pair
29
+ end
30
+
31
+ def find_user(jid)
32
+ jid = JID.new(jid).bare.to_s
33
+ return if jid.empty?
34
+ doc = get(:users, jid)
35
+ return unless doc
36
+ User.new(jid: jid).tap do |user|
37
+ user.name, user.password = doc.values_at('name', 'password')
38
+ (doc['roster'] || {}).each_pair do |jid, props|
39
+ user.roster << Contact.new(
40
+ jid: jid,
41
+ name: props['name'],
42
+ subscription: props['subscription'],
43
+ ask: props['ask'],
44
+ groups: props['groups'] || [])
45
+ end
46
+ end
47
+ end
48
+
49
+ def save_user(user)
50
+ id = user.jid.bare.to_s
51
+ doc = get(:users, id) || {'_id' => id}
52
+ doc['name'] = user.name
53
+ doc['password'] = user.password
54
+ doc['roster'] = {}
55
+ user.roster.each do |contact|
56
+ doc['roster'][contact.jid.bare.to_s] = contact.to_h
57
+ end
58
+ save_doc(:users, doc)
59
+ end
60
+
61
+ def find_vcard(jid)
62
+ jid = JID.new(jid).bare.to_s
63
+ return if jid.empty?
64
+ doc = get(:vcards, jid)
65
+ return unless doc
66
+ Nokogiri::XML(doc['card']).root rescue nil
67
+ end
68
+
69
+ def save_vcard(jid, card)
70
+ jid = JID.new(jid).bare.to_s
71
+ doc = get(:vcards, jid) || {'_id' => jid}
72
+ doc['card'] = card.to_xml
73
+ save_doc(:vcards, doc)
74
+ end
75
+
76
+ def find_fragment(jid, node)
77
+ jid = JID.new(jid).bare.to_s
78
+ return if jid.empty?
79
+ doc = get(:fragments, fragment_id(jid, node))
80
+ return unless doc
81
+ Nokogiri::XML(doc['xml']).root rescue nil
82
+ end
83
+
84
+ def save_fragment(jid, node)
85
+ jid = JID.new(jid).bare.to_s
86
+ id = fragment_id(jid, node)
87
+ doc = get(:fragments, id) || {'_id' => id}
88
+ doc['xml'] = node.to_xml
89
+ save_doc(:fragments, doc)
90
+ end
91
+
92
+ private
93
+
94
+ def fragment_id(jid, node)
95
+ id = Digest::SHA1.hexdigest("#{node.name}:#{node.namespace.href}")
96
+ "#{jid}:#{id}"
97
+ end
98
+
99
+ def get(collection, id)
100
+ db.collection(collection).find_one({_id: id})
101
+ end
102
+ defer :get
103
+
104
+ def save_doc(collection, doc)
105
+ db.collection(collection).save(doc, safe: true)
106
+ end
107
+ defer :save_doc
108
+
109
+ def db
110
+ @db ||= connect
111
+ end
112
+
113
+ def connect
114
+ opts = {
115
+ pool_timeout: 5,
116
+ pool_size: @config[:pool] || 5,
117
+ ssl: @config[:tls]
118
+ }
119
+ conn = if @hosts.size == 1
120
+ Mongo::Connection.new(@hosts.first[0], @hosts.first[1], opts)
121
+ else
122
+ Mongo::ReplSetConnection.new(*@hosts, opts)
123
+ end
124
+ conn.db(@config[:database]).tap do |db|
125
+ user = @config[:username] || ''
126
+ pass = @config[:password] || ''
127
+ db.authenticate(user, pass) unless user.empty? || pass.empty?
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,38 @@
1
+ # A mock implementation of the Mongo::Connection class.
2
+ class MockMongo
3
+ def initialize
4
+ @collections = collections
5
+ end
6
+
7
+ def collection(name)
8
+ @collections[name]
9
+ end
10
+
11
+ def clear
12
+ @collections = collections
13
+ end
14
+
15
+ def collections
16
+ {
17
+ users: MockCollection.new,
18
+ vcards: MockCollection.new,
19
+ fragments: MockCollection.new
20
+ }
21
+ end
22
+
23
+ class MockCollection
24
+ def initialize
25
+ @docs = {}
26
+ end
27
+
28
+ def find_one(doc)
29
+ id = doc['_id'] || doc[:_id]
30
+ @docs[id]
31
+ end
32
+
33
+ def save(doc, opts={})
34
+ id = doc['_id'] || doc[:_id]
35
+ @docs[id] = doc
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,4 @@
1
+ require 'vines'
2
+ require 'mock_mongo'
3
+ require 'storage_specs'
4
+ require 'minitest/autorun'
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+
3
+ describe Vines::Storage::MongoDB do
4
+ include StorageSpecs
5
+
6
+ MOCK_MONGO = MockMongo.new
7
+
8
+ before do
9
+ fibered do
10
+ db = MOCK_MONGO
11
+ db.collection(:users).save({'_id' => 'empty@wonderland.lit'})
12
+ db.collection(:users).save({'_id' => 'no_password@wonderland.lit', 'foo' => 'bar'})
13
+ db.collection(:users).save({'_id' => 'clear_password@wonderland.lit', 'password' => 'secret'})
14
+ db.collection(:users).save({'_id' => 'bcrypt_password@wonderland.lit', 'password' => BCrypt::Password.create('secret')})
15
+ db.collection(:users).save({
16
+ '_id' => 'full@wonderland.lit',
17
+ 'password' => BCrypt::Password.create('secret'),
18
+ 'name' => 'Tester',
19
+ 'roster' => {
20
+ 'contact1@wonderland.lit' => {
21
+ 'name' => 'Contact1',
22
+ 'groups' => %w[Group1 Group2]
23
+ },
24
+ 'contact2@wonderland.lit' => {
25
+ 'name' => 'Contact2',
26
+ 'groups' => %w[Group3 Group4]
27
+ }
28
+ }
29
+ })
30
+ db.collection(:vcards).save({'_id' => 'full@wonderland.lit', 'card' => vcard.to_xml})
31
+ db.collection(:fragments).save({'_id' => "full@wonderland.lit:#{fragment_id}", 'xml' => fragment.to_xml})
32
+ end
33
+ end
34
+
35
+ after do
36
+ MOCK_MONGO.clear
37
+ end
38
+
39
+ def storage
40
+ storage = Vines::Storage::MongoDB.new do
41
+ host 'localhost', 27017
42
+ database 'xmpp_testcase'
43
+ end
44
+ def storage.db
45
+ MOCK_MONGO
46
+ end
47
+ storage
48
+ end
49
+
50
+ describe 'creating a new instance' do
51
+ it 'raises with no database' do
52
+ fibered do
53
+ -> { Vines::Storage::MongoDB.new {} }.must_raise RuntimeError
54
+ end
55
+ end
56
+
57
+ it 'raises with no host' do
58
+ fibered do
59
+ -> { Vines::Storage::MongoDB.new { database 'test' } }.must_raise RuntimeError
60
+ end
61
+ end
62
+
63
+ it 'raises with duplicate hosts' do
64
+ fibered do
65
+ proc do
66
+ Vines::Storage::MongoDB.new do
67
+ host 'localhost', 27017
68
+ host 'localhost', 27017
69
+ database 'test'
70
+ end
71
+ end.must_raise RuntimeError
72
+ end
73
+ end
74
+
75
+ it 'does not raise with host and database' do
76
+ fibered do
77
+ obj =
78
+ Vines::Storage::MongoDB.new do
79
+ host 'localhost', 27017
80
+ database 'test'
81
+ end
82
+ obj.wont_be_nil
83
+ end
84
+ end
85
+
86
+ it 'does not raise with two hosts' do
87
+ fibered do
88
+ obj =
89
+ Vines::Storage::MongoDB.new do
90
+ host 'localhost', 27017
91
+ host 'localhost', 27018
92
+ database 'test'
93
+ end
94
+ obj.wont_be_nil
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,182 @@
1
+ # Mixin methods for storage implementation test classes. The behavioral
2
+ # tests are the same regardless of implementation so share those methods
3
+ # here.
4
+ module StorageSpecs
5
+ def fragment_id
6
+ Digest::SHA1.hexdigest("characters:urn:wonderland")
7
+ end
8
+
9
+ def fragment
10
+ Nokogiri::XML(%q{
11
+ <characters xmlns="urn:wonderland">
12
+ <character>Alice</character>
13
+ </characters>
14
+ }.strip).root
15
+ end
16
+
17
+ def vcard
18
+ Nokogiri::XML(%q{
19
+ <vCard xmlns="vcard-temp">
20
+ <FN>Alice in Wonderland</FN>
21
+ </vCard>
22
+ }.strip).root
23
+ end
24
+
25
+ def fibered
26
+ EM.run do
27
+ Fiber.new do
28
+ yield
29
+ EM.stop
30
+ end.resume
31
+ end
32
+ end
33
+
34
+ def test_authenticate
35
+ fibered do
36
+ db = storage
37
+ db.authenticate(nil, nil).must_be_nil
38
+ db.authenticate(nil, 'secret').must_be_nil
39
+ db.authenticate('bogus', nil).must_be_nil
40
+ db.authenticate('bogus', 'secret').must_be_nil
41
+ db.authenticate('empty@wonderland.lit', 'secret').must_be_nil
42
+ db.authenticate('no_password@wonderland.lit', 'secret').must_be_nil
43
+ db.authenticate('clear_password@wonderland.lit', 'secret').must_be_nil
44
+
45
+ user = db.authenticate('bcrypt_password@wonderland.lit', 'secret')
46
+ user.wont_be_nil
47
+ user.jid.to_s.must_equal 'bcrypt_password@wonderland.lit'
48
+
49
+ user = db.authenticate('full@wonderland.lit', 'secret')
50
+ user.wont_be_nil
51
+ user.name.must_equal 'Tester'
52
+ user.jid.to_s.must_equal 'full@wonderland.lit'
53
+
54
+ user.roster.length.must_equal 2
55
+ user.roster[0].jid.to_s.must_equal 'contact1@wonderland.lit'
56
+ user.roster[0].name.must_equal 'Contact1'
57
+ user.roster[0].groups.length.must_equal 2
58
+ user.roster[0].groups[0].must_equal 'Group1'
59
+ user.roster[0].groups[1].must_equal 'Group2'
60
+
61
+ user.roster[1].jid.to_s.must_equal 'contact2@wonderland.lit'
62
+ user.roster[1].name.must_equal 'Contact2'
63
+ user.roster[1].groups.length.must_equal 2
64
+ user.roster[1].groups[0].must_equal 'Group3'
65
+ user.roster[1].groups[1].must_equal 'Group4'
66
+ end
67
+ end
68
+
69
+ def test_find_user
70
+ fibered do
71
+ db = storage
72
+ user = db.find_user(nil)
73
+ user.must_be_nil
74
+
75
+ user = db.find_user('full@wonderland.lit')
76
+ user.wont_be_nil
77
+ user.jid.to_s.must_equal 'full@wonderland.lit'
78
+
79
+ user = db.find_user(Vines::JID.new('full@wonderland.lit'))
80
+ user.wont_be_nil
81
+ user.jid.to_s.must_equal 'full@wonderland.lit'
82
+
83
+ user = db.find_user(Vines::JID.new('full@wonderland.lit/resource'))
84
+ user.wont_be_nil
85
+ user.jid.to_s.must_equal 'full@wonderland.lit'
86
+ end
87
+ end
88
+
89
+ def test_save_user
90
+ fibered do
91
+ db = storage
92
+ user = Vines::User.new(
93
+ jid: 'save_user@domain.tld/resource1',
94
+ name: 'Save User',
95
+ password: 'secret')
96
+ user.roster << Vines::Contact.new(
97
+ jid: 'contact1@domain.tld/resource2',
98
+ name: 'Contact 1')
99
+ db.save_user(user)
100
+ user = db.find_user('save_user@domain.tld')
101
+ user.wont_be_nil
102
+ user.jid.to_s.must_equal 'save_user@domain.tld'
103
+ user.name.must_equal 'Save User'
104
+ user.roster.length.must_equal 1
105
+ user.roster[0].jid.to_s.must_equal 'contact1@domain.tld'
106
+ user.roster[0].name.must_equal 'Contact 1'
107
+ end
108
+ end
109
+
110
+ def test_find_vcard
111
+ fibered do
112
+ db = storage
113
+ card = db.find_vcard(nil)
114
+ card.must_be_nil
115
+
116
+ card = db.find_vcard('full@wonderland.lit')
117
+ card.wont_be_nil
118
+ card.to_s.must_equal vcard.to_s
119
+
120
+ card = db.find_vcard(Vines::JID.new('full@wonderland.lit'))
121
+ card.wont_be_nil
122
+ card.to_s.must_equal vcard.to_s
123
+
124
+ card = db.find_vcard(Vines::JID.new('full@wonderland.lit/resource'))
125
+ card.wont_be_nil
126
+ card.to_s.must_equal vcard.to_s
127
+ end
128
+ end
129
+
130
+ def test_save_vcard
131
+ fibered do
132
+ db = storage
133
+ db.save_user(Vines::User.new(jid: 'save_user@domain.tld'))
134
+ db.save_vcard('save_user@domain.tld/resource1', vcard)
135
+ card = db.find_vcard('save_user@domain.tld')
136
+ card.wont_be_nil
137
+ card.to_s.must_equal vcard.to_s
138
+ end
139
+ end
140
+
141
+ def test_find_fragment
142
+ fibered do
143
+ db = storage
144
+ root = Nokogiri::XML(%q{<characters xmlns="urn:wonderland"/>}).root
145
+ bad_name = Nokogiri::XML(%q{<not_characters xmlns="urn:wonderland"/>}).root
146
+ bad_ns = Nokogiri::XML(%q{<characters xmlns="not:wonderland"/>}).root
147
+
148
+ node = db.find_fragment(nil, nil)
149
+ node.must_be_nil
150
+
151
+ node = db.find_fragment('full@wonderland.lit', bad_name)
152
+ node.must_be_nil
153
+
154
+ node = db.find_fragment('full@wonderland.lit', bad_ns)
155
+ node.must_be_nil
156
+
157
+ node = db.find_fragment('full@wonderland.lit', root)
158
+ node.wont_be_nil
159
+ node.to_s.must_equal fragment.to_s
160
+
161
+ node = db.find_fragment(Vines::JID.new('full@wonderland.lit'), root)
162
+ node.wont_be_nil
163
+ node.to_s.must_equal fragment.to_s
164
+
165
+ node = db.find_fragment(Vines::JID.new('full@wonderland.lit/resource'), root)
166
+ node.wont_be_nil
167
+ node.to_s.must_equal fragment.to_s
168
+ end
169
+ end
170
+
171
+ def test_save_fragment
172
+ fibered do
173
+ db = storage
174
+ root = Nokogiri::XML(%q{<characters xmlns="urn:wonderland"/>}).root
175
+ db.save_user(Vines::User.new(jid: 'save_user@domain.tld'))
176
+ db.save_fragment('save_user@domain.tld/resource1', fragment)
177
+ node = db.find_fragment('save_user@domain.tld', root)
178
+ node.wont_be_nil
179
+ node.to_s.must_equal fragment.to_s
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,24 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'vines-mongodb'
3
+ s.version = '0.1.0'
4
+ s.summary = %q[Provides a MongoDB storage adapter for the Vines XMPP chat server.]
5
+ s.description = %q[Stores Vines user data in MongoDB.]
6
+
7
+ s.authors = ['David Graham']
8
+ s.email = %w[david@negativecode.com]
9
+ s.homepage = 'http://www.getvines.org'
10
+ s.license = 'MIT'
11
+
12
+ s.files = Dir['[A-Z]*', 'vines-mongodb.gemspec', 'lib/**/*'] - ['Gemfile.lock']
13
+ s.test_files = Dir['spec/**/*']
14
+ s.require_path = 'lib'
15
+
16
+ s.add_dependency 'mongo', '~> 1.5.2'
17
+ s.add_dependency 'bson_ext', '~> 1.5.2'
18
+ s.add_dependency 'vines', '>= 0.4.5'
19
+
20
+ s.add_development_dependency 'minitest', '~> 4.7.4'
21
+ s.add_development_dependency 'rake', '~> 10.1.0'
22
+
23
+ s.required_ruby_version = '>= 1.9.3'
24
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vines-mongodb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - David Graham
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mongo
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.5.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.5.2
30
+ - !ruby/object:Gem::Dependency
31
+ name: bson_ext
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.5.2
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.5.2
46
+ - !ruby/object:Gem::Dependency
47
+ name: vines
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: 0.4.5
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 0.4.5
62
+ - !ruby/object:Gem::Dependency
63
+ name: minitest
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 4.7.4
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 4.7.4
78
+ - !ruby/object:Gem::Dependency
79
+ name: rake
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 10.1.0
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 10.1.0
94
+ description: Stores Vines user data in MongoDB.
95
+ email:
96
+ - david@negativecode.com
97
+ executables: []
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - Gemfile
102
+ - LICENSE
103
+ - Rakefile
104
+ - README.md
105
+ - vines-mongodb.gemspec
106
+ - lib/vines/storage/mongodb.rb
107
+ - spec/mock_mongo.rb
108
+ - spec/spec_helper.rb
109
+ - spec/storage/mongodb_spec.rb
110
+ - spec/storage_specs.rb
111
+ homepage: http://www.getvines.org
112
+ licenses:
113
+ - MIT
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ! '>='
122
+ - !ruby/object:Gem::Version
123
+ version: 1.9.3
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ! '>='
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ segments:
131
+ - 0
132
+ hash: 1526150033927747490
133
+ requirements: []
134
+ rubyforge_project:
135
+ rubygems_version: 1.8.23
136
+ signing_key:
137
+ specification_version: 3
138
+ summary: Provides a MongoDB storage adapter for the Vines XMPP chat server.
139
+ test_files:
140
+ - spec/mock_mongo.rb
141
+ - spec/spec_helper.rb
142
+ - spec/storage/mongodb_spec.rb
143
+ - spec/storage_specs.rb