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,100 @@
1
+ require 'tentd/core_ext/hash/slice'
2
+
3
+ module TentD
4
+ module Model
5
+ class Following
6
+ include DataMapper::Resource
7
+ include Permissible
8
+ include RandomPublicId
9
+ include Serializable
10
+ include UserScoped
11
+
12
+ storage_names[:default] = 'followings'
13
+
14
+ property :id, Serial
15
+ property :remote_id, String
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 :mac_key_id, String
22
+ property :mac_key, String
23
+ property :mac_algorithm, String
24
+ property :mac_timestamp_delta, Integer
25
+ property :created_at, DateTime
26
+ property :updated_at, DateTime
27
+ property :deleted_at, ParanoidDateTime
28
+ property :confirmed, Boolean, :default => true
29
+
30
+ has n, :permissions, 'TentD::Model::Permission', :constraint => :destroy
31
+
32
+ def confirm_from_params(params)
33
+ update(
34
+ :remote_id => params.id,
35
+ :profile => params.profile || {},
36
+ :mac_key_id => params.mac_key_id,
37
+ :mac_key => params.mac_key,
38
+ :mac_algorithm => params.mac_algorithm,
39
+ :confirmed => true
40
+ )
41
+ end
42
+
43
+ def self.public_attributes
44
+ [:remote_id, :entity]
45
+ end
46
+
47
+ def self.update_profile(id)
48
+ first(:id => id).update_profile
49
+ end
50
+
51
+ def update_profile
52
+ client = TentClient.new(core_profile.servers, auth_details.merge(:faraday_adapter => TentD.faraday_adapter))
53
+ res = client.profile.get
54
+ if res.status == 200
55
+ self.profile = res.body
56
+ self.licenses = core_profile.licenses
57
+ self.entity = core_profile.entity
58
+ end
59
+ save
60
+ profile
61
+ end
62
+
63
+ def core_profile
64
+ API::CoreProfileData.new(profile)
65
+ end
66
+
67
+ def notification_path
68
+ 'posts'
69
+ end
70
+
71
+ def notification_servers
72
+ core_profile.servers
73
+ end
74
+
75
+ def auth_details
76
+ attributes.slice(:mac_key_id, :mac_key, :mac_algorithm)
77
+ end
78
+
79
+ def update_from_params(params, authorized_scopes = [])
80
+ whitelist = [:remote_id, :entity, :groups, :public, :licenses, :profile]
81
+ if authorized_scopes.include?(:write_secrets)
82
+ whitelist.concat([:mac_key_id, :mac_key, :mac_algorithm, :mac_timestamp_delta])
83
+ end
84
+ attributes = params.slice(*whitelist)
85
+ update(attributes)
86
+ end
87
+
88
+ def as_json(options = {})
89
+ attributes = super
90
+
91
+ if options[:app]
92
+ attributes[:profile] = profile
93
+ attributes[:licenses] = licenses
94
+ end
95
+
96
+ attributes
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,24 @@
1
+ module TentD
2
+ module Model
3
+ class Group
4
+ include DataMapper::Resource
5
+ include RandomPublicId
6
+ include Serializable
7
+ include UserScoped
8
+
9
+ storage_names[:default] = "groups"
10
+
11
+ property :id, Serial
12
+ property :name, Text, :required => true, :lazy => false
13
+ property :created_at, DateTime
14
+ property :updated_at, DateTime
15
+ property :deleted_at, ParanoidDateTime
16
+
17
+ has n, :permissions, 'TentD::Model::Permission', :constraint => :destroy, :parent_key => :public_id
18
+
19
+ def self.public_attributes
20
+ [:name]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ module TentD
2
+ module Model
3
+ class Mention
4
+ include DataMapper::Resource
5
+
6
+ storage_names[:default] = "mentions"
7
+
8
+ property :id, Serial
9
+ property :entity, Text, :lazy => false, :required => true
10
+ property :mentioned_post_id, String
11
+
12
+ belongs_to :post, 'TentD::Model::Post', :required => false
13
+ belongs_to :post_version, 'TentD::Model::PostVersion', :required => false
14
+
15
+ validates_presence_of :post_id, :if => lambda { |m| m.post_version_id.nil? }
16
+ validates_presence_of :post_version_id, :if => lambda { |m| m.post_id.nil? }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,56 @@
1
+ module TentD
2
+ module Model
3
+ class NotificationSubscription
4
+ include DataMapper::Resource
5
+ include TypeProperties
6
+ include UserScoped
7
+
8
+ storage_names[:default] = 'notification_subscriptions'
9
+
10
+ property :id, Serial
11
+ property :created_at, DateTime
12
+ property :updated_at, DateTime
13
+
14
+ belongs_to :app_authorization, 'TentD::Model::AppAuthorization', :required => false
15
+ belongs_to :follower, 'TentD::Model::Follower', :required => false
16
+
17
+ def subject
18
+ app_authorization || follower
19
+ end
20
+
21
+ def self.notify_all(type, post_id)
22
+ post = Post.first(:id => post_id, :fields => [:id, :original, :public])
23
+ post.user.notification_subscriptions.all(:type_base => [TentType.new(type).base, 'all'],
24
+ :fields => [:id, :app_authorization_id, :follower_id]).each do |subscription|
25
+ next unless post.can_notify?(subscription.subject)
26
+ Notifications.notify(:subscription_id => subscription.id, :post_id => post_id, :view => subscription.type_view)
27
+ end
28
+ end
29
+
30
+ def self.notify_entity(entity, post_id, view='full')
31
+ post = Post.first(:id => post_id)
32
+ return if post.entity == entity
33
+ if follow = post.user.followers.first(:entity => entity) || post.user.followings.first(:entity => entity)
34
+ return unless post.can_notify?(follow)
35
+ client = TentClient.new(follow.notification_servers, follow.auth_details.merge(:faraday_adapter => TentD.faraday_adapter))
36
+ path = follow.notification_path
37
+ else
38
+ return unless post.public
39
+ profile, server_url = TentClient.new(nil, :faraday_adapter => TentD.faraday_adapter).discover(entity).get_profile
40
+ server_urls = API::CoreProfileData.new(profile).servers
41
+ client = TentClient.new(server_urls, :faraday_adapter => TentD.faraday_adapter)
42
+ path = 'posts'
43
+ end
44
+ client.post.create(post.as_json(:view => view), :url => path)
45
+ end
46
+
47
+ def notify_about(post_id, view='full')
48
+ post = Post.first(:id => post_id)
49
+ client = TentClient.new(subject.notification_servers, subject.auth_details.merge(:faraday_adapter => TentD.faraday_adapter))
50
+ permissions = subject.respond_to?(:scopes) && subject.scopes.include?(:read_permissions)
51
+ client.post.create(post.as_json(:app => !!app_authorization, :permissions => permissions, :view => view), :url => subject.notification_path)
52
+ rescue Faraday::Error::ConnectionFailed
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,227 @@
1
+ require 'hashie'
2
+
3
+ module TentD
4
+ module Model
5
+ module Permissible
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ def permissions_json(extended = false)
11
+ if extended
12
+ groups = []
13
+ entities = []
14
+ send(respond_to?(:permissions) ? :permissions : :visibility_permissions).each do |permission|
15
+ groups << permission.group.public_id if permission.group
16
+ entities << permission.follower_access.entity if permission.follower_access
17
+ end
18
+
19
+ {
20
+ :groups => groups.uniq,
21
+ :entities => Hash[entities.uniq.map { |e| [e, true] }],
22
+ :public => self.public
23
+ }
24
+ else
25
+ { :public => self.public }
26
+ end
27
+ end
28
+
29
+ def assign_permissions(permissions)
30
+ return unless permissions.kind_of?(Hash)
31
+
32
+ if permissions.groups && permissions.groups.kind_of?(Array)
33
+ permissions.groups.each do |g|
34
+ next unless g.id
35
+ group = Model::Group.first(:public_id => g.id, :fields => [:id])
36
+ self.permissions.create(:group => group) if group
37
+ end
38
+ end
39
+
40
+ if permissions.entities && permissions.entities.kind_of?(Hash)
41
+ permissions.entities.each do |entity,visible|
42
+ next unless visible
43
+ followers = Model::Follower.all(:entity => entity, :fields => [:id])
44
+ followers.each do |follower|
45
+ self.permissions.create(:follower_access => follower)
46
+ end
47
+ followings = Model::Following.all(:entity => entity, :fields => [:id])
48
+ followings.each do |following|
49
+ self.permissions.create(:following => following)
50
+ end
51
+ end
52
+ end
53
+
54
+ self.public = permissions.public unless permissions.public.nil?
55
+ end
56
+
57
+ module ClassMethods
58
+ def query_with_permissions(current_auth, params=Hashie::Mash.new)
59
+ query = []
60
+ query_bindings = []
61
+
62
+ if params.return_count
63
+ query << "SELECT COUNT(#{table_name}.*) FROM #{table_name}"
64
+ else
65
+ query << "SELECT #{table_name}.* FROM #{table_name}"
66
+ end
67
+
68
+ if current_auth && current_auth.respond_to?(:permissible_foreign_key)
69
+ query << "LEFT OUTER JOIN permissions ON permissions.#{visibility_permissions_relationship_foreign_key} = #{table_name}.id"
70
+ query << "AND (permissions.#{current_auth.permissible_foreign_key} = ?"
71
+ query_bindings << current_auth.id
72
+ if current_auth.respond_to?(:groups) && current_auth.groups.to_a.any?
73
+ query << "OR permissions.group_public_id IN ?)"
74
+ query_bindings << current_auth.groups
75
+ else
76
+ query << ")"
77
+ end
78
+ query << "WHERE (public = ? OR permissions.#{visibility_permissions_relationship_foreign_key} = #{table_name}.id)"
79
+ query_bindings << true
80
+ else
81
+ query << "WHERE public = ?"
82
+ query_bindings << true
83
+ end
84
+
85
+ query << "AND user_id = ?"
86
+ query_bindings << User.current.id
87
+
88
+ query << "AND #{table_name}.deleted_at IS NULL"
89
+
90
+ if properties[:original]
91
+ query << "AND original = ?"
92
+ query_bindings << true
93
+ end
94
+
95
+ if block_given?
96
+ yield query, query_bindings
97
+ end
98
+ end
99
+
100
+ def find_with_permissions(id, current_auth)
101
+ query_with_permissions(current_auth) do |query, query_bindings|
102
+ query << "AND #{table_name}.id = ?"
103
+ query_bindings << id
104
+
105
+ query << "LIMIT 1"
106
+
107
+ records = find_by_sql([query.join(' '), *query_bindings])
108
+ records.first
109
+ end
110
+ end
111
+
112
+ def fetch_all(params)
113
+ params = Hashie::Mash.new(params) unless params.kind_of?(Hashie::Mash)
114
+
115
+ query = []
116
+ query_conditions = []
117
+ query_bindings = []
118
+
119
+ if params.return_count
120
+ query << "SELECT COUNT(#{table_name}.*) FROM #{table_name}"
121
+ else
122
+ query << "SELECT #{table_name}.* FROM #{table_name}"
123
+ end
124
+
125
+ if params.since_id
126
+ query_conditions << "#{table_name}.id > ?"
127
+ query_bindings << params.since_id.to_i
128
+ end
129
+
130
+ if params.before_id
131
+ query_conditions << "#{table_name}.id < ?"
132
+ query_bindings << params.before_id.to_i
133
+ end
134
+
135
+ if params.entity
136
+ query_conditions << "#{table_name}.entity IN ?"
137
+ query_bindings << Array(params.entity)
138
+ end
139
+
140
+ if block_given?
141
+ yield params, query_conditions, query_bindings
142
+ end
143
+
144
+ query_conditions << "#{table_name}.user_id = ?"
145
+ query_bindings << User.current.id
146
+
147
+ query_conditions << "#{table_name}.deleted_at IS NULL"
148
+
149
+ query << "WHERE #{query_conditions.join(' AND ')}"
150
+
151
+ unless params.return_count
152
+ query << "ORDER BY id DESC" unless query.find { |q| q =~ /^order/i }
153
+
154
+ query << "LIMIT ?"
155
+ query_bindings << [(params.limit ? params.limit.to_i : TentD::API::PER_PAGE), TentD::API::MAX_PER_PAGE].min
156
+ end
157
+
158
+ if params.return_count
159
+ DataMapper.repository(:default).adapter.send(:with_connection) do |connection|
160
+ connection.create_command(query.join(' ')).execute_reader(*query_bindings).to_a.first['count']
161
+ end
162
+ else
163
+ find_by_sql([query.join(' '), *query_bindings])
164
+ end
165
+ end
166
+
167
+ def fetch_with_permissions(params, current_auth, &block)
168
+ params = Hashie::Mash.new(params) unless params.kind_of?(Hashie::Mash)
169
+
170
+ query_with_permissions(current_auth, params) do |query, query_bindings|
171
+ if params.since_id
172
+ query << "AND #{table_name}.id > ?"
173
+ query_bindings << params.since_id.to_i
174
+ end
175
+
176
+ if params.before_id
177
+ query << "AND #{table_name}.id < ?"
178
+ query_bindings << params.before_id.to_i
179
+ end
180
+
181
+ if params.entity
182
+ query << "AND #{table_name}.entity IN ?"
183
+ query_bindings << Array(params.entity)
184
+ end
185
+
186
+ if block_given?
187
+ yield params, query, query_bindings
188
+ end
189
+
190
+ unless params.return_count
191
+ query << "ORDER BY id DESC" unless query.find { |q| q =~ /^order/i }
192
+
193
+ query << "LIMIT ?"
194
+ query_bindings << [(params.limit ? params.limit.to_i : TentD::API::PER_PAGE), TentD::API::MAX_PER_PAGE].min
195
+ end
196
+
197
+ if params.return_count
198
+ DataMapper.repository(:default).adapter.send(:with_connection) do |connection|
199
+ connection.create_command(query.join(' ')).execute_reader(*query_bindings).to_a.first['count']
200
+ end
201
+ else
202
+ find_by_sql([query.join(' '), *query_bindings])
203
+ end
204
+ end
205
+ end
206
+
207
+ protected
208
+
209
+ def table_name
210
+ storage_names[repository_name]
211
+ end
212
+
213
+ def permissions_relationship_name
214
+ relationships.map(&:name).include?(:access_permissions) ? :access_permissions : :permissions
215
+ end
216
+
217
+ def permissions_relationship_foreign_key
218
+ send(permissions_relationship_name).relationships.first.child_key.first.name
219
+ end
220
+
221
+ def visibility_permissions_relationship_foreign_key
222
+ relationships.map(&:name).include?(:visibility_permissions) ? visibility_permissions.relationships.first.child_key.first.name : permissions_relationship_foreign_key
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,28 @@
1
+ module TentD
2
+ module Model
3
+ class Permission
4
+ include DataMapper::Resource
5
+
6
+ storage_names[:default] = "permissions"
7
+
8
+ belongs_to :post, 'TentD::Model::Post', :required => false
9
+ belongs_to :group, 'TentD::Model::Group', :required => false, :parent_key => :public_id
10
+ belongs_to :following, 'TentD::Model::Following', :required => false
11
+ belongs_to :follower_visibility, 'TentD::Model::Follower', :required => false
12
+ belongs_to :follower_access, 'TentD::Model::Follower', :required => false
13
+ belongs_to :profile_info, 'TentD::Model::ProfileInfo', :required => false
14
+
15
+ property :id, Serial
16
+ property :visible, Boolean, :default => true
17
+
18
+ def self.copy(from, to)
19
+ from.permissions.each do |permission|
20
+ attrs = permission.attributes
21
+ attrs.delete(:id)
22
+ to.permissions.create(attrs)
23
+ end
24
+ to.update(:public => from.public)
25
+ end
26
+ end
27
+ end
28
+ end