tentd 0.0.1
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/.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
|