vines 0.4.5 → 0.4.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/Gemfile +3 -0
  2. data/README.md +48 -0
  3. data/Rakefile +6 -58
  4. data/bin/vines +12 -2
  5. data/conf/certs/ca-bundle.crt +568 -39
  6. data/conf/config.rb +9 -42
  7. data/lib/vines.rb +1 -8
  8. data/lib/vines/command/cert.rb +4 -4
  9. data/lib/vines/command/init.rb +3 -3
  10. data/lib/vines/config.rb +9 -0
  11. data/lib/vines/kit.rb +2 -7
  12. data/lib/vines/storage/local.rb +50 -17
  13. data/lib/vines/store.rb +6 -4
  14. data/lib/vines/stream.rb +1 -1
  15. data/lib/vines/stream/http/session.rb +0 -0
  16. data/lib/vines/stream/http/sessions.rb +0 -0
  17. data/lib/vines/stream/parser.rb +3 -2
  18. data/lib/vines/token_bucket.rb +19 -10
  19. data/lib/vines/version.rb +1 -1
  20. data/test/cluster/publisher_test.rb +45 -33
  21. data/test/cluster/sessions_test.rb +32 -39
  22. data/test/cluster/subscriber_test.rb +93 -78
  23. data/test/config/host_test.rb +2 -4
  24. data/test/config/pubsub_test.rb +132 -126
  25. data/test/config_test.rb +2 -4
  26. data/test/contact_test.rb +80 -66
  27. data/test/error_test.rb +54 -55
  28. data/test/jid_test.rb +1 -2
  29. data/test/kit_test.rb +22 -17
  30. data/test/router_test.rb +187 -146
  31. data/test/stanza/iq/disco_info_test.rb +59 -59
  32. data/test/stanza/iq/disco_items_test.rb +36 -34
  33. data/test/stanza/iq/private_storage_test.rb +138 -143
  34. data/test/stanza/iq/roster_test.rb +198 -175
  35. data/test/stanza/iq/session_test.rb +17 -18
  36. data/test/stanza/iq/vcard_test.rb +117 -116
  37. data/test/stanza/iq/version_test.rb +47 -46
  38. data/test/stanza/iq_test.rb +53 -49
  39. data/test/stanza/message_test.rb +92 -89
  40. data/test/stanza/presence/probe_test.rb +2 -5
  41. data/test/stanza/presence/subscribe_test.rb +67 -54
  42. data/test/stanza/pubsub/create_test.rb +86 -108
  43. data/test/stanza/pubsub/delete_test.rb +141 -114
  44. data/test/stanza/pubsub/publish_test.rb +256 -320
  45. data/test/stanza/pubsub/subscribe_test.rb +169 -150
  46. data/test/stanza/pubsub/unsubscribe_test.rb +111 -142
  47. data/test/stanza_test.rb +61 -54
  48. data/test/storage/ldap_test.rb +1 -2
  49. data/test/storage/local_test.rb +3 -5
  50. data/test/storage/null_test.rb +3 -4
  51. data/test/storage/storage_tests.rb +1 -3
  52. data/test/storage_test.rb +1 -2
  53. data/test/store_test.rb +1 -2
  54. data/test/stream/client/auth_test.rb +61 -63
  55. data/test/stream/client/ready_test.rb +7 -8
  56. data/test/stream/client/session_test.rb +19 -18
  57. data/test/stream/component/handshake_test.rb +40 -37
  58. data/test/stream/component/ready_test.rb +76 -61
  59. data/test/stream/component/start_test.rb +7 -8
  60. data/test/stream/http/auth_test.rb +3 -4
  61. data/test/stream/http/ready_test.rb +52 -60
  62. data/test/stream/http/request_test.rb +1 -3
  63. data/test/stream/http/sessions_test.rb +2 -3
  64. data/test/stream/http/start_test.rb +3 -4
  65. data/test/stream/parser_test.rb +3 -4
  66. data/test/stream/sasl_test.rb +105 -86
  67. data/test/stream/server/auth_test.rb +40 -36
  68. data/test/stream/server/outbound/auth_test.rb +3 -4
  69. data/test/stream/server/ready_test.rb +51 -51
  70. data/test/test_helper.rb +42 -0
  71. data/test/token_bucket_test.rb +38 -18
  72. data/test/user_test.rb +79 -49
  73. data/vines.gemspec +33 -0
  74. data/web/chat/javascripts/app.js +1 -1
  75. data/web/lib/coffeescripts/layout.coffee +1 -1
  76. data/web/lib/javascripts/base.js +10 -10
  77. data/web/lib/javascripts/jquery.js +4 -4
  78. metadata +31 -128
  79. data/README +0 -35
  80. data/lib/vines/storage/couchdb.rb +0 -129
  81. data/lib/vines/storage/mongodb.rb +0 -132
  82. data/lib/vines/storage/redis.rb +0 -127
  83. data/lib/vines/storage/sql.rb +0 -220
  84. data/test/rake_test_loader.rb +0 -17
  85. data/test/storage/couchdb_test.rb +0 -107
  86. data/test/storage/mock_mongo.rb +0 -40
  87. data/test/storage/mongodb_test.rb +0 -81
  88. data/test/storage/redis_test.rb +0 -51
  89. data/test/storage/sql_test.rb +0 -62
data/README DELETED
@@ -1,35 +0,0 @@
1
- == Welcome to Vines
2
-
3
- Vines is an XMPP chat server that supports thousands of simultaneous connections
4
- by using EventMachine for asynchronous IO. User data is stored in a SQL database,
5
- CouchDB, MongoDB, Redis, the file system, or a custom storage implementation
6
- that you provide. LDAP authentication can be used so user names and passwords
7
- aren't stored in the chat database. SSL encryption is mandatory on all client
8
- and server connections.
9
-
10
- The Vines XMPP server includes a web chat client. The web application is available
11
- immediately after starting the chat server at http://localhost:5280/chat/.
12
-
13
- Additional documentation can be found at www.getvines.org.
14
-
15
- == Usage
16
-
17
- 1. gem install vines
18
- 2. vines init wonderland.lit
19
- 3. cd wonderland.lit && vines start
20
- 4. Login with your favorite chat program (iChat, Adium, Pidgin, etc.) to
21
- start chatting!
22
-
23
- == Dependencies
24
-
25
- Vines requires Ruby 1.9.2 or better. Instructions for installing the
26
- needed OS packages, as well as Ruby itself, are available at
27
- http://www.getvines.org/ruby.
28
-
29
- == Contact
30
-
31
- * David Graham <david@negativecode.com>
32
-
33
- == License
34
-
35
- Vines is released under the MIT license. Check the LICENSE file for details.
@@ -1,129 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- module Vines
4
- class Storage
5
- class CouchDB < Storage
6
- register :couchdb
7
-
8
- %w[host port database tls username password].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 = {}
20
- instance_eval(&block)
21
- [:host, :port, :database].each {|key| raise "Must provide #{key}" unless @config[key] }
22
- @url = url(@config)
23
- end
24
-
25
- def find_user(jid)
26
- jid = JID.new(jid).bare.to_s
27
- return if jid.empty?
28
- doc = get("user:#{jid}")
29
- return unless doc && doc['type'] == 'User'
30
- User.new(jid: jid).tap do |user|
31
- user.name, user.password = doc.values_at('name', 'password')
32
- (doc['roster'] || {}).each_pair do |jid, props|
33
- user.roster << Contact.new(
34
- jid: jid,
35
- name: props['name'],
36
- subscription: props['subscription'],
37
- ask: props['ask'],
38
- groups: props['groups'] || [])
39
- end
40
- end
41
- end
42
-
43
- def save_user(user)
44
- id = "user:#{user.jid.bare}"
45
- doc = get(id) || {'_id' => id}
46
- doc['type'] = 'User'
47
- doc['name'] = user.name
48
- doc['password'] = user.password
49
- doc['roster'] = {}
50
- user.roster.each do |contact|
51
- doc['roster'][contact.jid.bare.to_s] = contact.to_h
52
- end
53
- save_doc(doc)
54
- end
55
-
56
- def find_vcard(jid)
57
- jid = JID.new(jid).bare.to_s
58
- return if jid.empty?
59
- doc = get("vcard:#{jid}")
60
- return unless doc && doc['type'] == 'Vcard'
61
- Nokogiri::XML(doc['card']).root rescue nil
62
- end
63
-
64
- def save_vcard(jid, card)
65
- jid = JID.new(jid).bare.to_s
66
- id = "vcard:#{jid}"
67
- doc = get(id) || {'_id' => id}
68
- doc['type'] = 'Vcard'
69
- doc['card'] = card.to_xml
70
- save_doc(doc)
71
- end
72
-
73
- def find_fragment(jid, node)
74
- jid = JID.new(jid).bare.to_s
75
- return if jid.empty?
76
- doc = get(fragment_id(jid, node))
77
- return unless doc && doc['type'] == 'Fragment'
78
- Nokogiri::XML(doc['xml']).root rescue nil
79
- end
80
-
81
- def save_fragment(jid, node)
82
- jid = JID.new(jid).bare.to_s
83
- id = fragment_id(jid, node)
84
- doc = get(id) || {'_id' => id}
85
- doc['type'] = 'Fragment'
86
- doc['xml'] = node.to_xml
87
- save_doc(doc)
88
- end
89
-
90
- private
91
-
92
- def fragment_id(jid, node)
93
- id = Digest::SHA1.hexdigest("#{node.name}:#{node.namespace.href}")
94
- "fragment:#{jid}:#{id}"
95
- end
96
-
97
- def url(config)
98
- scheme = config[:tls] ? 'https' : 'http'
99
- user, password = config.values_at(:username, :password)
100
- credentials = empty?(user, password) ? '' : "%s:%s@" % [user, password]
101
- "%s://%s%s:%s/%s" % [scheme, credentials, *config.values_at(:host, :port, :database)]
102
- end
103
-
104
- def get(jid)
105
- http = EM::HttpRequest.new("#{@url}/#{escape(jid)}").get
106
- http.errback { yield }
107
- http.callback do
108
- doc = if http.response_header.status == 200
109
- JSON.parse(http.response) rescue nil
110
- end
111
- yield doc
112
- end
113
- end
114
- fiber :get
115
-
116
- def save_doc(doc)
117
- head = {'Content-Type' => 'application/json'}
118
- http = EM::HttpRequest.new(@url).post(head: head, body: doc.to_json)
119
- http.callback { yield }
120
- http.errback { yield }
121
- end
122
- fiber :save_doc
123
-
124
- def escape(jid)
125
- URI.escape(jid, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
126
- end
127
- end
128
- end
129
- end
@@ -1,132 +0,0 @@
1
- # encoding: UTF-8
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
@@ -1,127 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- module Vines
4
- class Storage
5
- class Redis < Storage
6
- register :redis
7
-
8
- %w[host port database password].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 = {}
20
- instance_eval(&block)
21
- end
22
-
23
- def find_user(jid)
24
- jid = JID.new(jid).bare.to_s
25
- return if jid.empty?
26
- response = query(:get, "user:#{jid}")
27
- return unless response
28
- doc = JSON.parse(response) rescue nil
29
- return unless doc
30
- User.new(jid: jid).tap do |user|
31
- user.name, user.password = doc.values_at('name', 'password')
32
- user.roster = find_roster(jid)
33
- end
34
- end
35
-
36
- def save_user(user)
37
- doc = {name: user.name, password: user.password}
38
- contacts = user.roster.map {|c| [c.jid.to_s, c.to_h.to_json] }.flatten
39
- roster = "roster:#{user.jid.bare}"
40
-
41
- redis.multi
42
- redis.set("user:#{user.jid.bare}", doc.to_json)
43
- redis.del(roster)
44
- redis.hmset(roster, *contacts) unless contacts.empty?
45
-
46
- req = redis.exec
47
- req.callback { yield }
48
- req.errback { yield }
49
- end
50
- fiber :save_user
51
-
52
- def find_vcard(jid)
53
- jid = JID.new(jid).bare.to_s
54
- return if jid.empty?
55
- response = query(:get, "vcard:#{jid}")
56
- return unless response
57
- doc = JSON.parse(response) rescue nil
58
- Nokogiri::XML(doc['card']).root rescue nil
59
- end
60
-
61
- def save_vcard(jid, card)
62
- jid = JID.new(jid).bare.to_s
63
- doc = {card: card.to_xml}
64
- query(:set, "vcard:#{jid}", doc.to_json)
65
- end
66
-
67
- def find_fragment(jid, node)
68
- jid = JID.new(jid).bare.to_s
69
- return if jid.empty?
70
- response = query(:hget, "fragments:#{jid}", fragment_id(node))
71
- return unless response
72
- doc = JSON.parse(response) rescue nil
73
- Nokogiri::XML(doc['xml']).root rescue nil
74
- end
75
-
76
- def save_fragment(jid, node)
77
- jid = JID.new(jid).bare.to_s
78
- doc = {xml: node.to_xml}
79
- query(:hset, "fragments:#{jid}", fragment_id(node), doc.to_json)
80
- end
81
-
82
- private
83
-
84
- def fragment_id(node)
85
- Digest::SHA1.hexdigest("#{node.name}:#{node.namespace.href}")
86
- end
87
-
88
- # Retrieve the hash stored at roster:jid and return an array of
89
- # +Vines::Contact+ objects.
90
- def find_roster(jid)
91
- jid = JID.new(jid).bare
92
- roster = query(:hgetall, "roster:#{jid}")
93
- Hash[*roster].map do |jid, json|
94
- contact = JSON.parse(json) rescue nil
95
- Contact.new(
96
- jid: jid,
97
- name: contact['name'],
98
- subscription: contact['subscription'],
99
- ask: contact['ask'],
100
- groups: contact['groups'] || []) if contact
101
- end.compact
102
- end
103
-
104
- def query(name, *args)
105
- req = redis.send(name, *args)
106
- req.callback {|response| yield response }
107
- req.errback { yield }
108
- end
109
- fiber :query
110
-
111
- # Cache and return a redis connection object. The em-hiredis gem reconnects
112
- # in unbind so only create one connection.
113
- def redis
114
- @redis ||= connect
115
- end
116
-
117
- # Return a new redis connection using the configuration attributes from the
118
- # conf/config.rb file.
119
- def connect
120
- args = @config.values_at(:host, :port, :password, :database)
121
- conn = EM::Hiredis::Client.new(*args)
122
- conn.connect
123
- conn
124
- end
125
- end
126
- end
127
- end
@@ -1,220 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- module Vines
4
- class Storage
5
- class Sql < Storage
6
- register :sql
7
-
8
- class Contact < ActiveRecord::Base
9
- belongs_to :user
10
- end
11
- class Fragment < ActiveRecord::Base
12
- belongs_to :user
13
- end
14
- class Group < ActiveRecord::Base; end
15
- class User < ActiveRecord::Base
16
- has_many :contacts, :dependent => :destroy
17
- has_many :fragments, :dependent => :delete_all
18
- end
19
-
20
- # Wrap the method with ActiveRecord connection pool logic, so we properly
21
- # return connections to the pool when we're finished with them. This also
22
- # defers the original method by pushing it onto the EM thread pool because
23
- # ActiveRecord uses blocking IO.
24
- def self.with_connection(method, args={})
25
- deferrable = args.key?(:defer) ? args[:defer] : true
26
- old = instance_method(method)
27
- define_method method do |*args|
28
- ActiveRecord::Base.connection_pool.with_connection do
29
- old.bind(self).call(*args)
30
- end
31
- end
32
- defer(method) if deferrable
33
- end
34
-
35
- %w[adapter host port database username password pool].each do |name|
36
- define_method(name) do |*args|
37
- if args.first
38
- @config[name.to_sym] = args.first
39
- else
40
- @config[name.to_sym]
41
- end
42
- end
43
- end
44
-
45
- def initialize(&block)
46
- @config = {}
47
- instance_eval(&block)
48
- required = [:adapter, :database]
49
- required << [:host, :port] unless @config[:adapter] == 'sqlite3'
50
- required.flatten.each {|key| raise "Must provide #{key}" unless @config[key] }
51
- [:username, :password].each {|key| @config.delete(key) if empty?(@config[key]) }
52
- establish_connection
53
- end
54
-
55
- def find_user(jid)
56
- jid = JID.new(jid).bare.to_s
57
- return if jid.empty?
58
- xuser = user_by_jid(jid)
59
- return Vines::User.new(jid: jid).tap do |user|
60
- user.name, user.password = xuser.name, xuser.password
61
- xuser.contacts.each do |contact|
62
- groups = contact.groups.map {|group| group.name }
63
- user.roster << Vines::Contact.new(
64
- jid: contact.jid,
65
- name: contact.name,
66
- subscription: contact.subscription,
67
- ask: contact.ask,
68
- groups: groups)
69
- end
70
- end if xuser
71
- end
72
- with_connection :find_user
73
-
74
- def save_user(user)
75
- xuser = user_by_jid(user.jid) || Sql::User.new(jid: user.jid.bare.to_s)
76
- xuser.name = user.name
77
- xuser.password = user.password
78
-
79
- # remove deleted contacts from roster
80
- xuser.contacts.delete(xuser.contacts.select do |contact|
81
- !user.contact?(contact.jid)
82
- end)
83
-
84
- # update contacts
85
- xuser.contacts.each do |contact|
86
- fresh = user.contact(contact.jid)
87
- contact.update_attributes(
88
- name: fresh.name,
89
- ask: fresh.ask,
90
- subscription: fresh.subscription,
91
- groups: groups(fresh))
92
- end
93
-
94
- # add new contacts to roster
95
- jids = xuser.contacts.map {|c| c.jid }
96
- user.roster.select {|contact| !jids.include?(contact.jid.bare.to_s) }
97
- .each do |contact|
98
- xuser.contacts.build(
99
- user: xuser,
100
- jid: contact.jid.bare.to_s,
101
- name: contact.name,
102
- ask: contact.ask,
103
- subscription: contact.subscription,
104
- groups: groups(contact))
105
- end
106
- xuser.save
107
- end
108
- with_connection :save_user
109
-
110
- def find_vcard(jid)
111
- jid = JID.new(jid).bare.to_s
112
- return if jid.empty?
113
- if xuser = user_by_jid(jid)
114
- Nokogiri::XML(xuser.vcard).root rescue nil
115
- end
116
- end
117
- with_connection :find_vcard
118
-
119
- def save_vcard(jid, card)
120
- xuser = user_by_jid(jid)
121
- if xuser
122
- xuser.vcard = card.to_xml
123
- xuser.save
124
- end
125
- end
126
- with_connection :save_vcard
127
-
128
- def find_fragment(jid, node)
129
- jid = JID.new(jid).bare.to_s
130
- return if jid.empty?
131
- if fragment = fragment_by_jid(jid, node)
132
- Nokogiri::XML(fragment.xml).root rescue nil
133
- end
134
- end
135
- with_connection :find_fragment
136
-
137
- def save_fragment(jid, node)
138
- jid = JID.new(jid).bare.to_s
139
- fragment = fragment_by_jid(jid, node) ||
140
- Sql::Fragment.new(
141
- user: user_by_jid(jid),
142
- root: node.name,
143
- namespace: node.namespace.href)
144
- fragment.xml = node.to_xml
145
- fragment.save
146
- end
147
- with_connection :save_fragment
148
-
149
- # Create the tables and indexes used by this storage engine.
150
- def create_schema(args={})
151
- args[:force] ||= false
152
-
153
- ActiveRecord::Schema.define do
154
- create_table :users, force: args[:force] do |t|
155
- t.string :jid, limit: 512, null: false
156
- t.string :name, limit: 256, null: true
157
- t.string :password, limit: 256, null: true
158
- t.text :vcard, null: true
159
- end
160
- add_index :users, :jid, unique: true
161
-
162
- create_table :contacts, force: args[:force] do |t|
163
- t.integer :user_id, null: false
164
- t.string :jid, limit: 512, null: false
165
- t.string :name, limit: 256, null: true
166
- t.string :ask, limit: 128, null: true
167
- t.string :subscription, limit: 128, null: false
168
- end
169
- add_index :contacts, [:user_id, :jid], unique: true
170
-
171
- create_table :groups, force: args[:force] do |t|
172
- t.string :name, limit: 256, null: false
173
- end
174
- add_index :groups, :name, unique: true
175
-
176
- create_table :contacts_groups, id: false, force: args[:force] do |t|
177
- t.integer :contact_id, null: false
178
- t.integer :group_id, null: false
179
- end
180
- add_index :contacts_groups, [:contact_id, :group_id], unique: true
181
-
182
- create_table :fragments, force: args[:force] do |t|
183
- t.integer :user_id, null: false
184
- t.string :root, limit: 256, null: false
185
- t.string :namespace, limit: 256, null: false
186
- t.text :xml, null: false
187
- end
188
- add_index :fragments, [:user_id, :root, :namespace], unique: true
189
- end
190
- end
191
- with_connection :create_schema, defer: false
192
-
193
- private
194
-
195
- def establish_connection
196
- ActiveRecord::Base.logger = Logger.new('/dev/null')
197
- ActiveRecord::Base.establish_connection(@config)
198
- # has_and_belongs_to_many requires a connection so configure the
199
- # associations here rather than in the class definitions above.
200
- Sql::Contact.has_and_belongs_to_many :groups
201
- Sql::Group.has_and_belongs_to_many :contacts
202
- end
203
-
204
- def user_by_jid(jid)
205
- jid = JID.new(jid).bare.to_s
206
- Sql::User.find_by_jid(jid, :include => {:contacts => :groups})
207
- end
208
-
209
- def fragment_by_jid(jid, node)
210
- jid = JID.new(jid).bare.to_s
211
- clause = 'user_id=(select id from users where jid=?) and root=? and namespace=?'
212
- Sql::Fragment.where(clause, jid, node.name, node.namespace.href).first
213
- end
214
-
215
- def groups(contact)
216
- contact.groups.map {|name| Sql::Group.find_or_create_by_name(name.strip) }
217
- end
218
- end
219
- end
220
- end