tentd 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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