libertree-model 0.9.11

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 (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