tentd 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rspec +1 -0
- data/.travis.yml +8 -0
- data/Gemfile +9 -0
- data/Guardfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +49 -0
- data/Rakefile +8 -0
- data/bin/tent-server +3 -0
- data/lib/tentd.rb +31 -0
- data/lib/tentd/api.rb +58 -0
- data/lib/tentd/api/apps.rb +196 -0
- data/lib/tentd/api/authentication_finalize.rb +12 -0
- data/lib/tentd/api/authentication_lookup.rb +27 -0
- data/lib/tentd/api/authentication_verification.rb +50 -0
- data/lib/tentd/api/authorizable.rb +21 -0
- data/lib/tentd/api/authorization.rb +14 -0
- data/lib/tentd/api/core_profile_data.rb +45 -0
- data/lib/tentd/api/followers.rb +218 -0
- data/lib/tentd/api/followings.rb +241 -0
- data/lib/tentd/api/groups.rb +161 -0
- data/lib/tentd/api/middleware.rb +32 -0
- data/lib/tentd/api/posts.rb +373 -0
- data/lib/tentd/api/profile.rb +78 -0
- data/lib/tentd/api/router.rb +123 -0
- data/lib/tentd/api/router/caching_headers.rb +49 -0
- data/lib/tentd/api/router/extract_params.rb +88 -0
- data/lib/tentd/api/router/serialize_response.rb +38 -0
- data/lib/tentd/api/user_lookup.rb +10 -0
- data/lib/tentd/core_ext/hash/slice.rb +29 -0
- data/lib/tentd/datamapper/array_property.rb +23 -0
- data/lib/tentd/datamapper/query.rb +19 -0
- data/lib/tentd/json_patch.rb +181 -0
- data/lib/tentd/model.rb +30 -0
- data/lib/tentd/model/app.rb +68 -0
- data/lib/tentd/model/app_authorization.rb +113 -0
- data/lib/tentd/model/follower.rb +105 -0
- data/lib/tentd/model/following.rb +100 -0
- data/lib/tentd/model/group.rb +24 -0
- data/lib/tentd/model/mention.rb +19 -0
- data/lib/tentd/model/notification_subscription.rb +56 -0
- data/lib/tentd/model/permissible.rb +227 -0
- data/lib/tentd/model/permission.rb +28 -0
- data/lib/tentd/model/post.rb +178 -0
- data/lib/tentd/model/post_attachment.rb +27 -0
- data/lib/tentd/model/post_version.rb +64 -0
- data/lib/tentd/model/profile_info.rb +80 -0
- data/lib/tentd/model/random_public_id.rb +46 -0
- data/lib/tentd/model/serializable.rb +58 -0
- data/lib/tentd/model/type_properties.rb +36 -0
- data/lib/tentd/model/user.rb +39 -0
- data/lib/tentd/model/user_scoped.rb +14 -0
- data/lib/tentd/notifications.rb +13 -0
- data/lib/tentd/notifications/girl_friday.rb +30 -0
- data/lib/tentd/notifications/sidekiq.rb +50 -0
- data/lib/tentd/tent_type.rb +20 -0
- data/lib/tentd/tent_version.rb +41 -0
- data/lib/tentd/version.rb +3 -0
- data/spec/fabricators/app_authorizations_fabricator.rb +5 -0
- data/spec/fabricators/apps_fabricator.rb +11 -0
- data/spec/fabricators/followers_fabricator.rb +14 -0
- data/spec/fabricators/followings_fabricator.rb +17 -0
- data/spec/fabricators/groups_fabricator.rb +3 -0
- data/spec/fabricators/mentions_fabricator.rb +3 -0
- data/spec/fabricators/notification_subscriptions_fabricator.rb +4 -0
- data/spec/fabricators/permissions_fabricator.rb +1 -0
- data/spec/fabricators/post_attachments_fabricator.rb +8 -0
- data/spec/fabricators/post_versions_fabricator.rb +12 -0
- data/spec/fabricators/posts_fabricator.rb +12 -0
- data/spec/fabricators/profile_infos_fabricator.rb +30 -0
- data/spec/integration/api/apps_spec.rb +466 -0
- data/spec/integration/api/followers_spec.rb +535 -0
- data/spec/integration/api/followings_spec.rb +688 -0
- data/spec/integration/api/groups_spec.rb +207 -0
- data/spec/integration/api/posts_spec.rb +874 -0
- data/spec/integration/api/profile_spec.rb +285 -0
- data/spec/integration/api/router_spec.rb +102 -0
- data/spec/integration/model/app_authorization_spec.rb +59 -0
- data/spec/integration/model/app_spec.rb +63 -0
- data/spec/integration/model/follower_spec.rb +344 -0
- data/spec/integration/model/following_spec.rb +97 -0
- data/spec/integration/model/group_spec.rb +39 -0
- data/spec/integration/model/notification_subscription_spec.rb +145 -0
- data/spec/integration/model/post_spec.rb +658 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/support/expect_server.rb +3 -0
- data/spec/support/json_request.rb +54 -0
- data/spec/support/with_constant.rb +23 -0
- data/spec/support/with_warnings.rb +6 -0
- data/spec/unit/api/authentication_finalize_spec.rb +45 -0
- data/spec/unit/api/authentication_lookup_spec.rb +65 -0
- data/spec/unit/api/authentication_verification_spec.rb +50 -0
- data/spec/unit/api/authorizable_spec.rb +50 -0
- data/spec/unit/api/authorization_spec.rb +44 -0
- data/spec/unit/api/caching_headers_spec.rb +121 -0
- data/spec/unit/core_profile_data_spec.rb +64 -0
- data/spec/unit/json_patch_spec.rb +407 -0
- data/spec/unit/tent_type_spec.rb +28 -0
- data/spec/unit/tent_version_spec.rb +68 -0
- data/tentd.gemspec +36 -0
- metadata +435 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'dm-core/query'
|
2
|
+
|
3
|
+
module DataMapper
|
4
|
+
class Query
|
5
|
+
private
|
6
|
+
|
7
|
+
def get_relative_position(offset, limit)
|
8
|
+
self_offset = self.offset
|
9
|
+
self_limit = self.limit
|
10
|
+
new_offset = self_offset + offset
|
11
|
+
|
12
|
+
if limit < 0 || offset < 0
|
13
|
+
raise RangeError, "offset #{offset} and limit #{limit} are outside allowed range"
|
14
|
+
end
|
15
|
+
|
16
|
+
return new_offset, limit
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
module TentD
|
2
|
+
class JsonPatch
|
3
|
+
OPERATIONS = %w( add remove replace move copy test )
|
4
|
+
|
5
|
+
class HashPointer
|
6
|
+
class Error < ::StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
class InvalidPointer < Error
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(hash, pointer)
|
13
|
+
@hash, @pointer = hash, pointer
|
14
|
+
end
|
15
|
+
|
16
|
+
def value
|
17
|
+
keys.inject(@hash) do |obj, key|
|
18
|
+
if obj.kind_of?(Array)
|
19
|
+
raise InvalidPointer if key.to_i >= obj.size
|
20
|
+
obj[key.to_i]
|
21
|
+
elsif obj.kind_of?(Hash)
|
22
|
+
raise InvalidPointer unless obj.has_key?(key)
|
23
|
+
obj[key]
|
24
|
+
else
|
25
|
+
raise InvalidPointer
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def value=(value)
|
31
|
+
if exists? && value_class == Array && keys.last !~ /^\d+$/
|
32
|
+
raise InvalidPointer
|
33
|
+
end
|
34
|
+
obj = keys[0..-2].inject(@hash) do |obj, key|
|
35
|
+
obj[key] = {} unless [Hash, Array].include?(obj[key].class)
|
36
|
+
obj[key]
|
37
|
+
end
|
38
|
+
if obj.kind_of?(Array)
|
39
|
+
obj.insert(keys.last.to_i, value)
|
40
|
+
else
|
41
|
+
obj[keys.last] = value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete
|
46
|
+
obj = keys[0..-2].inject(@hash) do |obj, key|
|
47
|
+
obj[key]
|
48
|
+
end
|
49
|
+
if obj.kind_of?(Array)
|
50
|
+
raise InvalidPointer if keys.last.to_i >= obj.size
|
51
|
+
obj.delete_at(keys.last.to_i)
|
52
|
+
else
|
53
|
+
raise InvalidPointer unless obj.has_key?(keys.last)
|
54
|
+
obj.delete(keys.last)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def move_to(pointer)
|
59
|
+
_value = value
|
60
|
+
to_pointer = self.class.new(@hash, pointer)
|
61
|
+
if value_class == Array && to_pointer.value_class == Array && to_pointer.keys.last !~ /^\d+$/
|
62
|
+
raise InvalidPointer
|
63
|
+
end
|
64
|
+
delete
|
65
|
+
to_pointer.value = _value
|
66
|
+
end
|
67
|
+
|
68
|
+
def exists?
|
69
|
+
i = 0
|
70
|
+
keys.inject(@hash) do |obj, key|
|
71
|
+
# points to a key that doesn't exist
|
72
|
+
break unless obj
|
73
|
+
|
74
|
+
if obj.kind_of?(Array)
|
75
|
+
return key.to_i < obj.size
|
76
|
+
end
|
77
|
+
|
78
|
+
return true if obj.kind_of?(Hash) && i == keys.size-1 && obj.has_key?(key)
|
79
|
+
|
80
|
+
return false if obj[key].nil?
|
81
|
+
|
82
|
+
return true if ![Hash, Array].include?(obj[key].class)
|
83
|
+
|
84
|
+
i += 1
|
85
|
+
obj[key]
|
86
|
+
end
|
87
|
+
false
|
88
|
+
end
|
89
|
+
|
90
|
+
def value_class
|
91
|
+
i = 0
|
92
|
+
keys.inject(@hash) do |obj, key|
|
93
|
+
return unless obj
|
94
|
+
|
95
|
+
return Array if obj.kind_of?(Array)
|
96
|
+
return Hash if i == keys.size-1 && obj[key].kind_of?(Hash)
|
97
|
+
|
98
|
+
i += 1
|
99
|
+
obj[key]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def keys
|
104
|
+
@pointer.sub(%r{^/}, '').split("/").map do |key|
|
105
|
+
unescape_key(key)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def unescape_key(key)
|
110
|
+
key.gsub(/~1/, '/').gsub(/~0/, '~')
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class Error < ::StandardError
|
115
|
+
end
|
116
|
+
|
117
|
+
class ObjectExists < Error
|
118
|
+
end
|
119
|
+
|
120
|
+
class ObjectNotFound < Error
|
121
|
+
end
|
122
|
+
|
123
|
+
class << self
|
124
|
+
def merge(object, patch)
|
125
|
+
patch.each do |patch_object|
|
126
|
+
operation = OPERATIONS.find { |key| !patch_object[key].nil? }
|
127
|
+
send(operation, object, patch_object)
|
128
|
+
end
|
129
|
+
object
|
130
|
+
end
|
131
|
+
|
132
|
+
def add(object, patch_object)
|
133
|
+
pointer = HashPointer.new(object, patch_object["add"])
|
134
|
+
if pointer.exists?
|
135
|
+
raise ObjectExists unless pointer.value_class == Array
|
136
|
+
end
|
137
|
+
pointer.value = patch_object["value"]
|
138
|
+
object
|
139
|
+
rescue HashPointer::InvalidPointer => e
|
140
|
+
raise ObjectExists
|
141
|
+
end
|
142
|
+
|
143
|
+
def remove(object, patch_object)
|
144
|
+
pointer = HashPointer.new(object, patch_object["remove"])
|
145
|
+
pointer.delete
|
146
|
+
object
|
147
|
+
rescue HashPointer::InvalidPointer => e
|
148
|
+
raise ObjectNotFound
|
149
|
+
end
|
150
|
+
|
151
|
+
def replace(object, patch_object)
|
152
|
+
pointer = HashPointer.new(object, patch_object["replace"])
|
153
|
+
pointer.delete
|
154
|
+
pointer.value = patch_object["value"]
|
155
|
+
object
|
156
|
+
rescue HashPointer::InvalidPointer => e
|
157
|
+
raise ObjectNotFound
|
158
|
+
end
|
159
|
+
|
160
|
+
def move(object, patch_object)
|
161
|
+
pointer = HashPointer.new(object, patch_object["move"])
|
162
|
+
pointer.move_to patch_object["to"]
|
163
|
+
rescue HashPointer::InvalidPointer => e
|
164
|
+
raise ObjectNotFound
|
165
|
+
end
|
166
|
+
|
167
|
+
def copy(object, patch_object)
|
168
|
+
from_pointer = HashPointer.new(object, patch_object["copy"])
|
169
|
+
add(object, { "add" => patch_object["to"], "value" => from_pointer.value })
|
170
|
+
rescue HashPointer::InvalidPointer => e
|
171
|
+
raise ObjectNotFound
|
172
|
+
end
|
173
|
+
|
174
|
+
def test(object, patch_object)
|
175
|
+
pointer = HashPointer.new(object, patch_object["test"])
|
176
|
+
raise ObjectNotFound unless pointer.exists?
|
177
|
+
raise ObjectNotFound unless pointer.value == patch_object["value"]
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
data/lib/tentd/model.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'jdbc/postgres' if RUBY_ENGINE == 'jruby'
|
2
|
+
require 'data_mapper'
|
3
|
+
require 'dm-ar-finders'
|
4
|
+
require 'tentd/datamapper/array_property'
|
5
|
+
require 'tentd/datamapper/query'
|
6
|
+
|
7
|
+
module TentD
|
8
|
+
module Model
|
9
|
+
require 'tentd/model/permissible'
|
10
|
+
require 'tentd/model/serializable'
|
11
|
+
require 'tentd/model/random_public_id'
|
12
|
+
require 'tentd/model/type_properties'
|
13
|
+
require 'tentd/model/user_scoped'
|
14
|
+
require 'tentd/model/mention'
|
15
|
+
require 'tentd/model/post'
|
16
|
+
require 'tentd/model/post_version'
|
17
|
+
require 'tentd/model/post_attachment'
|
18
|
+
require 'tentd/model/follower'
|
19
|
+
require 'tentd/model/following'
|
20
|
+
require 'tentd/model/app'
|
21
|
+
require 'tentd/model/app_authorization'
|
22
|
+
require 'tentd/model/notification_subscription'
|
23
|
+
require 'tentd/model/profile_info'
|
24
|
+
require 'tentd/model/group'
|
25
|
+
require 'tentd/model/permission'
|
26
|
+
require 'tentd/model/user'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
DataMapper.finalize
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'tentd/core_ext/hash/slice'
|
3
|
+
|
4
|
+
module TentD
|
5
|
+
module Model
|
6
|
+
class App
|
7
|
+
include DataMapper::Resource
|
8
|
+
include RandomPublicId
|
9
|
+
include Serializable
|
10
|
+
include UserScoped
|
11
|
+
|
12
|
+
storage_names[:default] = 'apps'
|
13
|
+
|
14
|
+
property :id, Serial
|
15
|
+
property :name, Text, :required => true, :lazy => false
|
16
|
+
property :description, Text, :lazy => false
|
17
|
+
property :url, Text, :required => true, :lazy => false
|
18
|
+
property :icon, Text, :lazy => false
|
19
|
+
property :redirect_uris, Array, :lazy => false, :default => []
|
20
|
+
property :scopes, Json, :default => {}, :lazy => false
|
21
|
+
property :mac_key_id, String, :default => lambda { |*args| 'a:' + SecureRandom.hex(4) }, :unique => true
|
22
|
+
property :mac_key, String, :default => lambda { |*args| SecureRandom.hex(16) }
|
23
|
+
property :mac_algorithm, String, :default => 'hmac-sha-256'
|
24
|
+
property :mac_timestamp_delta, Integer
|
25
|
+
property :created_at, DateTime
|
26
|
+
property :updated_at, DateTime
|
27
|
+
property :deleted_at, ParanoidDateTime
|
28
|
+
|
29
|
+
has n, :authorizations, 'TentD::Model::AppAuthorization', :constraint => :destroy
|
30
|
+
has n, :posts, 'TentD::Model::Post', :constraint => :set_nil
|
31
|
+
has n, :post_versions, 'TentD::Model::PostVersion', :constraint => :set_nil
|
32
|
+
|
33
|
+
def self.create_from_params(params)
|
34
|
+
create(params.slice(:name, :description, :url, :icon, :redirect_uris, :scopes))
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.update_from_params(id, params)
|
38
|
+
app = first(:id => id)
|
39
|
+
return unless app
|
40
|
+
app.update(params.slice(:name, :description, :url, :icon, :redirect_uris, :scopes))
|
41
|
+
app
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.public_attributes
|
45
|
+
[:name, :description, :url, :icon, :scopes, :redirect_uris]
|
46
|
+
end
|
47
|
+
|
48
|
+
def auth_details
|
49
|
+
attributes.slice(:mac_key_id, :mac_key, :mac_algorithm)
|
50
|
+
end
|
51
|
+
|
52
|
+
def as_json(options = {})
|
53
|
+
attributes = super
|
54
|
+
|
55
|
+
if options[:mac]
|
56
|
+
[:mac_key, :mac_key_id, :mac_algorithm].each { |key|
|
57
|
+
attributes[key] = send(key)
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
attributes[:authorizations] = authorizations.all.map { |a| a.as_json(options.merge(:self => nil)) }
|
62
|
+
|
63
|
+
Array(options[:exclude]).each { |k| attributes.delete(k) if k }
|
64
|
+
attributes
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'tentd/core_ext/hash/slice'
|
3
|
+
|
4
|
+
module TentD
|
5
|
+
module Model
|
6
|
+
class AppAuthorization
|
7
|
+
include DataMapper::Resource
|
8
|
+
include RandomPublicId
|
9
|
+
include Serializable
|
10
|
+
|
11
|
+
storage_names[:default] = 'app_authorizations'
|
12
|
+
|
13
|
+
property :id, Serial
|
14
|
+
property :post_types, Array, :lazy => false, :default => []
|
15
|
+
property :profile_info_types, Array, :default => [], :lazy => false
|
16
|
+
property :scopes, Array, :default => [], :lazy => false
|
17
|
+
property :token_code, String, :default => lambda { |*args| SecureRandom.hex(16) }, :unique => true
|
18
|
+
property :mac_key_id, String, :default => lambda { |*args| 'u:' + SecureRandom.hex(4) }, :unique => true
|
19
|
+
property :mac_key, String, :default => lambda { |*args| SecureRandom.hex(16) }
|
20
|
+
property :mac_algorithm, String, :default => 'hmac-sha-256'
|
21
|
+
property :mac_timestamp_delta, Integer
|
22
|
+
property :notification_url, Text, :lazy => false
|
23
|
+
property :follow_url, Text, :lazy => false
|
24
|
+
property :created_at, DateTime
|
25
|
+
property :updated_at, DateTime
|
26
|
+
|
27
|
+
belongs_to :app, 'TentD::Model::App'
|
28
|
+
has n, :notification_subscriptions, 'TentD::Model::NotificationSubscription', :constraint => :destroy
|
29
|
+
|
30
|
+
before :save do
|
31
|
+
if scopes.to_a.map(&:to_s).include?('follow_ui') && follow_url
|
32
|
+
_auths = self.class.all(:follow_url.not => nil, :id.not => id)
|
33
|
+
_auths.each { |a| a.update(:scopes => a.scopes - ['follow_ui']) }
|
34
|
+
end
|
35
|
+
self.notification_url = nil if notification_url.to_s == ''
|
36
|
+
end
|
37
|
+
|
38
|
+
def auth_details
|
39
|
+
attributes.slice(:mac_key_id, :mac_key, :mac_algorithm)
|
40
|
+
end
|
41
|
+
|
42
|
+
def notification_servers
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def notification_path
|
47
|
+
notification_url
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.public_attributes
|
51
|
+
[:post_types, :profile_info_types, :scopes, :notification_url]
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.create_from_params(data)
|
55
|
+
authorization = create(data)
|
56
|
+
|
57
|
+
if data[:notification_url]
|
58
|
+
data[:post_types].each do |type|
|
59
|
+
authorization.notification_subscriptions.create(:type => type)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
authorization
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.follow_url(entity)
|
67
|
+
app_auth = all(:follow_url.not => nil).find { |a| a.scopes.map(&:to_sym).include?(:follow_ui) }
|
68
|
+
return unless app_auth
|
69
|
+
uri = URI(app_auth.follow_url)
|
70
|
+
query = "entity=#{URI.encode_www_form_component(entity)}"
|
71
|
+
uri.query ? uri.query += "&#{query}" : uri.query = query
|
72
|
+
uri.to_s
|
73
|
+
end
|
74
|
+
|
75
|
+
def update_from_params(data)
|
76
|
+
_post_types = post_types
|
77
|
+
|
78
|
+
saved = update(data.slice(:post_types, :profile_info_types, :scopes, :notification_url))
|
79
|
+
|
80
|
+
if saved && data[:post_types] && data[:post_types] != _post_types
|
81
|
+
notification_subscriptions.all(:type_base.not => post_types.map { |t| TentType.new(t).base }).destroy
|
82
|
+
|
83
|
+
data[:post_types].map { |t| TentType.new(t) }.each do |type|
|
84
|
+
next if notification_subscriptions.first(:type_base => type.base)
|
85
|
+
notification_subscriptions.create(:type_base => type.base, :type_version => type.version, :type_view => type.view)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
saved
|
90
|
+
end
|
91
|
+
|
92
|
+
def token_exchange!
|
93
|
+
update(:token_code => SecureRandom.hex(16))
|
94
|
+
{
|
95
|
+
:access_token => mac_key_id,
|
96
|
+
:mac_key => mac_key,
|
97
|
+
:mac_algorithm => mac_algorithm,
|
98
|
+
:token_type => 'mac'
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
def as_json(options = {})
|
103
|
+
attributes = super
|
104
|
+
|
105
|
+
if options[:authorization_token]
|
106
|
+
attributes[:token_code] = token_code
|
107
|
+
end
|
108
|
+
|
109
|
+
attributes
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'tentd/core_ext/hash/slice'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
module TentD
|
5
|
+
module Model
|
6
|
+
class Follower
|
7
|
+
include DataMapper::Resource
|
8
|
+
include Permissible
|
9
|
+
include RandomPublicId
|
10
|
+
include Serializable
|
11
|
+
include UserScoped
|
12
|
+
|
13
|
+
storage_names[:default] = 'followers'
|
14
|
+
|
15
|
+
property :id, Serial
|
16
|
+
property :groups, Array, :lazy => false, :default => []
|
17
|
+
property :entity, Text, :required => true, :lazy => false
|
18
|
+
property :public, Boolean, :default => true
|
19
|
+
property :profile, Json, :default => {}
|
20
|
+
property :licenses, Array, :lazy => false, :default => []
|
21
|
+
property :notification_path, Text, :lazy => false, :required => true
|
22
|
+
property :mac_key_id, String, :default => lambda { |*args| 's:' + SecureRandom.hex(4) }, :unique => true
|
23
|
+
property :mac_key, String, :default => lambda { |*args| SecureRandom.hex(16) }
|
24
|
+
property :mac_algorithm, String, :default => 'hmac-sha-256'
|
25
|
+
property :mac_timestamp_delta, Integer
|
26
|
+
property :created_at, DateTime
|
27
|
+
property :updated_at, DateTime
|
28
|
+
property :deleted_at, ParanoidDateTime
|
29
|
+
|
30
|
+
has n, :notification_subscriptions, 'TentD::Model::NotificationSubscription', :constraint => :destroy
|
31
|
+
|
32
|
+
# permissions describing who can see them
|
33
|
+
has n, :visibility_permissions, 'TentD::Model::Permission', :child_key => [ :follower_visibility_id ], :constraint => :destroy
|
34
|
+
|
35
|
+
# permissions describing what they have access to
|
36
|
+
has n, :access_permissions, 'TentD::Model::Permission', :child_key => [ :follower_access_id ], :constraint => :destroy
|
37
|
+
|
38
|
+
def self.create_follower(data, authorized_scopes = [])
|
39
|
+
if authorized_scopes.include?(:write_followers) && authorized_scopes.include?(:write_secrets)
|
40
|
+
follower = create(data.slice(:entity, :groups, :public, :profile, :licenses, :notification_path, :mac_key_id, :mac_key, :mac_algorithm, :mac_timestamp_delta))
|
41
|
+
else
|
42
|
+
follower = create(data.slice('entity', 'licenses', 'profile', 'notification_path'))
|
43
|
+
end
|
44
|
+
(data.types || ['all']).each do |type_url|
|
45
|
+
follower.notification_subscriptions.create(:type => type_url)
|
46
|
+
end
|
47
|
+
follower
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.update_follower(id, data, authorized_scopes = [])
|
51
|
+
follower = first(:id => id)
|
52
|
+
return unless follower
|
53
|
+
whitelist = ['licenses']
|
54
|
+
if authorized_scopes.include?(:write_followers)
|
55
|
+
whitelist.concat(['entity', 'profile', 'public', 'groups'])
|
56
|
+
|
57
|
+
if authorized_scopes.include?(:write_secrets)
|
58
|
+
whitelist.concat(['mac_key_id', 'mac_key', 'mac_algorithm', 'mac_timestamp_delta'])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
follower.update(data.slice(*whitelist))
|
62
|
+
if data['types']
|
63
|
+
follower.notification_subscriptions.destroy
|
64
|
+
data['types'].each do |type_url|
|
65
|
+
follower.notification_subscriptions.create(:type => type_url)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
follower
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.public_attributes
|
72
|
+
[:entity]
|
73
|
+
end
|
74
|
+
|
75
|
+
def permissible_foreign_key
|
76
|
+
:follower_access_id
|
77
|
+
end
|
78
|
+
|
79
|
+
def core_profile
|
80
|
+
API::CoreProfileData.new(profile)
|
81
|
+
end
|
82
|
+
|
83
|
+
def notification_servers
|
84
|
+
core_profile.servers
|
85
|
+
end
|
86
|
+
|
87
|
+
def auth_details
|
88
|
+
attributes.slice(:mac_key_id, :mac_key, :mac_algorithm)
|
89
|
+
end
|
90
|
+
|
91
|
+
def as_json(options = {})
|
92
|
+
attributes = super
|
93
|
+
|
94
|
+
attributes.merge!(:profile => profile) if options[:app]
|
95
|
+
|
96
|
+
if options[:app] || options[:self]
|
97
|
+
types = notification_subscriptions.all.map { |s| s.type.uri }
|
98
|
+
attributes.merge!(:licenses => licenses, :types => types)
|
99
|
+
end
|
100
|
+
|
101
|
+
attributes
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|