libertree-model 0.9.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/lib/libertree/compat.rb +29 -0
  3. data/lib/libertree/console.rb +17 -0
  4. data/lib/libertree/db.rb +37 -0
  5. data/lib/libertree/embedder.rb +69 -0
  6. data/lib/libertree/embedding/custom-providers.rb +148 -0
  7. data/lib/libertree/model/account-settings.rb +14 -0
  8. data/lib/libertree/model/account.rb +487 -0
  9. data/lib/libertree/model/chat-message.rb +75 -0
  10. data/lib/libertree/model/comment-like.rb +68 -0
  11. data/lib/libertree/model/comment.rb +164 -0
  12. data/lib/libertree/model/contact-list.rb +81 -0
  13. data/lib/libertree/model/embed-cache.rb +6 -0
  14. data/lib/libertree/model/file.rb +6 -0
  15. data/lib/libertree/model/forest.rb +82 -0
  16. data/lib/libertree/model/group-member.rb +13 -0
  17. data/lib/libertree/model/group.rb +47 -0
  18. data/lib/libertree/model/has-display-text.rb +34 -0
  19. data/lib/libertree/model/has-searchable-text.rb +19 -0
  20. data/lib/libertree/model/ignored-member.rb +13 -0
  21. data/lib/libertree/model/invitation.rb +6 -0
  22. data/lib/libertree/model/is-remote-or-local.rb +30 -0
  23. data/lib/libertree/model/job.rb +93 -0
  24. data/lib/libertree/model/member.rb +222 -0
  25. data/lib/libertree/model/message.rb +154 -0
  26. data/lib/libertree/model/node.rb +22 -0
  27. data/lib/libertree/model/node_affiliation.rb +14 -0
  28. data/lib/libertree/model/node_subscription.rb +23 -0
  29. data/lib/libertree/model/notification.rb +71 -0
  30. data/lib/libertree/model/pool-post.rb +17 -0
  31. data/lib/libertree/model/pool.rb +172 -0
  32. data/lib/libertree/model/post-hidden.rb +21 -0
  33. data/lib/libertree/model/post-like.rb +72 -0
  34. data/lib/libertree/model/post-revision.rb +9 -0
  35. data/lib/libertree/model/post.rb +735 -0
  36. data/lib/libertree/model/profile.rb +25 -0
  37. data/lib/libertree/model/remote-storage-connection.rb +6 -0
  38. data/lib/libertree/model/river.rb +249 -0
  39. data/lib/libertree/model/server.rb +35 -0
  40. data/lib/libertree/model/session-account.rb +10 -0
  41. data/lib/libertree/model/url-expansion.rb +6 -0
  42. data/lib/libertree/model.rb +48 -0
  43. data/lib/libertree/query.rb +167 -0
  44. data/lib/libertree/render.rb +28 -0
  45. metadata +198 -0
@@ -0,0 +1,164 @@
1
+ require_relative '../embedder'
2
+
3
+ module Libertree
4
+ module Model
5
+ class Comment < Sequel::Model(:comments)
6
+ include IsRemoteOrLocal
7
+ extend HasSearchableText
8
+ include HasDisplayText
9
+
10
+ def after_create
11
+ super
12
+
13
+ if self.local? && self.post.distribute?
14
+ Libertree::Model::Job.create_for_forests(
15
+ {
16
+ task: 'request:COMMENT',
17
+ params: { 'comment_id' => self.id, }
18
+ },
19
+ *self.forests
20
+ )
21
+ end
22
+ Libertree::Embedder.autoembed(self.text)
23
+ end
24
+
25
+ # TODO: DB: association
26
+ def member
27
+ @member = Member[self.member_id]
28
+ end
29
+
30
+ # TODO: DB: association
31
+ def post
32
+ @post = Post[self.post_id]
33
+ end
34
+
35
+ def before_destroy
36
+ if self.post
37
+ remaining_comments = self.post.comments - [self]
38
+ self.post.time_commented = remaining_comments.map(&:time_created).max
39
+ end
40
+
41
+ if self.local? && self.post.distribute?
42
+ Libertree::Model::Job.create_for_forests(
43
+ {
44
+ task: 'request:COMMENT-DELETE',
45
+ params: { 'comment_id' => self.id, }
46
+ },
47
+ *self.forests
48
+ )
49
+ end
50
+ super
51
+ end
52
+
53
+ # TODO: the correct method to call is "destroy"
54
+ def delete
55
+ self.before_destroy
56
+ super
57
+ end
58
+
59
+ # NOTE: deletion is NOT distributed when force=true
60
+ def delete_cascade(force=false)
61
+ self.before_destroy unless force
62
+ DB.dbh[ "SELECT delete_cascade_comment(?)", self.id ].get
63
+ end
64
+
65
+ def self.create(*args)
66
+ comment = super
67
+ account = comment.member.account
68
+ post = comment.post
69
+
70
+ post.time_commented = comment.time_created
71
+ post.mark_as_unread_by_all except: [account]
72
+ if account
73
+ post.mark_as_read_by account
74
+ account.subscribe_to comment.post
75
+ end
76
+ post.notify_about_comment comment
77
+ post.save
78
+
79
+ comment
80
+ end
81
+
82
+ def likes
83
+ @likes ||= CommentLike.where(comment_id: self.id).reverse_order(:id)
84
+ end
85
+
86
+ def notify_about_like(like)
87
+ notification_attributes = {
88
+ 'type' => 'comment-like',
89
+ 'comment_like_id' => like.id,
90
+ }
91
+ local_comment_author = like.comment.member.account
92
+ like_author = like.member.account
93
+
94
+ if(
95
+ local_comment_author &&
96
+ (!like_author || local_comment_author.id != like_author.id) &&
97
+ ! local_comment_author.ignoring?(like.member)
98
+ )
99
+ local_comment_author.notify_about notification_attributes
100
+ end
101
+ end
102
+
103
+ def like_by(member)
104
+ # take advantage of cached self.likes
105
+ if self.likes.is_a? Array
106
+ self.likes.find {|like| like.member.id == member.id}
107
+ else
108
+ CommentLike[ member_id: member.id, comment_id: self.id ]
109
+ end
110
+ end
111
+
112
+ # overriding method from IsRemoteOrLocal
113
+ def forests
114
+ if self.post.remote?
115
+ self.post.server.forests
116
+ else
117
+ Libertree::Model::Forest.all_local_is_member
118
+ end
119
+ end
120
+
121
+ def to_hash
122
+ {
123
+ 'id' => self.id,
124
+ 'time_created' => self.time_created,
125
+ 'time_updated' => self.time_updated,
126
+ 'text' => self.text,
127
+ 'post_id' => self.post_id,
128
+ }
129
+ end
130
+
131
+ # TODO: When more visibilities come, restrict this result set by visibility
132
+ def self.comments_since_id(comment_id)
133
+ self.where{ id > comment_id }.order(:id)
134
+ end
135
+
136
+ # @param [Hash] opt options for restricting the comment set returned
137
+ # @option opts [Fixnum] :from_id Only return comments with id greater than or equal to this id
138
+ # @option opts [Fixnum] :to_id Only return comments with id less than this id
139
+ # @option opts [Account] :viewing_account An account to use to hide comments by ignored members
140
+ def self.on_post(post, opt = {})
141
+ res = Comment.where(post_id: post.id)
142
+ if opt[:viewing_account]
143
+ # Array() because sometimes opt[:viewing_account] is a strange nil-like object for some reason.
144
+ # Ramaze weirdness?
145
+ res = res.exclude(member_id: Array(opt[:viewing_account].ignored_members).map(&:id))
146
+ end
147
+
148
+ # reverse ordering is required in order to get the *last* n
149
+ # comments, rather than the first few when using :limit
150
+ res = res.reverse_order(:id)
151
+ res = res.where{ id >= opt[:from_id].to_i } if opt[:from_id]
152
+ res = res.where{ id < opt[:to_id].to_i } if opt[:to_id]
153
+ res = res.limit(opt[:limit].to_i) if opt[:limit]
154
+ res.all.reverse
155
+ end
156
+
157
+ def guid
158
+ server = self.member.server
159
+ origin = server ? server.domain : Server.own_domain
160
+ "xmpp:#{origin}?;node=/comments;item=#{self.public_id}"
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,81 @@
1
+ module Libertree
2
+ module Model
3
+ class ContactList < Sequel::Model(:contact_lists)
4
+ def account
5
+ @account ||= Account[self.account_id]
6
+ end
7
+
8
+ def members
9
+ return @members if @members
10
+
11
+ @members = Member.s(
12
+ %{
13
+ SELECT
14
+ m.*
15
+ FROM
16
+ contact_lists_members clm
17
+ , members m
18
+ WHERE
19
+ clm.contact_list_id = ?
20
+ AND m.id = clm.member_id
21
+ },
22
+ self.id
23
+ )
24
+ end
25
+
26
+ def member_ids
27
+ Libertree::DB.dbh[:contact_lists_members].
28
+ select(:member_id).
29
+ where(:contact_list_id => self.id).
30
+ all.
31
+ flat_map(&:values)
32
+ end
33
+
34
+ def members=(arg)
35
+ DB.dbh.transaction do
36
+ DB.dbh[ "DELETE FROM contact_lists_members WHERE contact_list_id = ?", self.id ].get
37
+ Array(arg).each do |member_id_s|
38
+ DB.dbh[ "INSERT INTO contact_lists_members ( contact_list_id, member_id ) VALUES ( ?, ? )", self.id, member_id_s.to_i ].get
39
+ end
40
+ end
41
+ end
42
+
43
+ def delete_cascade
44
+ DB.dbh[ "SELECT delete_cascade_contact_list(?)", self.id ].get
45
+ end
46
+
47
+ def <<(member)
48
+ # refuse to add anything that's not a Member
49
+ return unless member.is_a? Member
50
+
51
+ Libertree::DB.dbh.transaction do
52
+ unless self.member_ids.include?(member.id)
53
+ Libertree::DB.dbh[:contact_lists_members].
54
+ insert(contact_list_id: self.id, member_id: member.id)
55
+ end
56
+ end
57
+ end
58
+
59
+ # refresh any river containing a reference to this contact list
60
+ def refresh_rivers
61
+ rivers = account.rivers.select do |r|
62
+ vals = r.parsed_query['contact-list'].values.flatten(1)
63
+ ! vals.empty? && vals.map(&:first).include?(self.id)
64
+ end
65
+
66
+ # refresh rivers in background jobs
67
+ rivers.each do |river|
68
+ if ! river.appended_to_all
69
+ Libertree::Model::Job.create(
70
+ task: 'river:refresh',
71
+ params: {
72
+ 'river_id' => river.id,
73
+ }.to_json
74
+ )
75
+ end
76
+ end
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,6 @@
1
+ module Libertree
2
+ module Model
3
+ class EmbedCache < Sequel::Model(:embed_cache)
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Libertree
2
+ module Model
3
+ class File < Sequel::Model(:files)
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,82 @@
1
+ module Libertree
2
+ module Model
3
+ class Forest < Sequel::Model(:forests)
4
+ def trees
5
+ Server.s(
6
+ %{
7
+ SELECT
8
+ s.*
9
+ FROM
10
+ forests_servers fs
11
+ , servers s
12
+ WHERE
13
+ fs.forest_id = ?
14
+ AND s.id = fs.server_id
15
+ },
16
+ self.id
17
+ )
18
+ end
19
+ alias :servers :trees
20
+
21
+ def add(server)
22
+ DB.dbh[
23
+ %{
24
+ INSERT INTO forests_servers (
25
+ forest_id, server_id
26
+ ) SELECT
27
+ ?, ?
28
+ WHERE NOT EXISTS(
29
+ SELECT 1
30
+ FROM forests_servers fs2
31
+ WHERE
32
+ fs2.forest_id = ?
33
+ AND fs2.server_id = ?
34
+ )
35
+ },
36
+ self.id,
37
+ server.id,
38
+ self.id,
39
+ server.id
40
+ ].get
41
+ end
42
+
43
+ def remove(server)
44
+ DB.dbh[ "DELETE FROM forests_servers WHERE forest_id = ? AND server_id = ?", self.id, server.id ].get
45
+ end
46
+
47
+ def local?
48
+ ! origin_server_id
49
+ end
50
+ def self.all_local_is_member
51
+ where local_is_member: true
52
+ end
53
+
54
+ def origin
55
+ Server[origin_server_id]
56
+ end
57
+
58
+ def local_is_member?
59
+ local_is_member
60
+ end
61
+
62
+ # @param [Array(String)] domains
63
+ # @return [Array(Model::Server)] any new Server records that were created
64
+ def set_trees_by_domain(domains)
65
+ DB.dbh[ "DELETE FROM forests_servers WHERE forest_id = ?", self.id ].get
66
+ new_trees = []
67
+
68
+ domains.each do |domain|
69
+ tree = Model::Server[domain: domain]
70
+ if tree.nil?
71
+ tree = Model::Server.create(domain: domain)
72
+ new_trees << tree
73
+ end
74
+
75
+ self.add tree
76
+ end
77
+
78
+ new_trees
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,13 @@
1
+ module Libertree
2
+ module Model
3
+ class GroupMember < Sequel::Model(:groups_members)
4
+ def group
5
+ Libertree::Model::Group[self.group_id]
6
+ end
7
+
8
+ def member
9
+ Libertree::Model::Member[self.member_id]
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,47 @@
1
+ module Libertree
2
+ module Model
3
+ class Group < Sequel::Model(:groups)
4
+ def after_create
5
+ super
6
+ # Add creator of the group to the group
7
+ Libertree::Model::GroupMember.create(group_id: self.id, member_id: self.admin_member_id)
8
+ end
9
+
10
+ def add_member(member)
11
+ Libertree::Model::GroupMember.create(group_id: self.id, member_id: member.id)
12
+ end
13
+
14
+ def remove_member(member)
15
+ self.group_member(member).delete
16
+ end
17
+
18
+ def member?(member)
19
+ self.group_member(member).any?
20
+ end
21
+
22
+ def members
23
+ Libertree::Model::GroupMember.where(group_id: self.id).map { |gm| gm.member }
24
+ end
25
+
26
+ def posts( opts = {} )
27
+ time = Time.at(
28
+ opts.fetch(:time, Time.now.to_f)
29
+ ).strftime("%Y-%m-%d %H:%M:%S.%6N%z")
30
+
31
+ Post.s(
32
+ "SELECT * FROM posts_in_group(?,?,?,?,?,?)",
33
+ self.id,
34
+ opts.fetch(:viewer_account_id),
35
+ time,
36
+ opts[:newer],
37
+ opts[:order_by] == :comment,
38
+ opts.fetch(:limit, 30)
39
+ )
40
+ end
41
+
42
+ def group_member(member)
43
+ Libertree::Model::GroupMember.where(group_id: self.id, member_id: member.id)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,34 @@
1
+ require 'libertree/render'
2
+
3
+ module Libertree
4
+ module Model
5
+ # Provides a "glimpse" instance method to objects that have a "text" field.
6
+ module HasDisplayText
7
+ def glimpse( length = 60 )
8
+ set_without_blockquotes = text_to_nodeset
9
+ set_without_blockquotes.xpath('.//blockquote').each(&:remove)
10
+ plain_text = set_without_blockquotes.inner_text.strip
11
+
12
+ if plain_text.empty?
13
+ plain_text = text_to_nodeset.inner_text.strip
14
+ end
15
+
16
+ plain_text = plain_text.gsub("\n", ' ')
17
+ snippet = plain_text[0...length]
18
+ if plain_text.length > length
19
+ snippet += '...'
20
+ end
21
+
22
+ snippet
23
+ end
24
+
25
+ def text_as_html
26
+ Render.to_html_nodeset(self.text)
27
+ end
28
+
29
+ def text_to_nodeset
30
+ Render.to_html_nodeset(self.text, [:no_images, :filter_html])
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ module Libertree
2
+ module Model
3
+ # Provides a "search" class method to a class that has a "text" field
4
+ # and a time_created field.
5
+ module HasSearchableText
6
+ def search(q, limit = 42, exact=true)
7
+ if exact
8
+ dict = 'simple'
9
+ else
10
+ dict = 'english'
11
+ end
12
+ self.where("(to_tsvector('simple', text) || to_tsvector('english', text)) @@ plainto_tsquery('#{dict}', ?)", q).
13
+ reverse_order(:time_created).
14
+ limit(limit.to_i).
15
+ all
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ module Libertree
2
+ module Model
3
+ class IgnoredMember < Sequel::Model(:ignored_members)
4
+ def account
5
+ Libertree::Model::Account[self.account_id]
6
+ end
7
+
8
+ def member
9
+ Libertree::Model::Member[self.member_id]
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ module Libertree
2
+ module Model
3
+ class Invitation < Sequel::Model(:invitations)
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,30 @@
1
+ module Libertree
2
+ module Model
3
+ module IsRemoteOrLocal
4
+ def public_id
5
+ self.remote_id || self.id
6
+ end
7
+
8
+ def remote?
9
+ !! remote_id
10
+ end
11
+
12
+ def local?
13
+ ! remote_id
14
+ end
15
+
16
+ def server
17
+ @server ||= self.member.server
18
+ end
19
+
20
+ def forests
21
+ if self.remote?
22
+ self.server.forests
23
+ else
24
+ Libertree::Model::Forest.all_local_is_member
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,93 @@
1
+ require 'json'
2
+ require 'set'
3
+
4
+ module Libertree
5
+ module Model
6
+ class Job < Sequel::Model(:jobs)
7
+ MAX_TRIES = 48
8
+ RETRY_FACTOR = 0.2
9
+
10
+ def params
11
+ if val = super
12
+ JSON.parse val
13
+ end
14
+ end
15
+
16
+ def retry!
17
+ self.pid = self.time_started = self.time_finished = nil
18
+ self.time_to_start = Time.now
19
+ self.tries = 0
20
+ self.save
21
+ end
22
+
23
+ # First parameter can be a Forest Array.
24
+ # Otherwise, assumed to create for all member forests.
25
+ def self.create_for_forests(create_args, *forests)
26
+ if forests.empty?
27
+ forests = Forest.all_local_is_member
28
+ end
29
+
30
+ trees = Set.new
31
+ forests.each do |f|
32
+ if f.local_is_member?
33
+ trees += f.trees
34
+ end
35
+ end
36
+ trees.each do |tree|
37
+ params = ( create_args[:params] || create_args['params'] || Hash.new )
38
+ params['server_id'] = tree.id
39
+ Libertree::Model::Job.create(
40
+ task: create_args[:task],
41
+ params: params.to_json
42
+ )
43
+ end
44
+ end
45
+
46
+ # @return [Job] nil if no job was reserved
47
+ def self.reserve(tasks)
48
+ job = self.where("task IN ? AND pid IS NULL AND tries < #{MAX_TRIES} AND time_to_start <= NOW()", tasks).order(:time_to_start).limit(1).first
49
+ return nil if job.nil?
50
+
51
+ self.where({ id: job.id, pid: nil }).
52
+ update({ pid: Process.pid, time_started: Time.now })
53
+
54
+ job = Job[job.id]
55
+ if job.pid == Process.pid
56
+ job
57
+ end
58
+ end
59
+
60
+ def unreserve
61
+ new_tries = self.tries+1
62
+ self.update(
63
+ time_started: nil,
64
+ pid: nil,
65
+ tries: new_tries,
66
+ time_to_start: Time.now + 60 * Math::E**(new_tries * RETRY_FACTOR)
67
+ )
68
+ end
69
+
70
+ def self.pending_where(*args)
71
+ query = args[0]
72
+ params = args[1..-1]
73
+
74
+ self.where(
75
+ query + %{
76
+ AND time_finished IS NULL
77
+ AND tries < ?
78
+ },
79
+ *params,
80
+ MAX_TRIES
81
+ )
82
+ end
83
+
84
+ def self.unfinished(task=nil)
85
+ if task
86
+ self.where("task = ? AND time_finished IS NULL", task).all
87
+ else
88
+ self.where("time_finished IS NULL").all
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end