thredded 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 (104) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.mkdn +46 -0
  4. data/Rakefile +31 -0
  5. data/app/assets/javascripts/thredded/application.js +15 -0
  6. data/app/assets/stylesheets/thredded/application.css +13 -0
  7. data/app/controllers/thredded/application_controller.rb +50 -0
  8. data/app/controllers/thredded/messageboards_controller.rb +9 -0
  9. data/app/controllers/thredded/posts_controller.rb +86 -0
  10. data/app/controllers/thredded/preferences_controller.rb +20 -0
  11. data/app/controllers/thredded/private_topics_controller.rb +25 -0
  12. data/app/controllers/thredded/setups_controller.rb +39 -0
  13. data/app/controllers/thredded/topics_controller.rb +121 -0
  14. data/app/decorators/thredded/post_decorator.rb +52 -0
  15. data/app/decorators/thredded/topic_decorator.rb +96 -0
  16. data/app/helpers/thredded/application_helper.rb +9 -0
  17. data/app/helpers/thredded/messageboard_helper.rb +52 -0
  18. data/app/helpers/thredded/posts_helper.rb +7 -0
  19. data/app/helpers/thredded/topics_helper.rb +21 -0
  20. data/app/mailers/thredded/post_mailer.rb +28 -0
  21. data/app/mailers/thredded/topic_mailer.rb +29 -0
  22. data/app/models/thredded/ability.rb +50 -0
  23. data/app/models/thredded/attachment.rb +24 -0
  24. data/app/models/thredded/category.rb +10 -0
  25. data/app/models/thredded/image.rb +27 -0
  26. data/app/models/thredded/messageboard.rb +92 -0
  27. data/app/models/thredded/messageboard_decorator.rb +18 -0
  28. data/app/models/thredded/messageboard_preference.rb +15 -0
  29. data/app/models/thredded/null_preference.rb +9 -0
  30. data/app/models/thredded/null_topic_read.rb +13 -0
  31. data/app/models/thredded/null_user.rb +27 -0
  32. data/app/models/thredded/post.rb +75 -0
  33. data/app/models/thredded/post_notification.rb +6 -0
  34. data/app/models/thredded/private_topic.rb +33 -0
  35. data/app/models/thredded/private_user.rb +7 -0
  36. data/app/models/thredded/role.rb +18 -0
  37. data/app/models/thredded/topic.rb +145 -0
  38. data/app/models/thredded/topic_category.rb +7 -0
  39. data/app/models/thredded/user_detail.rb +6 -0
  40. data/app/models/thredded/user_extender.rb +27 -0
  41. data/app/models/thredded/user_preference.rb +6 -0
  42. data/app/models/thredded/user_topic_read.rb +67 -0
  43. data/app/uploaders/thredded/attachment_uploader.rb +30 -0
  44. data/app/uploaders/thredded/image_uploader.rb +17 -0
  45. data/app/views/thredded/messageboards/_messageboard.html.erb +17 -0
  46. data/app/views/thredded/messageboards/index.html.erb +15 -0
  47. data/app/views/thredded/post_mailer/at_notification.html.erb +17 -0
  48. data/app/views/thredded/post_mailer/at_notification.text.erb +13 -0
  49. data/app/views/thredded/posts/_form.html.erb +25 -0
  50. data/app/views/thredded/posts/_null_user.html.erb +0 -0
  51. data/app/views/thredded/posts/_post.html.erb +25 -0
  52. data/app/views/thredded/posts/_user.html.erb +7 -0
  53. data/app/views/thredded/posts/edit.html.erb +28 -0
  54. data/app/views/thredded/posts/index.html.erb +74 -0
  55. data/app/views/thredded/preferences/edit.html.erb +23 -0
  56. data/app/views/thredded/search/_form.html.erb +9 -0
  57. data/app/views/thredded/setups/_form.html.erb +35 -0
  58. data/app/views/thredded/setups/new.html.erb +5 -0
  59. data/app/views/thredded/shared/_currently_online.html.erb +10 -0
  60. data/app/views/thredded/shared/_error_msgs.html.erb +7 -0
  61. data/app/views/thredded/shared/_google_analytics.html.erb +10 -0
  62. data/app/views/thredded/shared/_topic_nav.html.erb +18 -0
  63. data/app/views/thredded/topic_mailer/message_notification.html.erb +17 -0
  64. data/app/views/thredded/topic_mailer/message_notification.text.erb +13 -0
  65. data/app/views/thredded/topics/_form.html.erb +6 -0
  66. data/app/views/thredded/topics/_topic_condensed.html.erb +20 -0
  67. data/app/views/thredded/topics/_topic_form.html.erb +27 -0
  68. data/app/views/thredded/topics/by_category.html.erb +51 -0
  69. data/app/views/thredded/topics/edit.html.erb +25 -0
  70. data/app/views/thredded/topics/index.html.erb +52 -0
  71. data/app/views/thredded/topics/menu/_new_topic.html.erb +3 -0
  72. data/app/views/thredded/topics/new.html.erb +18 -0
  73. data/app/views/thredded/topics/search.html.erb +45 -0
  74. data/config/routes.rb +26 -0
  75. data/db/migrate/20130425230852_create_thredded_tables.rb +143 -0
  76. data/lib/tasks/thredded_tasks.rake +4 -0
  77. data/lib/thredded.rb +38 -0
  78. data/lib/thredded/at_notification_extractor.rb +15 -0
  79. data/lib/thredded/at_notifier.rb +65 -0
  80. data/lib/thredded/at_users.rb +17 -0
  81. data/lib/thredded/email_processor.rb +74 -0
  82. data/lib/thredded/engine.rb +17 -0
  83. data/lib/thredded/filter.rb +4 -0
  84. data/lib/thredded/filter/at_notification.rb +11 -0
  85. data/lib/thredded/filter/attachment.rb +51 -0
  86. data/lib/thredded/filter/base.rb +13 -0
  87. data/lib/thredded/filter/bbcode.rb +75 -0
  88. data/lib/thredded/filter/emoji.rb +11 -0
  89. data/lib/thredded/filter/markdown.rb +25 -0
  90. data/lib/thredded/filter/syntax.rb +28 -0
  91. data/lib/thredded/filter/textile.rb +21 -0
  92. data/lib/thredded/messageboard_user_permissions.rb +27 -0
  93. data/lib/thredded/post_sql_builder.rb +34 -0
  94. data/lib/thredded/post_user_permissions.rb +37 -0
  95. data/lib/thredded/private_topic_notifier.rb +56 -0
  96. data/lib/thredded/private_topic_user_permissions.rb +32 -0
  97. data/lib/thredded/search_parser.rb +45 -0
  98. data/lib/thredded/search_sql_builder.rb +25 -0
  99. data/lib/thredded/setup_thredded.rb +7 -0
  100. data/lib/thredded/table_sql_builder.rb +105 -0
  101. data/lib/thredded/topic_sql_builder.rb +32 -0
  102. data/lib/thredded/topic_user_permissions.rb +51 -0
  103. data/lib/thredded/version.rb +3 -0
  104. metadata +537 -0
@@ -0,0 +1,27 @@
1
+ module Thredded
2
+ class MessageboardUserPermissions
3
+ attr_reader :messageboard, :user
4
+
5
+ def initialize(messageboard, user)
6
+ @messageboard = messageboard
7
+ @user = user
8
+ end
9
+
10
+ def readable?
11
+ (messageboard.restricted_to_private? && messageboard.has_member?(user)) ||
12
+ (messageboard.restricted_to_logged_in? && user.valid?) ||
13
+ messageboard.public?
14
+ end
15
+
16
+ def postable?
17
+ if messageboard.posting_for_anonymous? &&
18
+ (messageboard.restricted_to_private? || messageboard.restricted_to_logged_in?)
19
+ false
20
+ else
21
+ messageboard.posting_for_anonymous? ||
22
+ (messageboard.posting_for_logged_in? && user.try(:valid?)) ||
23
+ (messageboard.posting_for_members? && messageboard.has_member?(user))
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,34 @@
1
+ module Thredded
2
+ class PostSqlBuilder < TableSqlBuilder
3
+ def build_text_search
4
+ if text.present?
5
+ search_text = text
6
+ add_from('posts p')
7
+ add_where('t.id = p.topic_id')
8
+ add_where("to_tsvector('english', p.content) @@ plainto_tsquery('english', ?)", search_text.uniq.join(' '))
9
+
10
+ search_text.each do |term|
11
+ if (is_quoted(term))
12
+ add_where('p.content ILIKE ?', term.gsub('"', '%'))
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ def build_in_category
19
+ if categories.present?
20
+ add_from('topic_categories tc')
21
+ add_where('tc.topic_id = t.id')
22
+ add_where('tc.category_id in (?)', categories)
23
+ end
24
+ end
25
+
26
+ def build_by_user
27
+ if users.present?
28
+ add_from 'posts p'
29
+ add_where('t.id = p.topic_id')
30
+ add_where('p.user_id in (?)', users)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,37 @@
1
+ module Thredded
2
+ class PostUserPermissions
3
+ attr_reader :post, :user, :user_details, :messageboard, :topic
4
+
5
+ def initialize(post, user, user_details)
6
+ @post = post
7
+ @topic = post.topic
8
+ @messageboard = post.messageboard
9
+ @user = user
10
+ @user_details = user_details || UserDetail.new
11
+ end
12
+
13
+ def manageable?
14
+ created_post?
15
+ end
16
+
17
+ def creatable?
18
+ thread_is_not_locked? && can_create_topic?
19
+ end
20
+
21
+ private
22
+
23
+ def created_post?
24
+ user.id == post.user_id
25
+ end
26
+
27
+ def thread_is_not_locked?
28
+ !@topic.locked?
29
+ end
30
+
31
+ def can_create_topic?
32
+ TopicUserPermissions.new(@topic, @user, @user_details).creatable?
33
+ end
34
+ end
35
+ end
36
+
37
+
@@ -0,0 +1,56 @@
1
+ module Thredded
2
+ class PrivateTopicNotifier
3
+ def initialize(topic)
4
+ @post = topic.posts.first || Post.new
5
+ @topic = topic
6
+ end
7
+
8
+ def notifications_for_private_topic
9
+ members = private_topic_recipients
10
+
11
+ if members.present?
12
+ user_emails = members.map(&:email)
13
+ TopicMailer.message_notification(topic.id, user_emails).deliver
14
+ mark_notified(members)
15
+ end
16
+ end
17
+
18
+ def private_topic_recipients
19
+ members = topic.users - [topic.user]
20
+ members = exclude_those_opting_out_of_message_notifications(members)
21
+ members = exclude_previously_notified(members)
22
+ members
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :post, :topic
28
+
29
+ def mark_notified(members)
30
+ members.each do |member|
31
+ post.post_notifications.create(email: member.email)
32
+ end
33
+ end
34
+
35
+ def exclude_those_opting_out_of_message_notifications(members)
36
+ members.reject do |member|
37
+ !Thredded::MessageboardPreference
38
+ .for(member)
39
+ .in(topic.messageboard)
40
+ .first
41
+ .try(:notify_on_message?)
42
+ end
43
+ end
44
+
45
+ def notify_for_member_in_messageboard?(member, messageboard)
46
+ end
47
+
48
+ def exclude_previously_notified(members)
49
+ emails_notified = post.post_notifications.map(&:email)
50
+
51
+ members.reject do |member|
52
+ emails_notified.include? member.email
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,32 @@
1
+ module Thredded
2
+ class PrivateTopicUserPermissions
3
+ attr_reader :private_topic, :user, :user_details, :messageboard
4
+
5
+ def initialize(private_topic, user, user_details)
6
+ @private_topic = private_topic
7
+ @messageboard = private_topic.messageboard
8
+ @user = user
9
+ @user_details = user_details || UserDetail.new
10
+ end
11
+
12
+ def manageable?
13
+ user_started_topic?
14
+ end
15
+
16
+ def readable?
17
+ private_topic.users.include?(user)
18
+ end
19
+
20
+ def creatable?
21
+ TopicUserPermissions
22
+ .new(private_topic, user, user_details).creatable?
23
+ end
24
+
25
+ private
26
+
27
+ def user_started_topic?
28
+ user.id == private_topic.user_id
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,45 @@
1
+ module Thredded
2
+ class SearchParser
3
+ def initialize(query)
4
+ @query = query
5
+ @keywords = ['in', 'by', 'order']
6
+ end
7
+
8
+ def parse
9
+ parsed_input = parse_keywords
10
+ parsed_input['text'] = parse_text
11
+ parsed_input
12
+ end
13
+
14
+ def parse_keywords
15
+ found_terms_hash = {}
16
+
17
+ @keywords.each do |keyword|
18
+ regex = Regexp.new(keyword+'\s*:\s*\w+', Regexp::IGNORECASE)
19
+ keyword_scan = @query.scan(regex)
20
+ @query = @query.gsub(regex, '')
21
+
22
+ if keyword_scan.present?
23
+ keyword_scan.each do |term|
24
+ keyword_term = term.gsub(' ', '').split(':')
25
+
26
+ if found_terms_hash[keyword].nil?
27
+ found_terms_hash[keyword] = []
28
+ end
29
+
30
+ found_terms_hash[keyword] << keyword_term[1]
31
+ end
32
+ end
33
+ end
34
+
35
+ found_terms_hash
36
+ end
37
+
38
+ def parse_text
39
+ regex = Regexp.new('\"[^"]*\"')
40
+ found_terms = @query.scan(regex)
41
+ @query = @query.sub(regex,'')
42
+ found_terms.concat(@query.split(/\s+/))
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,25 @@
1
+ require 'thredded/topic_sql_builder'
2
+ require 'thredded/post_sql_builder'
3
+
4
+ module Thredded
5
+ class SearchSqlBuilder
6
+ def initialize(query, messageboard)
7
+ @topic_builder = TopicSqlBuilder.new(query, messageboard)
8
+ @post_builder = PostSqlBuilder.new(query, messageboard)
9
+ @order_by = 'ORDER BY updated_at DESC'
10
+ end
11
+
12
+ def build
13
+ [
14
+ 'SELECT * FROM topics WHERE id IN (', @post_builder.sql,
15
+ 'UNION', @topic_builder.sql,
16
+ ')', @order_by,
17
+ 'LIMIT 50'
18
+ ].join(' ')
19
+ end
20
+
21
+ def binds
22
+ @post_builder.binds.concat(@topic_builder.binds)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,7 @@
1
+ module Thredded
2
+ class SetupThredded
3
+ def matches?(request)
4
+ return Thredded::Messageboard.all.empty?
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,105 @@
1
+ require 'thredded/search_parser'
2
+
3
+ module Thredded
4
+ class TableSqlBuilder
5
+ attr_accessor :binds
6
+
7
+ def initialize(query, messageboard)
8
+ @terms = SearchParser.new(query).parse
9
+
10
+ @select = 'SELECT t.id'
11
+ @from = 'FROM topics t'
12
+ @where = ['t.messageboard_id = ?']
13
+ @binds = [messageboard.id]
14
+
15
+ @search_categories = []
16
+ @search_users = []
17
+ @search_text = []
18
+ end
19
+
20
+ def sql
21
+ build_by_user
22
+ build_in_category
23
+ build_text_search
24
+
25
+ [@select,
26
+ @from,
27
+ 'WHERE', @where.join(' AND '),
28
+ ].join(' ')
29
+ end
30
+
31
+ private
32
+
33
+ def is_quoted(term)
34
+ term.count('"') == 2
35
+ end
36
+
37
+ def categories
38
+ if @search_categories.any?
39
+ @search_categories
40
+ else
41
+ if @terms['in']
42
+ @terms['in'].each do |category_name|
43
+ category = Category
44
+ .where('lower(name) = ?', category_name.downcase).first
45
+ if category
46
+ @search_categories << category.id
47
+ end
48
+ end
49
+ end
50
+
51
+ @search_categories
52
+ end
53
+ end
54
+
55
+ def users
56
+ if @search_users.any?
57
+ @search_users
58
+ else
59
+
60
+ if @terms['by']
61
+ @terms['by'].each do |username|
62
+ user = User.where('lower(name) = ?', username.downcase).first
63
+
64
+ if user
65
+ @search_users << user.id
66
+ end
67
+ end
68
+ end
69
+
70
+ @search_users
71
+ end
72
+ end
73
+
74
+ def text
75
+ @terms['text']
76
+ end
77
+
78
+ def build_by_user
79
+ raise 'SubclassResponsibility'
80
+ end
81
+
82
+ def build_in_category
83
+ raise 'SubclassResponsibility'
84
+ end
85
+
86
+ def build_text_search
87
+ raise 'SubclassResponsibility'
88
+ end
89
+
90
+ def add_from(table)
91
+ if @from.exclude? table
92
+ @from = "#{@from}, #{table}"
93
+ end
94
+ end
95
+
96
+ def add_where(where, binds=nil)
97
+ if @where.exclude? where
98
+ @where << where
99
+ if (binds.present?)
100
+ @binds.push(binds)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,32 @@
1
+ require 'thredded/table_sql_builder'
2
+
3
+ module Thredded
4
+ class TopicSqlBuilder < TableSqlBuilder
5
+ def build_text_search
6
+ if text.present?
7
+ search_text = text
8
+ add_where("to_tsvector('english', t.title) @@ plainto_tsquery('english', ?)", search_text.uniq.join(' '))
9
+
10
+ search_text.each do |term|
11
+ if (is_quoted(term))
12
+ add_where('t.title ILIKE ?', term.gsub('"', '%'))
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ def build_in_category
19
+ if categories.present?
20
+ add_from('topic_categories tc')
21
+ add_where('tc.topic_id = t.id')
22
+ add_where('tc.category_id in (?)', categories)
23
+ end
24
+ end
25
+
26
+ def build_by_user
27
+ if users.present?
28
+ add_where('t.user_id in (?)', users)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,51 @@
1
+ module Thredded
2
+ class TopicUserPermissions
3
+ attr_reader :topic, :user, :user_details, :messageboard
4
+
5
+ def initialize(topic, user, user_details)
6
+ @topic = topic
7
+ @messageboard = topic.messageboard
8
+ @user = user
9
+ @user_details = user_details || UserDetail.new
10
+ end
11
+
12
+ def creatable?
13
+ member? || messageboard_restrictions_allow?
14
+ end
15
+
16
+ def manageable?
17
+ superadmin? || started_by_user? || administrates_messageboard?
18
+ end
19
+
20
+ def readable?
21
+ MessageboardUserPermissions.new(messageboard, user).readable?
22
+ end
23
+
24
+ private
25
+
26
+ def messageboard_restrictions_allow?
27
+ user.valid? &&
28
+ (
29
+ messageboard.public? ||
30
+ messageboard.restricted_to_logged_in? ||
31
+ messageboard.posting_for_logged_in?
32
+ )
33
+ end
34
+
35
+ def member?
36
+ user.valid? && messageboard.has_member?(user)
37
+ end
38
+
39
+ def superadmin?
40
+ user_details.superadmin?
41
+ end
42
+
43
+ def started_by_user?
44
+ topic.user_id == user.id
45
+ end
46
+
47
+ def administrates_messageboard?
48
+ user.valid? && messageboard.member_is_a?(user, 'admin')
49
+ end
50
+ end
51
+ end