ixtlan-remote 0.1.0
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.
- data/Gemfile +2 -0
- data/README.md +64 -0
- data/agpl-3.0.txt +661 -0
- data/lib/ixtlan-remote.rb +23 -0
- data/lib/ixtlan/passwords.rb +52 -0
- data/lib/ixtlan/passwords.rb~ +47 -0
- data/lib/ixtlan/railtie.rb~ +17 -0
- data/lib/ixtlan/remote.rb +23 -0
- data/lib/ixtlan/remote/access_controller.rb +62 -0
- data/lib/ixtlan/remote/access_controller.rb~ +25 -0
- data/lib/ixtlan/remote/constant_time_compare.rb +39 -0
- data/lib/ixtlan/remote/constant_time_compare.rb~ +19 -0
- data/lib/ixtlan/remote/heartbeat.rb~ +80 -0
- data/lib/ixtlan/remote/model_conversion.rb~ +245 -0
- data/lib/ixtlan/remote/model_helpers.rb +73 -0
- data/lib/ixtlan/remote/model_helpers.rb~ +53 -0
- data/lib/ixtlan/remote/permission.rb +40 -0
- data/lib/ixtlan/remote/permission.rb~ +19 -0
- data/lib/ixtlan/remote/railtie.rb +30 -0
- data/lib/ixtlan/remote/railtie.rb~ +9 -0
- data/lib/ixtlan/remote/remote_access_controller.rb~ +25 -0
- data/lib/ixtlan/remote/remote_permisson.rb~ +9 -0
- data/lib/ixtlan/remote/resource.rb +145 -0
- data/lib/ixtlan/remote/resource.rb-~ +165 -0
- data/lib/ixtlan/remote/resource.rb~ +152 -0
- data/lib/ixtlan/remote/rest.rb +108 -0
- data/lib/ixtlan/remote/rest.rb~ +83 -0
- data/lib/ixtlan/remote/rest_resource.rb~ +140 -0
- data/lib/ixtlan/remote/rest_resource_config.rb~ +63 -0
- data/lib/ixtlan/remote/rest_resource_factory.rb~ +259 -0
- data/lib/ixtlan/remote/rest_resource_old.rb-~ +259 -0
- data/lib/ixtlan/remote/server.rb +119 -0
- data/lib/ixtlan/remote/server.rb~ +82 -0
- data/lib/ixtlan/remote/summary.rb +46 -0
- data/lib/ixtlan/remote/summary.rb~ +23 -0
- data/lib/ixtlan/remote/sync.rb +104 -0
- data/lib/ixtlan/remote/sync.rb~ +78 -0
- data/lib/ixtlan/remote/sync_summary.rb~ +23 -0
- data/lib/ixtlan/remote/tranlation_key.rb~ +7 -0
- data/lib/ixtlan/remote/translation.rake~ +194 -0
- data/lib/ixtlan/remote/translation_models.rb~ +11 -0
- data/lib/ixtlan/remote/updater.rb~ +71 -0
- data/lib/ixtlan/user_management/application_model.rb +30 -0
- data/lib/ixtlan/user_management/application_model.rb~ +21 -0
- data/lib/ixtlan/user_management/application_resource.rb +48 -0
- data/lib/ixtlan/user_management/application_resource.rb~ +21 -0
- data/lib/ixtlan/user_management/authentcator.rb~ +31 -0
- data/lib/ixtlan/user_management/authentication_model.rb +31 -0
- data/lib/ixtlan/user_management/authentication_model.rb~ +21 -0
- data/lib/ixtlan/user_management/authenticator.rb +55 -0
- data/lib/ixtlan/user_management/authenticator.rb~ +20 -0
- data/lib/ixtlan/user_management/domain_resource.rb +48 -0
- data/lib/ixtlan/user_management/domain_resource.rb~ +21 -0
- data/lib/ixtlan/user_management/dummy_authentication.rb +50 -0
- data/lib/ixtlan/user_management/dummy_authentication.rb~ +49 -0
- data/lib/ixtlan/user_management/group.rb~ +39 -0
- data/lib/ixtlan/user_management/group_model.rb +31 -0
- data/lib/ixtlan/user_management/group_model.rb~ +21 -0
- data/lib/ixtlan/user_management/models.rb~ +39 -0
- data/lib/ixtlan/user_management/session-serializer.rb~ +18 -0
- data/lib/ixtlan/user_management/session_cuba.rb +47 -0
- data/lib/ixtlan/user_management/session_cuba.rb~ +44 -0
- data/lib/ixtlan/user_management/session_manager.rb +38 -0
- data/lib/ixtlan/user_management/session_model.rb +36 -0
- data/lib/ixtlan/user_management/session_model.rb~ +10 -0
- data/lib/ixtlan/user_management/session_plugin.rb +32 -0
- data/lib/ixtlan/user_management/session_serializer.rb +21 -0
- data/lib/ixtlan/user_management/session_serializer.rb~ +21 -0
- data/lib/ixtlan/user_management/user.rb~ +16 -0
- data/lib/ixtlan/user_management/user_model.rb +36 -0
- data/lib/ixtlan/user_management/user_model.rb~ +33 -0
- data/lib/ixtlan/user_management/user_resource.rb +55 -0
- data/lib/ixtlan/user_management/user_resource.rb~ +24 -0
- data/lib/ixtlan/user_management/user_serializer.rb +15 -0
- data/lib/ixtlan/user_management/user_serializer.rb~ +23 -0
- data/spec/access_controller_spec.rb +65 -0
- data/spec/access_controller_spec.rb~ +65 -0
- data/spec/model_helpers_spec.rb +40 -0
- data/spec/model_helpers_spec.rb~ +36 -0
- data/spec/remote_access_controller_spec.rb~ +36 -0
- data/spec/resource_spec.rb +181 -0
- data/spec/resource_spec.rb~ +173 -0
- data/spec/rest_resource_spec.rb~ +173 -0
- data/spec/rest_spec.rb +94 -0
- data/spec/rest_spec.rb~ +99 -0
- data/spec/rest_with_attribute_name_like_model_name_spec.rb +82 -0
- data/spec/sync_spec.rb +83 -0
- data/spec/sync_spec.rb~ +81 -0
- metadata +313 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#
|
|
2
|
+
# ixtlan-remote - helper sync data between miniapps or communicate wth realtime
|
|
3
|
+
# rest-services
|
|
4
|
+
# Copyright (C) 2012 Christian Meier
|
|
5
|
+
#
|
|
6
|
+
# This file is part of ixtlan-remote.
|
|
7
|
+
#
|
|
8
|
+
# ixtlan-remote is free software: you can redistribute it and/or modify
|
|
9
|
+
# it under the terms of the GNU Affero General Public License as
|
|
10
|
+
# published by the Free Software Foundation, either version 3 of the
|
|
11
|
+
# License, or (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# ixtlan-remote is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU Affero General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
19
|
+
# along with ixtlan-remote. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
#
|
|
21
|
+
if defined?(Rails)
|
|
22
|
+
require 'ixtlan/remote/railtie'
|
|
23
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'erb'
|
|
3
|
+
module Ixtlan
|
|
4
|
+
class HashWithDefault < ::Hash
|
|
5
|
+
def get( key, default = HashWithDefault.new )
|
|
6
|
+
self[ key ] || default
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class Passwords
|
|
11
|
+
|
|
12
|
+
def self.symbolize_keys(h)
|
|
13
|
+
result = HashWithDefault.new
|
|
14
|
+
|
|
15
|
+
h.each do |k, v|
|
|
16
|
+
v = ' ' if v.nil?
|
|
17
|
+
if v.is_a?(Hash)
|
|
18
|
+
result[k.to_sym] = symbolize_keys(v) unless v.size == 0
|
|
19
|
+
else
|
|
20
|
+
result[k.to_sym] = v unless k.to_sym == v.to_sym
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
result
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.load( file )
|
|
27
|
+
rel_file = File.expand_path( file ).sub( /#{File.expand_path '.' }\/?/, '' )
|
|
28
|
+
if File.exists?( file )
|
|
29
|
+
warn "[Passwords] Loaded #{rel_file} file"
|
|
30
|
+
symbolize_keys( YAML::load( ERB.new( IO.read( file ) ).result ) )
|
|
31
|
+
else
|
|
32
|
+
warn "[Passwords] No #{rel_file} file to load"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.config( file = 'password.yml' )
|
|
37
|
+
@config ||= load( file ) || HashWithDefault.new
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.[]( key )
|
|
41
|
+
@config[ key ]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.get( key, default = nil )
|
|
45
|
+
if default
|
|
46
|
+
@config.get( key, default )
|
|
47
|
+
else
|
|
48
|
+
@config.get( key )
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'erb'
|
|
3
|
+
module Ixtlan
|
|
4
|
+
class Hash < ::Hash
|
|
5
|
+
def get( key, default = {} )
|
|
6
|
+
self[ key ] || default
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class Passwords
|
|
11
|
+
|
|
12
|
+
def self.symbolize_keys(h)
|
|
13
|
+
result = Hash.new
|
|
14
|
+
|
|
15
|
+
h.each do |k, v|
|
|
16
|
+
v = ' ' if v.nil?
|
|
17
|
+
if v.is_a?(Hash)
|
|
18
|
+
result[k.to_sym] = symbolize_keys(v) unless v.size == 0
|
|
19
|
+
else
|
|
20
|
+
result[k.to_sym] = v unless k.to_sym == v.to_sym
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
result
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.load( file )
|
|
28
|
+
if File.exists?(file)
|
|
29
|
+
symbolize_keys(YAML::load(ERB.new(IO.read(file)).result))
|
|
30
|
+
else
|
|
31
|
+
warn "no #{file} file to load"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.config( file = 'password.yml' )
|
|
36
|
+
@config ||= load( file ) || {}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.[]( key )
|
|
40
|
+
@config[ key ]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.get( key )
|
|
44
|
+
@config.get( key )
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'ixtlan/remote/rest'
|
|
2
|
+
require 'ixtlan/gettext/manager'
|
|
3
|
+
module Ixtlan
|
|
4
|
+
module Remote
|
|
5
|
+
|
|
6
|
+
class Railtie < Rails::Railtie
|
|
7
|
+
|
|
8
|
+
config.rest = Ixtlan::Remote::Rest.new
|
|
9
|
+
|
|
10
|
+
config.gettext = Ixtlan::Gettext::Manager.new
|
|
11
|
+
|
|
12
|
+
rake_tasks do
|
|
13
|
+
load 'ixtlan/gettext/translation.rake'
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#
|
|
2
|
+
# ixtlan-remote - helper sync data between miniapps or communicate wth realtime
|
|
3
|
+
# rest-services
|
|
4
|
+
# Copyright (C) 2012 Christian Meier
|
|
5
|
+
#
|
|
6
|
+
# This file is part of ixtlan-remote.
|
|
7
|
+
#
|
|
8
|
+
# ixtlan-remote is free software: you can redistribute it and/or modify
|
|
9
|
+
# it under the terms of the GNU Affero General Public License as
|
|
10
|
+
# published by the Free Software Foundation, either version 3 of the
|
|
11
|
+
# License, or (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# ixtlan-remote is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU Affero General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
19
|
+
# along with ixtlan-remote. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
#
|
|
21
|
+
if defined?(Rails)
|
|
22
|
+
require 'ixtlan/remote/railtie'
|
|
23
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#
|
|
2
|
+
# ixtlan-remote - helper sync data between miniapps or communicate wth realtime
|
|
3
|
+
# rest-services
|
|
4
|
+
# Copyright (C) 2012 Christian Meier
|
|
5
|
+
#
|
|
6
|
+
# This file is part of ixtlan-remote.
|
|
7
|
+
#
|
|
8
|
+
# ixtlan-remote is free software: you can redistribute it and/or modify
|
|
9
|
+
# it under the terms of the GNU Affero General Public License as
|
|
10
|
+
# published by the Free Software Foundation, either version 3 of the
|
|
11
|
+
# License, or (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# ixtlan-remote is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU Affero General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
19
|
+
# along with ixtlan-remote. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
#
|
|
21
|
+
require 'ixtlan/remote/constant_time_compare'
|
|
22
|
+
module Ixtlan
|
|
23
|
+
module Remote
|
|
24
|
+
module AccessController
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
include Ixtlan::Remote::ConstantTimeCompare
|
|
29
|
+
|
|
30
|
+
# this implementation uses rails request instance
|
|
31
|
+
def x_service_token
|
|
32
|
+
request.headers['X-SERVICE-TOKEN']
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
protected
|
|
36
|
+
|
|
37
|
+
def permission_model
|
|
38
|
+
Permission
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
public
|
|
42
|
+
|
|
43
|
+
def remote_permission
|
|
44
|
+
@_remote_permission ||=
|
|
45
|
+
begin
|
|
46
|
+
# constant time for finding the right permission
|
|
47
|
+
perm = nil
|
|
48
|
+
token = x_service_token
|
|
49
|
+
raise "ip #{request.remote_ip} sent no token" unless token
|
|
50
|
+
permission_model.all.each do |rp|
|
|
51
|
+
perm = rp if rp.authentication_token && constant_time_compare(rp.authentication_token, token)
|
|
52
|
+
end
|
|
53
|
+
raise "ip #{request.remote_ip} wrong authentication" unless perm
|
|
54
|
+
# if the perm.ip == nil then do not check IP
|
|
55
|
+
# server clusters have many IPs then use perm.ip = nil
|
|
56
|
+
raise "ip #{request.remote_ip} not allowed" if (!perm.allowed_ip.blank? && request.remote_ip != perm.allowed_ip)
|
|
57
|
+
perm
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'ixtlan/remote/constant_time_compare'
|
|
2
|
+
module Ixtlan
|
|
3
|
+
module Remote
|
|
4
|
+
module AccessController
|
|
5
|
+
include Ixtlan::Remote::ConstantTimeCompare
|
|
6
|
+
|
|
7
|
+
def x_service_token
|
|
8
|
+
@_x_service_token ||= request.headers['X-SERVICE-TOKEN']
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def remote_permission
|
|
12
|
+
# constant time for finding the right permission
|
|
13
|
+
perm = nil
|
|
14
|
+
Permission.all.each do |rp|
|
|
15
|
+
perm = rp if constant_time_compare(rp.token, x_service_token)
|
|
16
|
+
end
|
|
17
|
+
raise "ip #{request.remote_ip} wrong authentication" unless perm
|
|
18
|
+
# if the perm.ip == nil then do not check IP
|
|
19
|
+
# server clusters have many IPs then use perm.ip = nil
|
|
20
|
+
raise "ip #{request.remote_ip} not allowed" if (!perm.ip.blank? && request.remote_ip != perm.ip)
|
|
21
|
+
perm
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#
|
|
2
|
+
# ixtlan-remote - helper sync data between miniapps or communicate wth realtime
|
|
3
|
+
# rest-services
|
|
4
|
+
# Copyright (C) 2012 Christian Meier
|
|
5
|
+
#
|
|
6
|
+
# This file is part of ixtlan-remote.
|
|
7
|
+
#
|
|
8
|
+
# ixtlan-remote is free software: you can redistribute it and/or modify
|
|
9
|
+
# it under the terms of the GNU Affero General Public License as
|
|
10
|
+
# published by the Free Software Foundation, either version 3 of the
|
|
11
|
+
# License, or (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# ixtlan-remote is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU Affero General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
19
|
+
# along with ixtlan-remote. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
#
|
|
21
|
+
module Ixtlan
|
|
22
|
+
module Remote
|
|
23
|
+
module ConstantTimeCompare
|
|
24
|
+
|
|
25
|
+
# from https://github.com/django/django/blob/master/django/utils/crypto.py#L82
|
|
26
|
+
# time to compare is independent of the number of matching characters
|
|
27
|
+
def constant_time_compare(val1, val2)
|
|
28
|
+
if val1.size != val2.size
|
|
29
|
+
return false
|
|
30
|
+
end
|
|
31
|
+
result = 0
|
|
32
|
+
val1.unpack('*C').zip(val2.unpack('*C')).each do |a|
|
|
33
|
+
result |= a[0][0] ^ a[1][0]
|
|
34
|
+
end
|
|
35
|
+
result == 0
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Ixtlan
|
|
2
|
+
module Remote
|
|
3
|
+
module ConstantTimeCompare
|
|
4
|
+
|
|
5
|
+
# from https://github.com/django/django/blob/master/django/utils/crypto.py#L82
|
|
6
|
+
# time to compare is independent of the number of matching characters
|
|
7
|
+
def constant_time_compare(val1, val2)
|
|
8
|
+
if val1.size != val2.size
|
|
9
|
+
return false
|
|
10
|
+
end
|
|
11
|
+
result = 0
|
|
12
|
+
val1.chars.zip(val2.chars).each do |a|
|
|
13
|
+
result |= a[0][0] ^ a[1][0]
|
|
14
|
+
end
|
|
15
|
+
result == 0
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
class Ixtlan::Remote::Heartbeat
|
|
2
|
+
|
|
3
|
+
class Summary
|
|
4
|
+
attr_reader :clazz
|
|
5
|
+
attr_accessor :count, :failures
|
|
6
|
+
|
|
7
|
+
def initialize(clazz)
|
|
8
|
+
@count = 0
|
|
9
|
+
@failures = 0
|
|
10
|
+
@clazz = clazz
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_log
|
|
14
|
+
"update #{@clazz} - total: #{@count + @failures} success: #{@count} failures: #{@failures}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(restserver)
|
|
19
|
+
@count = 0
|
|
20
|
+
@failures = 0
|
|
21
|
+
@restserver = restserver
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def clazzes
|
|
25
|
+
@clazzes ||= []
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def register(clazz)
|
|
29
|
+
c = clazz.to_s.pluralize.underscore
|
|
30
|
+
clazzes << c unless clazzes.member?(c)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
if defined? ActiveRecord
|
|
34
|
+
def max(clazz)
|
|
35
|
+
@restserver.to_class(clazz).maximum(:updated_at)
|
|
36
|
+
end
|
|
37
|
+
else
|
|
38
|
+
def max(clazz)
|
|
39
|
+
@restserver.to_class(clazz).max(:updated_at)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
private :max
|
|
43
|
+
|
|
44
|
+
def last_update(clazz)
|
|
45
|
+
@last_update ||= begin
|
|
46
|
+
last_date = (max(clazz) || 2000.years.ago).utc
|
|
47
|
+
p last_date
|
|
48
|
+
last_date.strftime('%Y-%m-%d %H:%M:%S.') + ("%06d" % (last_date.sec_fraction / Date::NANOSECONDS_IN_DAY / 1000))
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
private :last_update
|
|
52
|
+
|
|
53
|
+
def beat(set = clazzes)
|
|
54
|
+
@last_result = []
|
|
55
|
+
set.to_a.each do |clazz|
|
|
56
|
+
summary = Summary.new(clazz)
|
|
57
|
+
@last_result << summary
|
|
58
|
+
@restserver.retrieve(clazz, :last_changes, :updated_at => last_update(clazz)).each do |item|
|
|
59
|
+
p item
|
|
60
|
+
result = item.save
|
|
61
|
+
if result
|
|
62
|
+
summary.count = summary.count + 1
|
|
63
|
+
else
|
|
64
|
+
p item.new?
|
|
65
|
+
p item.errors
|
|
66
|
+
summary.failures = summary.failures + 1
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
to_log
|
|
71
|
+
end
|
|
72
|
+
def to_log
|
|
73
|
+
if @last_result
|
|
74
|
+
@last_result.collect { |r| r.to_log }.join("\n\t")
|
|
75
|
+
else
|
|
76
|
+
"no results yet"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
alias :to_s :to_log
|
|
80
|
+
end
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
require 'rest-client'
|
|
2
|
+
module Ixtlan
|
|
3
|
+
module Remote
|
|
4
|
+
module ModelHelpers
|
|
5
|
+
|
|
6
|
+
def to_model_underscore(data)
|
|
7
|
+
to_model(data).to_s.underscore
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def to_model_singular_underscore(data)
|
|
11
|
+
to_model.underscore.singular
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_model(data)
|
|
15
|
+
case data
|
|
16
|
+
when Hash
|
|
17
|
+
data.keys.first
|
|
18
|
+
when Array
|
|
19
|
+
if data.empty?
|
|
20
|
+
#TODO
|
|
21
|
+
else
|
|
22
|
+
to_model_class(data[0].class)
|
|
23
|
+
end
|
|
24
|
+
when String
|
|
25
|
+
data
|
|
26
|
+
when Symbol
|
|
27
|
+
data
|
|
28
|
+
when Class
|
|
29
|
+
to_model_class(data)
|
|
30
|
+
else
|
|
31
|
+
to_model_class(data.class)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_model_class(obj)
|
|
36
|
+
obj.respond_to?(:model_name) ? obj.model_name.constantize : obj.class
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def model_map
|
|
40
|
+
@_model_map ||= {}
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class RestResourceConfig
|
|
45
|
+
|
|
46
|
+
def register(server_name, url, options = {})
|
|
47
|
+
models = options.delete(:models).dup
|
|
48
|
+
#model_map =
|
|
49
|
+
case models
|
|
50
|
+
when Array
|
|
51
|
+
map = {}
|
|
52
|
+
models.each do |model|
|
|
53
|
+
model_map[to_model_singular_underscore(model)] = model
|
|
54
|
+
end
|
|
55
|
+
map
|
|
56
|
+
when Hash
|
|
57
|
+
models
|
|
58
|
+
else
|
|
59
|
+
{}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
s = servers[server_name.to_sym] = RestResourceFactory.new(url, model_map, options)
|
|
63
|
+
|
|
64
|
+
model_map.keys.each do |k|
|
|
65
|
+
model_to_servers[k] = s
|
|
66
|
+
end
|
|
67
|
+
s
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def model_to_servers
|
|
71
|
+
@_models ||= {}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def new_resource(model)
|
|
75
|
+
model_to_servers[model].new
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def to_class(model)
|
|
79
|
+
key = to_model_singular_underscore(model)
|
|
80
|
+
model_to_servers[key].to_class(key)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def servers
|
|
84
|
+
@_servers ||= {}
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def server(name)
|
|
88
|
+
servers[name.to_sym]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def create(model, *args)
|
|
92
|
+
new_resource(model).create(model, *args).send
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def retrieve(model, *args)
|
|
96
|
+
resource = new_resource(model)
|
|
97
|
+
if args.last.is_a? Hash
|
|
98
|
+
params = args.delete_at(args.size - 1)
|
|
99
|
+
resource.retrieve(model, *args).query_params(params)
|
|
100
|
+
else
|
|
101
|
+
resource.retrieve(model, *args)
|
|
102
|
+
end
|
|
103
|
+
resource.send
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def update(model, *args)
|
|
107
|
+
new_resource(model).update(model, *args).send
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def delete(model, *args)
|
|
111
|
+
new_resource(model).delete(model, *args).send
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
include ModelHelpers
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
class RestResource
|
|
118
|
+
|
|
119
|
+
include ModelHelpers
|
|
120
|
+
|
|
121
|
+
def initialize(resource, model_map = {})
|
|
122
|
+
@base = resource
|
|
123
|
+
@map = model_map
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def send(&block)
|
|
127
|
+
raise "no method given - call any CRUD method first" unless @method
|
|
128
|
+
headers = {:content_type => :json, :accept => :json}
|
|
129
|
+
headers[:params] = @params if @params
|
|
130
|
+
result =
|
|
131
|
+
if @payload
|
|
132
|
+
@resource.send @method, @payload.to_json, headers, &block
|
|
133
|
+
else
|
|
134
|
+
@resource.send @method, headers, &block
|
|
135
|
+
end
|
|
136
|
+
result = JSON.load(result)
|
|
137
|
+
if @model
|
|
138
|
+
root = @model_key || @model.to_s.underscore
|
|
139
|
+
if result.is_a? Array
|
|
140
|
+
result.collect do |r|
|
|
141
|
+
first_or_new(r[root])
|
|
142
|
+
end
|
|
143
|
+
else
|
|
144
|
+
first_or_new(result[root])
|
|
145
|
+
end
|
|
146
|
+
else
|
|
147
|
+
result
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def query_params(params)
|
|
152
|
+
@params = params
|
|
153
|
+
self
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# retrieve(Locale) => GET /locales
|
|
157
|
+
# retrieve(Locale, 1) => GET /locales/1
|
|
158
|
+
# retrieve(:locales) => GET /locales
|
|
159
|
+
# retrieve(:locales, 1) => GET /locales/1
|
|
160
|
+
def retrieve(*args)
|
|
161
|
+
@method = :get
|
|
162
|
+
@payload = nil
|
|
163
|
+
@params = nil
|
|
164
|
+
@resource = @base[path(*args)]
|
|
165
|
+
self
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# create(locale) => POST /locales
|
|
169
|
+
# create(:locale => {:code => 'en'}) => POST /locales
|
|
170
|
+
def create(*obj_or_hash)
|
|
171
|
+
@method = :post
|
|
172
|
+
@payload = nil#obj_or_hash.is_a?(Class)? nil : obj_or_hash
|
|
173
|
+
@params = nil
|
|
174
|
+
@resource = @base[path(*obj_or_hash)]
|
|
175
|
+
self
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def update(*obj_or_hash)
|
|
179
|
+
@method = :put
|
|
180
|
+
@payload = nil#obj_or_hash.is_a?(Class)? nil : obj_or_hash
|
|
181
|
+
@params = nil
|
|
182
|
+
@resource = @base[path(*obj_or_hash)]
|
|
183
|
+
self
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def delete(*obj_or_hash)
|
|
187
|
+
@method = :delete
|
|
188
|
+
@payload = nil #obj_or_hash.is_a?(Class)? nil : obj_or_hash
|
|
189
|
+
@params = nil
|
|
190
|
+
@resource = @base[path(*obj_or_hash)]
|
|
191
|
+
self
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
private
|
|
195
|
+
|
|
196
|
+
def first_or_new(attributes)
|
|
197
|
+
cond = {}
|
|
198
|
+
@model.key.each { |k| cond[k.name] = attributes[k.name.to_s] }
|
|
199
|
+
m = @model.first_or_new(cond)
|
|
200
|
+
m.attributes = attributes
|
|
201
|
+
m
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def path(*parts)
|
|
205
|
+
parts.collect {|p| path_part(p)} * '/'
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def model=(model)
|
|
209
|
+
m = @map[model.singularize]
|
|
210
|
+
m ||= model.singularize.camelize.constantize rescue nil
|
|
211
|
+
if m
|
|
212
|
+
@model = m
|
|
213
|
+
@model_key = model.singularize.underscore
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def path_part(data)
|
|
218
|
+
model = to_model_underscore(data)
|
|
219
|
+
case data
|
|
220
|
+
when Hash
|
|
221
|
+
@payload = data
|
|
222
|
+
self.model = model
|
|
223
|
+
model = model.pluralize
|
|
224
|
+
when Array
|
|
225
|
+
@payload = data
|
|
226
|
+
self.model = model
|
|
227
|
+
model = model.pluralize
|
|
228
|
+
when String
|
|
229
|
+
self.model = model
|
|
230
|
+
when Fixnum
|
|
231
|
+
when Symbol
|
|
232
|
+
self.model = model
|
|
233
|
+
when Class
|
|
234
|
+
model = model.pluralize
|
|
235
|
+
else
|
|
236
|
+
@payload = data
|
|
237
|
+
self.model = model
|
|
238
|
+
model = model.pluralize
|
|
239
|
+
end
|
|
240
|
+
model.underscore
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|