vines 0.4.5 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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