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.
Files changed (101) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +9 -0
  5. data/Guardfile +6 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +49 -0
  8. data/Rakefile +8 -0
  9. data/bin/tent-server +3 -0
  10. data/lib/tentd.rb +31 -0
  11. data/lib/tentd/api.rb +58 -0
  12. data/lib/tentd/api/apps.rb +196 -0
  13. data/lib/tentd/api/authentication_finalize.rb +12 -0
  14. data/lib/tentd/api/authentication_lookup.rb +27 -0
  15. data/lib/tentd/api/authentication_verification.rb +50 -0
  16. data/lib/tentd/api/authorizable.rb +21 -0
  17. data/lib/tentd/api/authorization.rb +14 -0
  18. data/lib/tentd/api/core_profile_data.rb +45 -0
  19. data/lib/tentd/api/followers.rb +218 -0
  20. data/lib/tentd/api/followings.rb +241 -0
  21. data/lib/tentd/api/groups.rb +161 -0
  22. data/lib/tentd/api/middleware.rb +32 -0
  23. data/lib/tentd/api/posts.rb +373 -0
  24. data/lib/tentd/api/profile.rb +78 -0
  25. data/lib/tentd/api/router.rb +123 -0
  26. data/lib/tentd/api/router/caching_headers.rb +49 -0
  27. data/lib/tentd/api/router/extract_params.rb +88 -0
  28. data/lib/tentd/api/router/serialize_response.rb +38 -0
  29. data/lib/tentd/api/user_lookup.rb +10 -0
  30. data/lib/tentd/core_ext/hash/slice.rb +29 -0
  31. data/lib/tentd/datamapper/array_property.rb +23 -0
  32. data/lib/tentd/datamapper/query.rb +19 -0
  33. data/lib/tentd/json_patch.rb +181 -0
  34. data/lib/tentd/model.rb +30 -0
  35. data/lib/tentd/model/app.rb +68 -0
  36. data/lib/tentd/model/app_authorization.rb +113 -0
  37. data/lib/tentd/model/follower.rb +105 -0
  38. data/lib/tentd/model/following.rb +100 -0
  39. data/lib/tentd/model/group.rb +24 -0
  40. data/lib/tentd/model/mention.rb +19 -0
  41. data/lib/tentd/model/notification_subscription.rb +56 -0
  42. data/lib/tentd/model/permissible.rb +227 -0
  43. data/lib/tentd/model/permission.rb +28 -0
  44. data/lib/tentd/model/post.rb +178 -0
  45. data/lib/tentd/model/post_attachment.rb +27 -0
  46. data/lib/tentd/model/post_version.rb +64 -0
  47. data/lib/tentd/model/profile_info.rb +80 -0
  48. data/lib/tentd/model/random_public_id.rb +46 -0
  49. data/lib/tentd/model/serializable.rb +58 -0
  50. data/lib/tentd/model/type_properties.rb +36 -0
  51. data/lib/tentd/model/user.rb +39 -0
  52. data/lib/tentd/model/user_scoped.rb +14 -0
  53. data/lib/tentd/notifications.rb +13 -0
  54. data/lib/tentd/notifications/girl_friday.rb +30 -0
  55. data/lib/tentd/notifications/sidekiq.rb +50 -0
  56. data/lib/tentd/tent_type.rb +20 -0
  57. data/lib/tentd/tent_version.rb +41 -0
  58. data/lib/tentd/version.rb +3 -0
  59. data/spec/fabricators/app_authorizations_fabricator.rb +5 -0
  60. data/spec/fabricators/apps_fabricator.rb +11 -0
  61. data/spec/fabricators/followers_fabricator.rb +14 -0
  62. data/spec/fabricators/followings_fabricator.rb +17 -0
  63. data/spec/fabricators/groups_fabricator.rb +3 -0
  64. data/spec/fabricators/mentions_fabricator.rb +3 -0
  65. data/spec/fabricators/notification_subscriptions_fabricator.rb +4 -0
  66. data/spec/fabricators/permissions_fabricator.rb +1 -0
  67. data/spec/fabricators/post_attachments_fabricator.rb +8 -0
  68. data/spec/fabricators/post_versions_fabricator.rb +12 -0
  69. data/spec/fabricators/posts_fabricator.rb +12 -0
  70. data/spec/fabricators/profile_infos_fabricator.rb +30 -0
  71. data/spec/integration/api/apps_spec.rb +466 -0
  72. data/spec/integration/api/followers_spec.rb +535 -0
  73. data/spec/integration/api/followings_spec.rb +688 -0
  74. data/spec/integration/api/groups_spec.rb +207 -0
  75. data/spec/integration/api/posts_spec.rb +874 -0
  76. data/spec/integration/api/profile_spec.rb +285 -0
  77. data/spec/integration/api/router_spec.rb +102 -0
  78. data/spec/integration/model/app_authorization_spec.rb +59 -0
  79. data/spec/integration/model/app_spec.rb +63 -0
  80. data/spec/integration/model/follower_spec.rb +344 -0
  81. data/spec/integration/model/following_spec.rb +97 -0
  82. data/spec/integration/model/group_spec.rb +39 -0
  83. data/spec/integration/model/notification_subscription_spec.rb +145 -0
  84. data/spec/integration/model/post_spec.rb +658 -0
  85. data/spec/spec_helper.rb +37 -0
  86. data/spec/support/expect_server.rb +3 -0
  87. data/spec/support/json_request.rb +54 -0
  88. data/spec/support/with_constant.rb +23 -0
  89. data/spec/support/with_warnings.rb +6 -0
  90. data/spec/unit/api/authentication_finalize_spec.rb +45 -0
  91. data/spec/unit/api/authentication_lookup_spec.rb +65 -0
  92. data/spec/unit/api/authentication_verification_spec.rb +50 -0
  93. data/spec/unit/api/authorizable_spec.rb +50 -0
  94. data/spec/unit/api/authorization_spec.rb +44 -0
  95. data/spec/unit/api/caching_headers_spec.rb +121 -0
  96. data/spec/unit/core_profile_data_spec.rb +64 -0
  97. data/spec/unit/json_patch_spec.rb +407 -0
  98. data/spec/unit/tent_type_spec.rb +28 -0
  99. data/spec/unit/tent_version_spec.rb +68 -0
  100. data/tentd.gemspec +36 -0
  101. 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
@@ -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