vinesmod 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. data/Gemfile +3 -0
  2. data/LICENSE +19 -0
  3. data/README.md +43 -0
  4. data/Rakefile +57 -0
  5. data/bin/vines +93 -0
  6. data/conf/certs/README +39 -0
  7. data/conf/certs/ca-bundle.crt +3366 -0
  8. data/conf/config.rb +149 -0
  9. data/lib/vines.rb +197 -0
  10. data/lib/vines/cluster.rb +246 -0
  11. data/lib/vines/cluster/connection.rb +26 -0
  12. data/lib/vines/cluster/publisher.rb +55 -0
  13. data/lib/vines/cluster/pubsub.rb +92 -0
  14. data/lib/vines/cluster/sessions.rb +125 -0
  15. data/lib/vines/cluster/subscriber.rb +108 -0
  16. data/lib/vines/command/bcrypt.rb +12 -0
  17. data/lib/vines/command/cert.rb +50 -0
  18. data/lib/vines/command/init.rb +68 -0
  19. data/lib/vines/command/register.rb +27 -0
  20. data/lib/vines/command/restart.rb +12 -0
  21. data/lib/vines/command/schema.rb +24 -0
  22. data/lib/vines/command/start.rb +28 -0
  23. data/lib/vines/command/stop.rb +18 -0
  24. data/lib/vines/command/unregister.rb +27 -0
  25. data/lib/vines/config.rb +213 -0
  26. data/lib/vines/config/host.rb +119 -0
  27. data/lib/vines/config/port.rb +132 -0
  28. data/lib/vines/config/pubsub.rb +108 -0
  29. data/lib/vines/contact.rb +111 -0
  30. data/lib/vines/daemon.rb +78 -0
  31. data/lib/vines/error.rb +150 -0
  32. data/lib/vines/jid.rb +95 -0
  33. data/lib/vines/kit.rb +35 -0
  34. data/lib/vines/log.rb +24 -0
  35. data/lib/vines/router.rb +179 -0
  36. data/lib/vines/stanza.rb +175 -0
  37. data/lib/vines/stanza/iq.rb +48 -0
  38. data/lib/vines/stanza/iq/auth.rb +18 -0
  39. data/lib/vines/stanza/iq/disco_info.rb +45 -0
  40. data/lib/vines/stanza/iq/disco_items.rb +29 -0
  41. data/lib/vines/stanza/iq/error.rb +16 -0
  42. data/lib/vines/stanza/iq/ping.rb +16 -0
  43. data/lib/vines/stanza/iq/private_storage.rb +83 -0
  44. data/lib/vines/stanza/iq/query.rb +10 -0
  45. data/lib/vines/stanza/iq/register.rb +42 -0
  46. data/lib/vines/stanza/iq/result.rb +16 -0
  47. data/lib/vines/stanza/iq/roster.rb +140 -0
  48. data/lib/vines/stanza/iq/session.rb +17 -0
  49. data/lib/vines/stanza/iq/vcard.rb +56 -0
  50. data/lib/vines/stanza/iq/version.rb +25 -0
  51. data/lib/vines/stanza/message.rb +43 -0
  52. data/lib/vines/stanza/presence.rb +156 -0
  53. data/lib/vines/stanza/presence/error.rb +23 -0
  54. data/lib/vines/stanza/presence/probe.rb +37 -0
  55. data/lib/vines/stanza/presence/subscribe.rb +42 -0
  56. data/lib/vines/stanza/presence/subscribed.rb +51 -0
  57. data/lib/vines/stanza/presence/unavailable.rb +15 -0
  58. data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
  59. data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
  60. data/lib/vines/stanza/pubsub.rb +22 -0
  61. data/lib/vines/stanza/pubsub/create.rb +39 -0
  62. data/lib/vines/stanza/pubsub/delete.rb +41 -0
  63. data/lib/vines/stanza/pubsub/publish.rb +66 -0
  64. data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
  65. data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
  66. data/lib/vines/storage.rb +188 -0
  67. data/lib/vines/storage/local.rb +165 -0
  68. data/lib/vines/storage/null.rb +39 -0
  69. data/lib/vines/storage/sql.rb +260 -0
  70. data/lib/vines/store.rb +94 -0
  71. data/lib/vines/stream.rb +247 -0
  72. data/lib/vines/stream/client.rb +84 -0
  73. data/lib/vines/stream/client/auth.rb +74 -0
  74. data/lib/vines/stream/client/auth_restart.rb +29 -0
  75. data/lib/vines/stream/client/bind.rb +72 -0
  76. data/lib/vines/stream/client/bind_restart.rb +24 -0
  77. data/lib/vines/stream/client/closed.rb +13 -0
  78. data/lib/vines/stream/client/ready.rb +17 -0
  79. data/lib/vines/stream/client/session.rb +210 -0
  80. data/lib/vines/stream/client/start.rb +27 -0
  81. data/lib/vines/stream/client/tls.rb +38 -0
  82. data/lib/vines/stream/component.rb +58 -0
  83. data/lib/vines/stream/component/handshake.rb +26 -0
  84. data/lib/vines/stream/component/ready.rb +23 -0
  85. data/lib/vines/stream/component/start.rb +19 -0
  86. data/lib/vines/stream/http.rb +157 -0
  87. data/lib/vines/stream/http/auth.rb +22 -0
  88. data/lib/vines/stream/http/bind.rb +32 -0
  89. data/lib/vines/stream/http/bind_restart.rb +37 -0
  90. data/lib/vines/stream/http/ready.rb +29 -0
  91. data/lib/vines/stream/http/request.rb +172 -0
  92. data/lib/vines/stream/http/session.rb +120 -0
  93. data/lib/vines/stream/http/sessions.rb +65 -0
  94. data/lib/vines/stream/http/start.rb +23 -0
  95. data/lib/vines/stream/parser.rb +78 -0
  96. data/lib/vines/stream/sasl.rb +92 -0
  97. data/lib/vines/stream/server.rb +150 -0
  98. data/lib/vines/stream/server/auth.rb +13 -0
  99. data/lib/vines/stream/server/auth_restart.rb +13 -0
  100. data/lib/vines/stream/server/final_restart.rb +21 -0
  101. data/lib/vines/stream/server/outbound/auth.rb +31 -0
  102. data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
  103. data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
  104. data/lib/vines/stream/server/outbound/final_features.rb +28 -0
  105. data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
  106. data/lib/vines/stream/server/outbound/start.rb +20 -0
  107. data/lib/vines/stream/server/outbound/tls.rb +30 -0
  108. data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
  109. data/lib/vines/stream/server/ready.rb +24 -0
  110. data/lib/vines/stream/server/start.rb +13 -0
  111. data/lib/vines/stream/server/tls.rb +13 -0
  112. data/lib/vines/stream/state.rb +60 -0
  113. data/lib/vines/token_bucket.rb +55 -0
  114. data/lib/vines/user.rb +123 -0
  115. data/lib/vines/version.rb +5 -0
  116. data/lib/vines/xmpp_server.rb +43 -0
  117. data/vines.gemspec +36 -0
  118. data/web/404.html +51 -0
  119. data/web/apple-touch-icon.png +0 -0
  120. data/web/chat/coffeescripts/chat.coffee +362 -0
  121. data/web/chat/coffeescripts/init.coffee +15 -0
  122. data/web/chat/index.html +16 -0
  123. data/web/chat/javascripts/app.js +1 -0
  124. data/web/chat/stylesheets/chat.css +144 -0
  125. data/web/favicon.png +0 -0
  126. data/web/lib/coffeescripts/button.coffee +25 -0
  127. data/web/lib/coffeescripts/contact.coffee +32 -0
  128. data/web/lib/coffeescripts/filter.coffee +49 -0
  129. data/web/lib/coffeescripts/layout.coffee +30 -0
  130. data/web/lib/coffeescripts/login.coffee +68 -0
  131. data/web/lib/coffeescripts/logout.coffee +5 -0
  132. data/web/lib/coffeescripts/navbar.coffee +84 -0
  133. data/web/lib/coffeescripts/notification.coffee +14 -0
  134. data/web/lib/coffeescripts/router.coffee +40 -0
  135. data/web/lib/coffeescripts/session.coffee +229 -0
  136. data/web/lib/coffeescripts/transfer.coffee +106 -0
  137. data/web/lib/images/dark-gray.png +0 -0
  138. data/web/lib/images/default-user.png +0 -0
  139. data/web/lib/images/light-gray.png +0 -0
  140. data/web/lib/images/logo-large.png +0 -0
  141. data/web/lib/images/logo-small.png +0 -0
  142. data/web/lib/images/white.png +0 -0
  143. data/web/lib/javascripts/base.js +12 -0
  144. data/web/lib/javascripts/icons.js +110 -0
  145. data/web/lib/javascripts/jquery.cookie.js +91 -0
  146. data/web/lib/javascripts/jquery.js +4 -0
  147. data/web/lib/javascripts/raphael.js +6 -0
  148. data/web/lib/javascripts/strophe.js +1 -0
  149. data/web/lib/stylesheets/base.css +385 -0
  150. data/web/lib/stylesheets/login.css +68 -0
  151. metadata +423 -0
@@ -0,0 +1,165 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Storage
5
+
6
+ # A storage implementation that persists data to YAML files on the
7
+ # local file system.
8
+ class Local < Storage
9
+ register :fs
10
+
11
+ def initialize(&block)
12
+ @dir = nil
13
+ instance_eval(&block)
14
+ unless @dir && File.directory?(@dir) && File.writable?(@dir)
15
+ raise 'Must provide a writable storage directory'
16
+ end
17
+
18
+ %w[user vcard fragment room message].each do |sub|
19
+ sub = File.expand_path(sub, @dir)
20
+ Dir.mkdir(sub, 0700) unless File.exists?(sub)
21
+ end
22
+ end
23
+
24
+ def dir(dir=nil)
25
+ dir ? @dir = File.expand_path(dir) : @dir
26
+ end
27
+
28
+ def find_user(jid)
29
+ jid = JID.new(jid).bare.to_s
30
+ file = "user/#{jid}" unless jid.empty?
31
+ record = YAML.load(read(file)) rescue nil
32
+ return User.new(jid: jid).tap do |user|
33
+ user.name, user.password = record.values_at('name', 'password')
34
+ (record['roster'] || {}).each_pair do |jid, props|
35
+ user.roster << Contact.new(
36
+ jid: jid,
37
+ name: props['name'],
38
+ subscription: props['subscription'],
39
+ ask: props['ask'],
40
+ groups: props['groups'] || [])
41
+ end
42
+ end if record
43
+ end
44
+
45
+ def save_user(user)
46
+ record = {'name' => user.name, 'password' => user.password, 'roster' => {}}
47
+ user.roster.each do |contact|
48
+ record['roster'][contact.jid.bare.to_s] = contact.to_h
49
+ end
50
+ save("user/#{user.jid.bare}", YAML.dump(record))
51
+ end
52
+
53
+ def offline_messages_present?(jid)
54
+ File.exist?(absolute_path("message/#{jid.bare.to_s}"))
55
+ end
56
+
57
+ def delete_offline_messages(jid)
58
+ if offline_messages_present?(jid)
59
+ File.delete(absolute_path("message/#{jid.bare.to_s}"))
60
+ end
61
+ end
62
+
63
+ def fetch_offline_messages(jid)
64
+ jid = JID.new(jid).bare.to_s
65
+ file = absolute_path("message/#{jid}") unless jid.empty?
66
+ offline_msgs = YAML.load_file(file) rescue {}
67
+ end
68
+
69
+ def save_offline_message(msg)
70
+ file = "message/#{msg[:to]}"
71
+ offline_msgs = YAML.load_file(absolute_path(file)) rescue []
72
+ msg.delete('to')
73
+ offline_msgs << msg
74
+ save(file) do |f|
75
+ YAML.dump(offline_msgs,f)
76
+ end
77
+ end
78
+
79
+ def find_vcard(jid)
80
+ jid = JID.new(jid).bare.to_s
81
+ return if jid.empty?
82
+ file = "vcard/#{jid}"
83
+ Nokogiri::XML(read(file)).root rescue nil
84
+ end
85
+
86
+ def save_vcard(jid, card)
87
+ jid = JID.new(jid).bare.to_s
88
+ return if jid.empty?
89
+ save("vcard/#{jid}", card.to_xml)
90
+ end
91
+
92
+ def find_fragment(jid, node)
93
+ jid = JID.new(jid).bare.to_s
94
+ return if jid.empty?
95
+ file = 'fragment/%s' % fragment_id(jid, node)
96
+ Nokogiri::XML(read(file)).root rescue nil
97
+ end
98
+
99
+ def save_fragment(jid, node)
100
+ jid = JID.new(jid).bare.to_s
101
+ return if jid.empty?
102
+ file = 'fragment/%s' % fragment_id(jid, node)
103
+ save(file, node.to_xml)
104
+ end
105
+
106
+ private
107
+
108
+ # Resolves a relative file name into an absolute path inside the
109
+ # storage directory.
110
+ #
111
+ # file - A fully-qualified or relative file name String.
112
+ #
113
+ # Returns the fully-qualified file path String.
114
+ #
115
+ # Raises RuntimeError if the resolved path is outside of the storage
116
+ # directory. This prevents directory path traversals with maliciously
117
+ # crafted JIDs.
118
+ def absolute_path(file)
119
+ File.expand_path(file, @dir).tap do |absolute|
120
+ parent = File.dirname(File.dirname(absolute))
121
+ raise "path traversal failed for #{file}" unless parent == @dir
122
+ end
123
+ end
124
+
125
+ # Read the file from the filesystem and return its contents as a String.
126
+ # All files are assumed to be encoded as UTF-8.
127
+ #
128
+ # file - A fully-qualified or relative file name String.
129
+ #
130
+ # Returns the file content as a UTF-8 encoded String.
131
+ def read(file)
132
+ file = absolute_path(file)
133
+ File.read(file, encoding: 'utf-8')
134
+ end
135
+
136
+ # Write the content to the file. Make sure to consistently encode files
137
+ # we read and write as UTF-8.
138
+ #
139
+ # file - A fully-qualified or relative file name String.
140
+ # content - The String to write.
141
+ #
142
+ # Returns nothing.
143
+ def save(file, content)
144
+ file = absolute_path(file)
145
+ File.open(file, 'w:utf-8') {|f| f.write(content) }
146
+ File.chmod(0600, file)
147
+ end
148
+
149
+ # Generates a unique file id for the user's private XML fragment.
150
+ #
151
+ # Private XML fragment storage needs to uniquely identify fragment files
152
+ # on disk. We combine the user's JID with a SHA-1 hash of the element's
153
+ # name and namespace to avoid special characters in the file name.
154
+ #
155
+ # jid - A bare JID identifying the user who owns this fragment.
156
+ # node - A Nokogiri::XML::Node for the XML to be stored.
157
+ #
158
+ # Returns an id String suitable for use in a file name.
159
+ def fragment_id(jid, node)
160
+ id = Digest::SHA1.hexdigest("#{node.name}:#{node.namespace.href}")
161
+ "#{jid}-#{id}"
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Storage
5
+ # A storage implementation that does not persist data to any form of storage.
6
+ # When looking up the storage object for a domain, it's easier to treat a
7
+ # missing domain with a Null storage than checking for nil.
8
+ #
9
+ # For example, presence subscription stanzas sent to a pubsub subdomain
10
+ # have no storage. Rather than checking for nil storage or pubsub addresses,
11
+ # it's easier to treat stanzas to pubsub domains as Null storage that never
12
+ # finds or saves users and their rosters.
13
+ class Null < Storage
14
+ def find_user(jid)
15
+ nil
16
+ end
17
+
18
+ def save_user(user)
19
+ # do nothing
20
+ end
21
+
22
+ def find_vcard(jid)
23
+ nil
24
+ end
25
+
26
+ def save_vcard(jid, card)
27
+ # do nothing
28
+ end
29
+
30
+ def find_fragment(jid, node)
31
+ nil
32
+ end
33
+
34
+ def save_fragment(jid, node)
35
+ # do nothing
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,260 @@
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
+
16
+ class OfflineMessage < ActiveRecord::Base
17
+ belongs_to :sender,:foreign_key=>"from",:class_name=>"User"
18
+ belongs_to :recipient,:foreign_key=>"to",:class_name=>"User"
19
+ end
20
+
21
+ class User < ActiveRecord::Base
22
+ has_many :contacts, :dependent => :destroy
23
+ has_many :fragments, :dependent => :delete_all
24
+ end
25
+
26
+ # Wrap the method with ActiveRecord connection pool logic, so we properly
27
+ # return connections to the pool when we're finished with them. This also
28
+ # defers the original method by pushing it onto the EM thread pool because
29
+ # ActiveRecord uses blocking IO.
30
+ def self.with_connection(method, args={})
31
+ deferrable = args.key?(:defer) ? args[:defer] : true
32
+ old = instance_method(method)
33
+ define_method method do |*args|
34
+ ActiveRecord::Base.connection_pool.with_connection do
35
+ old.bind(self).call(*args)
36
+ end
37
+ end
38
+ defer(method) if deferrable
39
+ end
40
+
41
+ %w[adapter host port database username password pool].each do |name|
42
+ define_method(name) do |*args|
43
+ if args.first
44
+ @config[name.to_sym] = args.first
45
+ else
46
+ @config[name.to_sym]
47
+ end
48
+ end
49
+ end
50
+
51
+ def initialize(&block)
52
+ @config = {}
53
+ instance_eval(&block)
54
+ required = [:adapter, :database]
55
+ required << [:host, :port] unless @config[:adapter] == 'sqlite3'
56
+ required.flatten.each {|key| raise "Must provide #{key}" unless @config[key] }
57
+ [:username, :password].each {|key| @config.delete(key) if empty?(@config[key]) }
58
+ establish_connection
59
+ end
60
+
61
+ def find_user(jid)
62
+ jid = JID.new(jid).bare.to_s
63
+ return if jid.empty?
64
+ xuser = user_by_jid(jid)
65
+ return Vines::User.new(jid: jid).tap do |user|
66
+ user.name, user.password = xuser.name, xuser.password
67
+ xuser.contacts.each do |contact|
68
+ groups = contact.groups.map {|group| group.name }
69
+ user.roster << Vines::Contact.new(
70
+ jid: contact.jid,
71
+ name: contact.name,
72
+ subscription: contact.subscription,
73
+ ask: contact.ask,
74
+ groups: groups)
75
+ end
76
+ end if xuser
77
+ end
78
+ with_connection :find_user
79
+
80
+ def save_user(user)
81
+ xuser = user_by_jid(user.jid) || Sql::User.new(jid: user.jid.bare.to_s)
82
+ xuser.name = user.name
83
+ xuser.password = user.password
84
+
85
+ # remove deleted contacts from roster
86
+ xuser.contacts.delete(xuser.contacts.select do |contact|
87
+ !user.contact?(contact.jid)
88
+ end)
89
+
90
+ # update contacts
91
+ xuser.contacts.each do |contact|
92
+ fresh = user.contact(contact.jid)
93
+ contact.update_attributes(
94
+ name: fresh.name,
95
+ ask: fresh.ask,
96
+ subscription: fresh.subscription,
97
+ groups: groups(fresh))
98
+ end
99
+
100
+ # add new contacts to roster
101
+ jids = xuser.contacts.map {|c| c.jid }
102
+ user.roster.select {|contact| !jids.include?(contact.jid.bare.to_s) }
103
+ .each do |contact|
104
+ xuser.contacts.build(
105
+ user: xuser,
106
+ jid: contact.jid.bare.to_s,
107
+ name: contact.name,
108
+ ask: contact.ask,
109
+ subscription: contact.subscription,
110
+ groups: groups(contact))
111
+ end
112
+ xuser.save
113
+ end
114
+ with_connection :save_user
115
+
116
+ def offline_messages_present?(jid)
117
+ jid = JID.new(jid).bare.to_s
118
+ return if jid.empty?
119
+ offline_messages_to_jid(jid)
120
+ end
121
+
122
+ def delete_offline_messages(jid)
123
+ jid = JID.new(jid).bare.to_s
124
+ return if jid.empty?
125
+ Sql::OfflineMessage.destroy_all(:to=>jid)
126
+ end
127
+
128
+ def fetch_offline_messages(jid)
129
+ jid = JID.new(jid).bare.to_s
130
+ offline_msgs = offline_messages_to_jid(jid) || {}
131
+ end
132
+
133
+ def save_offline_message(msg)
134
+ Sql::OfflineMessage.create(msg)
135
+ end
136
+
137
+ def find_vcard(jid)
138
+ jid = JID.new(jid).bare.to_s
139
+ return if jid.empty?
140
+ if xuser = user_by_jid(jid)
141
+ Nokogiri::XML(xuser.vcard).root rescue nil
142
+ end
143
+ end
144
+ with_connection :find_vcard
145
+
146
+ def save_vcard(jid, card)
147
+ xuser = user_by_jid(jid)
148
+ if xuser
149
+ xuser.vcard = card.to_xml
150
+ xuser.save
151
+ end
152
+ end
153
+ with_connection :save_vcard
154
+
155
+ def find_fragment(jid, node)
156
+ jid = JID.new(jid).bare.to_s
157
+ return if jid.empty?
158
+ if fragment = fragment_by_jid(jid, node)
159
+ Nokogiri::XML(fragment.xml).root rescue nil
160
+ end
161
+ end
162
+ with_connection :find_fragment
163
+
164
+ def save_fragment(jid, node)
165
+ jid = JID.new(jid).bare.to_s
166
+ fragment = fragment_by_jid(jid, node) ||
167
+ Sql::Fragment.new(
168
+ user: user_by_jid(jid),
169
+ root: node.name,
170
+ namespace: node.namespace.href)
171
+ fragment.xml = node.to_xml
172
+ fragment.save
173
+ end
174
+ with_connection :save_fragment
175
+
176
+ # Create the tables and indexes used by this storage engine.
177
+ def create_schema(args={})
178
+ args[:force] ||= false
179
+
180
+ ActiveRecord::Schema.define do
181
+ create_table :users, force: args[:force] do |t|
182
+ t.string :jid, limit: 512, null: false
183
+ t.string :name, limit: 256, null: true
184
+ t.string :password, limit: 256, null: true
185
+ t.text :vcard, null: true
186
+ end
187
+ add_index :users, :jid, unique: true
188
+
189
+ create_table :contacts, force: args[:force] do |t|
190
+ t.integer :user_id, null: false
191
+ t.string :jid, limit: 512, null: false
192
+ t.string :name, limit: 256, null: true
193
+ t.string :ask, limit: 128, null: true
194
+ t.string :subscription, limit: 128, null: false
195
+ end
196
+ add_index :contacts, [:user_id, :jid], unique: true
197
+
198
+ create_table :groups, force: args[:force] do |t|
199
+ t.string :name, limit: 256, null: false
200
+ end
201
+ add_index :groups, :name, unique: true
202
+
203
+ create_table :contacts_groups, id: false, force: args[:force] do |t|
204
+ t.integer :contact_id, null: false
205
+ t.integer :group_id, null: false
206
+ end
207
+ add_index :contacts_groups, [:contact_id, :group_id], unique: true
208
+
209
+ create_table :fragments, force: args[:force] do |t|
210
+ t.integer :user_id, null: false
211
+ t.string :root, limit: 256, null: false
212
+ t.string :namespace, limit: 256, null: false
213
+ t.text :xml, null: false
214
+ end
215
+ add_index :fragments, [:user_id, :root, :namespace], unique: true
216
+
217
+ create_table :offline_messages do |t|
218
+ t.string :from, null: false
219
+ t.string :to, limit: 512,null: false
220
+ t.text :body, null: false
221
+ end
222
+ add_index :offline_messages,[:from,:to]
223
+ end
224
+ end
225
+ with_connection :create_schema, defer: false
226
+
227
+ private
228
+
229
+ def establish_connection
230
+ ActiveRecord::Base.logger = Logger.new('/dev/null')
231
+ ActiveRecord::Base.establish_connection(@config)
232
+ # has_and_belongs_to_many requires a connection so configure the
233
+ # associations here rather than in the class definitions above.
234
+ Sql::Contact.has_and_belongs_to_many :groups
235
+ Sql::Group.has_and_belongs_to_many :contacts
236
+ end
237
+
238
+ def user_by_jid(jid)
239
+ jid = JID.new(jid).bare.to_s
240
+ Sql::User.find_by_jid(jid, :include => {:contacts => :groups})
241
+ end
242
+
243
+ def fragment_by_jid(jid, node)
244
+ jid = JID.new(jid).bare.to_s
245
+ clause = 'user_id=(select id from users where jid=?) and root=? and namespace=?'
246
+ Sql::Fragment.where(clause, jid, node.name, node.namespace.href).first
247
+ end
248
+
249
+ def groups(contact)
250
+ contact.groups.map {|name| Sql::Group.find_or_create_by_name(name.strip) }
251
+ end
252
+
253
+ def offline_messages_to_jid(jid)
254
+ jid = JID.new(jid).bare.to_s
255
+ Sql::OfflineMessage.find_all_by_to(jid)
256
+ end
257
+
258
+ end
259
+ end
260
+ end