rails_den 2.0.0

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 (528) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +652 -0
  3. data/README.md +74 -0
  4. data/Rakefile +6 -0
  5. data/app/assets/javascript/rails_den/attachments.js +733 -0
  6. data/app/assets/javascript/rails_den/bookmarks.js +58 -0
  7. data/app/assets/javascript/rails_den/cable.js +18 -0
  8. data/app/assets/javascript/rails_den/chat.js +3008 -0
  9. data/app/assets/javascript/rails_den/chat_3d_management.js +1230 -0
  10. data/app/assets/javascript/rails_den/composer.js +1785 -0
  11. data/app/assets/javascript/rails_den/content.js +1337 -0
  12. data/app/assets/javascript/rails_den/emoji_catalog.js +136 -0
  13. data/app/assets/javascript/rails_den/licenses/react-LICENSE.txt +21 -0
  14. data/app/assets/javascript/rails_den/licenses/react-dom-LICENSE.txt +21 -0
  15. data/app/assets/javascript/rails_den/licenses/scheduler-LICENSE.txt +21 -0
  16. data/app/assets/javascript/rails_den/licenses/three-LICENSE.txt +21 -0
  17. data/app/assets/javascript/rails_den/members.js +187 -0
  18. data/app/assets/javascript/rails_den/notifications.js +211 -0
  19. data/app/assets/javascript/rails_den/presence.js +757 -0
  20. data/app/assets/javascript/rails_den/private_messages.js +92 -0
  21. data/app/assets/javascript/rails_den/react_runtime.js +43 -0
  22. data/app/assets/javascript/rails_den/reactions.js +139 -0
  23. data/app/assets/javascript/rails_den/spatial.js +4338 -0
  24. data/app/assets/stylesheets/rails_den/advertising.css +91 -0
  25. data/app/assets/stylesheets/rails_den/application.css +1724 -0
  26. data/app/assets/stylesheets/rails_den/attachments.css +255 -0
  27. data/app/assets/stylesheets/rails_den/board_presence.css +38 -0
  28. data/app/assets/stylesheets/rails_den/bookmarks.css +36 -0
  29. data/app/assets/stylesheets/rails_den/chat.css +1555 -0
  30. data/app/assets/stylesheets/rails_den/chat_3d.css +1283 -0
  31. data/app/assets/stylesheets/rails_den/chat_3d_management.css +294 -0
  32. data/app/assets/stylesheets/rails_den/chat_reactions.css +79 -0
  33. data/app/assets/stylesheets/rails_den/community_presence.css +51 -0
  34. data/app/assets/stylesheets/rails_den/composer.css +358 -0
  35. data/app/assets/stylesheets/rails_den/emoji_catalog.css +33 -0
  36. data/app/assets/stylesheets/rails_den/member_profiles.css +662 -0
  37. data/app/assets/stylesheets/rails_den/moderation.css +561 -0
  38. data/app/assets/stylesheets/rails_den/notifications.css +222 -0
  39. data/app/assets/stylesheets/rails_den/onebox.css +126 -0
  40. data/app/assets/stylesheets/rails_den/post_links.css +37 -0
  41. data/app/assets/stylesheets/rails_den/post_presence.css +47 -0
  42. data/app/assets/stylesheets/rails_den/presence.css +96 -0
  43. data/app/assets/stylesheets/rails_den/presence_directory.css +217 -0
  44. data/app/assets/stylesheets/rails_den/presence_live.css +15 -0
  45. data/app/assets/stylesheets/rails_den/private_messages.css +41 -0
  46. data/app/assets/stylesheets/rails_den/reactions.css +53 -0
  47. data/app/assets/stylesheets/rails_den/search.css +100 -0
  48. data/app/assets/stylesheets/rails_den/tags.css +136 -0
  49. data/app/channels/rails_den/chat_spatial_channel.rb +203 -0
  50. data/app/channels/rails_den/chat_spatial_occupancy_channel.rb +69 -0
  51. data/app/channels/rails_den/chat_stream_channel.rb +108 -0
  52. data/app/channels/rails_den/notifications_channel.rb +30 -0
  53. data/app/channels/rails_den/presence_channel.rb +205 -0
  54. data/app/controllers/admin/rails_den/administrators_controller.rb +26 -0
  55. data/app/controllers/admin/rails_den/advertising_management_controller.rb +337 -0
  56. data/app/controllers/admin/rails_den/application_controller.rb +116 -0
  57. data/app/controllers/admin/rails_den/badges_controller.rb +106 -0
  58. data/app/controllers/admin/rails_den/board_access_controller.rb +143 -0
  59. data/app/controllers/admin/rails_den/boards_controller.rb +89 -0
  60. data/app/controllers/admin/rails_den/categories_controller.rb +89 -0
  61. data/app/controllers/admin/rails_den/member_badge_awards_controller.rb +92 -0
  62. data/app/controllers/admin/rails_den/members_controller.rb +495 -0
  63. data/app/controllers/admin/rails_den/plugins_controller.rb +25 -0
  64. data/app/controllers/admin/rails_den/profile_fields_controller.rb +104 -0
  65. data/app/controllers/admin/rails_den/reaction_types_controller.rb +127 -0
  66. data/app/controllers/admin/rails_den/report_reasons_controller.rb +15 -0
  67. data/app/controllers/admin/rails_den/roles_controller.rb +190 -0
  68. data/app/controllers/admin/rails_den/tags_controller.rb +11 -0
  69. data/app/controllers/admin/rails_den/themes_controller.rb +60 -0
  70. data/app/controllers/admin/rails_den/trust_levels_controller.rb +97 -0
  71. data/app/controllers/rails_den/advertising_controller.rb +14 -0
  72. data/app/controllers/rails_den/application_controller.rb +218 -0
  73. data/app/controllers/rails_den/attachments_controller.rb +128 -0
  74. data/app/controllers/rails_den/board_follows_controller.rb +119 -0
  75. data/app/controllers/rails_den/board_read_states_controller.rb +36 -0
  76. data/app/controllers/rails_den/boards_controller.rb +103 -0
  77. data/app/controllers/rails_den/bookmarks_controller.rb +91 -0
  78. data/app/controllers/rails_den/chat_channel_memberships_controller.rb +193 -0
  79. data/app/controllers/rails_den/chat_channel_read_states_controller.rb +46 -0
  80. data/app/controllers/rails_den/chat_channels_controller.rb +556 -0
  81. data/app/controllers/rails_den/chat_message_pins_controller.rb +78 -0
  82. data/app/controllers/rails_den/chat_messages_controller.rb +166 -0
  83. data/app/controllers/rails_den/chat_reaction_types_controller.rb +24 -0
  84. data/app/controllers/rails_den/community_read_states_controller.rb +12 -0
  85. data/app/controllers/rails_den/home_controller.rb +35 -0
  86. data/app/controllers/rails_den/member_profiles_controller.rb +286 -0
  87. data/app/controllers/rails_den/member_reports_controller.rb +22 -0
  88. data/app/controllers/rails_den/members_controller.rb +42 -0
  89. data/app/controllers/rails_den/mention_candidates_controller.rb +14 -0
  90. data/app/controllers/rails_den/moderation/audit_events_controller.rb +14 -0
  91. data/app/controllers/rails_den/moderation/member_histories_controller.rb +37 -0
  92. data/app/controllers/rails_den/moderation/member_notes_controller.rb +26 -0
  93. data/app/controllers/rails_den/moderation/report_notes_controller.rb +27 -0
  94. data/app/controllers/rails_den/moderation/reports_controller.rb +97 -0
  95. data/app/controllers/rails_den/moderation/restrictions_controller.rb +71 -0
  96. data/app/controllers/rails_den/moderation/topics_controller.rb +61 -0
  97. data/app/controllers/rails_den/moderation/warnings_controller.rb +50 -0
  98. data/app/controllers/rails_den/notification_preferences_controller.rb +116 -0
  99. data/app/controllers/rails_den/notifications_controller.rb +67 -0
  100. data/app/controllers/rails_den/post_bookmarks_controller.rb +29 -0
  101. data/app/controllers/rails_den/post_drafts_controller.rb +125 -0
  102. data/app/controllers/rails_den/post_reports_controller.rb +31 -0
  103. data/app/controllers/rails_den/posts_controller.rb +318 -0
  104. data/app/controllers/rails_den/presence_controller.rb +12 -0
  105. data/app/controllers/rails_den/presence_snapshots_controller.rb +83 -0
  106. data/app/controllers/rails_den/previews_controller.rb +10 -0
  107. data/app/controllers/rails_den/private_conversations_controller.rb +120 -0
  108. data/app/controllers/rails_den/private_messages_controller.rb +45 -0
  109. data/app/controllers/rails_den/reactions_controller.rb +124 -0
  110. data/app/controllers/rails_den/reports_controller.rb +63 -0
  111. data/app/controllers/rails_den/search_controller.rb +64 -0
  112. data/app/controllers/rails_den/tags_controller.rb +70 -0
  113. data/app/controllers/rails_den/topic_bookmarks_controller.rb +35 -0
  114. data/app/controllers/rails_den/topic_follows_controller.rb +117 -0
  115. data/app/controllers/rails_den/topic_read_positions_controller.rb +40 -0
  116. data/app/controllers/rails_den/topic_reports_controller.rb +18 -0
  117. data/app/controllers/rails_den/topics_controller.rb +583 -0
  118. data/app/dashboards/rails_den/administrator_dashboard.rb +51 -0
  119. data/app/dashboards/rails_den/board_dashboard.rb +57 -0
  120. data/app/dashboards/rails_den/category_dashboard.rb +50 -0
  121. data/app/dashboards/rails_den/profile_field_dashboard.rb +53 -0
  122. data/app/dashboards/rails_den/reaction_type_dashboard.rb +26 -0
  123. data/app/dashboards/rails_den/report_reason_dashboard.rb +57 -0
  124. data/app/dashboards/rails_den/tag_dashboard.rb +59 -0
  125. data/app/helpers/rails_den/application_helper.rb +39 -0
  126. data/app/helpers/rails_den/boards_helper.rb +4 -0
  127. data/app/helpers/rails_den/moderation/reports_helper.rb +42 -0
  128. data/app/helpers/rails_den/notifications_helper.rb +4 -0
  129. data/app/helpers/rails_den/posts_helper.rb +4 -0
  130. data/app/helpers/rails_den/topics_helper.rb +4 -0
  131. data/app/jobs/rails_den/application_job.rb +4 -0
  132. data/app/jobs/rails_den/expire_user_restriction_job.rb +9 -0
  133. data/app/jobs/rails_den/member_profile_stats_refresh_job.rb +48 -0
  134. data/app/jobs/rails_den/reconcile_user_restrictions_job.rb +9 -0
  135. data/app/jobs/rails_den/resolve_onebox_entry_job.rb +74 -0
  136. data/app/mailers/rails_den/application_mailer.rb +6 -0
  137. data/app/models/rails_den/administrator.rb +18 -0
  138. data/app/models/rails_den/advertising_configuration.rb +14 -0
  139. data/app/models/rails_den/application_record.rb +5 -0
  140. data/app/models/rails_den/badge.rb +142 -0
  141. data/app/models/rails_den/badge_award.rb +87 -0
  142. data/app/models/rails_den/board.rb +129 -0
  143. data/app/models/rails_den/board_follow.rb +18 -0
  144. data/app/models/rails_den/board_read_state.rb +60 -0
  145. data/app/models/rails_den/board_role_assignment.rb +45 -0
  146. data/app/models/rails_den/board_role_permission.rb +31 -0
  147. data/app/models/rails_den/board_role_requirement.rb +16 -0
  148. data/app/models/rails_den/board_sponsorship.rb +21 -0
  149. data/app/models/rails_den/bookmark.rb +31 -0
  150. data/app/models/rails_den/category.rb +81 -0
  151. data/app/models/rails_den/category_role_requirement.rb +16 -0
  152. data/app/models/rails_den/category_sponsorship.rb +21 -0
  153. data/app/models/rails_den/chat_channel.rb +44 -0
  154. data/app/models/rails_den/chat_channel_membership.rb +21 -0
  155. data/app/models/rails_den/chat_channel_read_state.rb +100 -0
  156. data/app/models/rails_den/chat_message.rb +37 -0
  157. data/app/models/rails_den/community_read_state.rb +52 -0
  158. data/app/models/rails_den/community_sponsorship.rb +15 -0
  159. data/app/models/rails_den/member_profile.rb +113 -0
  160. data/app/models/rails_den/mention.rb +20 -0
  161. data/app/models/rails_den/moderation_event.rb +16 -0
  162. data/app/models/rails_den/moderation_note.rb +11 -0
  163. data/app/models/rails_den/notification.rb +33 -0
  164. data/app/models/rails_den/notification_event.rb +40 -0
  165. data/app/models/rails_den/notification_mute.rb +9 -0
  166. data/app/models/rails_den/onebox_entry.rb +214 -0
  167. data/app/models/rails_den/post.rb +396 -0
  168. data/app/models/rails_den/post_draft.rb +63 -0
  169. data/app/models/rails_den/post_revision.rb +10 -0
  170. data/app/models/rails_den/private_conversation.rb +25 -0
  171. data/app/models/rails_den/private_conversation_participant.rb +16 -0
  172. data/app/models/rails_den/private_message.rb +35 -0
  173. data/app/models/rails_den/private_message_recipient.rb +44 -0
  174. data/app/models/rails_den/profile_field.rb +150 -0
  175. data/app/models/rails_den/profile_field_value.rb +136 -0
  176. data/app/models/rails_den/reaction.rb +56 -0
  177. data/app/models/rails_den/reaction_type.rb +23 -0
  178. data/app/models/rails_den/reader_visit.rb +61 -0
  179. data/app/models/rails_den/report.rb +127 -0
  180. data/app/models/rails_den/report_reason.rb +52 -0
  181. data/app/models/rails_den/reputation_event.rb +92 -0
  182. data/app/models/rails_den/role.rb +87 -0
  183. data/app/models/rails_den/role_assignment.rb +18 -0
  184. data/app/models/rails_den/role_permission.rb +41 -0
  185. data/app/models/rails_den/sluggable.rb +102 -0
  186. data/app/models/rails_den/sponsor.rb +20 -0
  187. data/app/models/rails_den/sponsorship.rb +87 -0
  188. data/app/models/rails_den/tag.rb +61 -0
  189. data/app/models/rails_den/theme_selection.rb +4 -0
  190. data/app/models/rails_den/topic.rb +307 -0
  191. data/app/models/rails_den/topic_follow.rb +18 -0
  192. data/app/models/rails_den/topic_read_state.rb +76 -0
  193. data/app/models/rails_den/topic_role_requirement.rb +16 -0
  194. data/app/models/rails_den/topic_tag.rb +16 -0
  195. data/app/models/rails_den/trust_level.rb +62 -0
  196. data/app/models/rails_den/trust_level_achievement.rb +21 -0
  197. data/app/models/rails_den/user_restriction.rb +54 -0
  198. data/app/models/rails_den/user_warning.rb +10 -0
  199. data/app/presenters/rails_den/member_profile_presenter.rb +285 -0
  200. data/app/queries/rails_den/topic_new_post_counts.rb +153 -0
  201. data/app/queries/rails_den/topic_unread_counts.rb +137 -0
  202. data/app/services/rails_den/board_following.rb +91 -0
  203. data/app/services/rails_den/bookmark_list.rb +109 -0
  204. data/app/services/rails_den/bookmark_state.rb +17 -0
  205. data/app/services/rails_den/built_in_role_provisioner.rb +98 -0
  206. data/app/services/rails_den/chat/broadcaster.rb +81 -0
  207. data/app/services/rails_den/chat/message_payload.rb +26 -0
  208. data/app/services/rails_den/chat/presence_snapshot.rb +269 -0
  209. data/app/services/rails_den/chat/spatial_broadcaster.rb +135 -0
  210. data/app/services/rails_den/chat/spatial_occupancy_snapshot.rb +35 -0
  211. data/app/services/rails_den/chat/spatial_registry.rb +682 -0
  212. data/app/services/rails_den/chat/spatial_room.rb +81 -0
  213. data/app/services/rails_den/chat/spatial_snapshot.rb +261 -0
  214. data/app/services/rails_den/chat/spatial_update.rb +243 -0
  215. data/app/services/rails_den/chat/subscription_token.rb +28 -0
  216. data/app/services/rails_den/chat/unread_counts.rb +79 -0
  217. data/app/services/rails_den/chat/whisper_delivery.rb +287 -0
  218. data/app/services/rails_den/chat_channel_access.rb +97 -0
  219. data/app/services/rails_den/default_report_reason_provisioner.rb +30 -0
  220. data/app/services/rails_den/emoji_catalog.rb +101 -0
  221. data/app/services/rails_den/gamification/activity_recorder.rb +69 -0
  222. data/app/services/rails_den/gamification/badge_evaluator.rb +55 -0
  223. data/app/services/rails_den/gamification/current_trust_level.rb +21 -0
  224. data/app/services/rails_den/gamification/metrics.rb +93 -0
  225. data/app/services/rails_den/gamification/reputation_ledger.rb +261 -0
  226. data/app/services/rails_den/gamification/trust_level_evaluator.rb +78 -0
  227. data/app/services/rails_den/global_moderation_policy.rb +32 -0
  228. data/app/services/rails_den/member_profile_bio_renderer.rb +28 -0
  229. data/app/services/rails_den/mentions/autocomplete.rb +69 -0
  230. data/app/services/rails_den/mentions/candidate.rb +19 -0
  231. data/app/services/rails_den/mentions/parser.rb +185 -0
  232. data/app/services/rails_den/mentions/reconciler.rb +46 -0
  233. data/app/services/rails_den/mentions/renderer.rb +165 -0
  234. data/app/services/rails_den/moderation_access.rb +42 -0
  235. data/app/services/rails_den/moderation_audit.rb +38 -0
  236. data/app/services/rails_den/moderation_event_scope.rb +12 -0
  237. data/app/services/rails_den/moderation_notification_delivery.rb +23 -0
  238. data/app/services/rails_den/notification_events/emitter.rb +226 -0
  239. data/app/services/rails_den/notification_preferences.rb +169 -0
  240. data/app/services/rails_den/notifications/broadcaster.rb +72 -0
  241. data/app/services/rails_den/notifications/materializer.rb +284 -0
  242. data/app/services/rails_den/notifications/subscription_token.rb +28 -0
  243. data/app/services/rails_den/permission_resolver.rb +252 -0
  244. data/app/services/rails_den/presence/board_summary.rb +131 -0
  245. data/app/services/rails_den/presence/broadcaster.rb +193 -0
  246. data/app/services/rails_den/presence/chat_room_registry.rb +263 -0
  247. data/app/services/rails_den/presence/community_summary.rb +166 -0
  248. data/app/services/rails_den/presence/directory.rb +499 -0
  249. data/app/services/rails_den/presence/last_seen.rb +23 -0
  250. data/app/services/rails_den/presence/page_map.rb +202 -0
  251. data/app/services/rails_den/presence/registry.rb +805 -0
  252. data/app/services/rails_den/presence/subscription_token.rb +28 -0
  253. data/app/services/rails_den/presence/visibility.rb +95 -0
  254. data/app/services/rails_den/private_messages/mailbox.rb +108 -0
  255. data/app/services/rails_den/private_messages/read_marker.rb +25 -0
  256. data/app/services/rails_den/private_messages/sender.rb +113 -0
  257. data/app/services/rails_den/private_messages/unread_count.rb +22 -0
  258. data/app/services/rails_den/reaction_summary.rb +54 -0
  259. data/app/services/rails_den/reaction_type_importer.rb +91 -0
  260. data/app/services/rails_den/reaction_type_search.rb +34 -0
  261. data/app/services/rails_den/readable_community_content.rb +156 -0
  262. data/app/services/rails_den/report_assignment.rb +31 -0
  263. data/app/services/rails_den/report_creator.rb +96 -0
  264. data/app/services/rails_den/report_delivery.rb +39 -0
  265. data/app/services/rails_den/report_disposition.rb +25 -0
  266. data/app/services/rails_den/report_recipients.rb +89 -0
  267. data/app/services/rails_den/report_review_policy.rb +66 -0
  268. data/app/services/rails_den/report_review_scope.rb +49 -0
  269. data/app/services/rails_den/restriction_resolver.rb +28 -0
  270. data/app/services/rails_den/role_resolver.rb +66 -0
  271. data/app/services/rails_den/search/adapters/basic.rb +35 -0
  272. data/app/services/rails_den/search/adapters/postgresql.rb +73 -0
  273. data/app/services/rails_den/search/adapters.rb +21 -0
  274. data/app/services/rails_den/search/query.rb +358 -0
  275. data/app/services/rails_den/topic_following.rb +91 -0
  276. data/app/services/rails_den/topic_merger.rb +27 -0
  277. data/app/services/rails_den/topic_moderation.rb +57 -0
  278. data/app/services/rails_den/topic_splitter.rb +36 -0
  279. data/app/services/rails_den/user_restriction_manager.rb +84 -0
  280. data/app/services/rails_den/user_warning_issuer.rb +19 -0
  281. data/app/views/admin/rails_den/advertising_management/_assignment_form.html.erb +340 -0
  282. data/app/views/admin/rails_den/advertising_management/_errors.html.erb +10 -0
  283. data/app/views/admin/rails_den/advertising_management/show.html.erb +324 -0
  284. data/app/views/admin/rails_den/badges/_form.html.erb +191 -0
  285. data/app/views/admin/rails_den/badges/edit.html.erb +14 -0
  286. data/app/views/admin/rails_den/badges/index.html.erb +98 -0
  287. data/app/views/admin/rails_den/badges/new.html.erb +14 -0
  288. data/app/views/admin/rails_den/board_access/show.html.erb +51 -0
  289. data/app/views/admin/rails_den/boards/_form.html.erb +5 -0
  290. data/app/views/admin/rails_den/categories/_form.html.erb +4 -0
  291. data/app/views/admin/rails_den/members/index.html.erb +121 -0
  292. data/app/views/admin/rails_den/members/show.html.erb +398 -0
  293. data/app/views/admin/rails_den/plugins/index.html.erb +49 -0
  294. data/app/views/admin/rails_den/profile_fields/_form.html.erb +169 -0
  295. data/app/views/admin/rails_den/reaction_types/_catalog_search.html.erb +22 -0
  296. data/app/views/admin/rails_den/reaction_types/_errors.html.erb +3 -0
  297. data/app/views/admin/rails_den/reaction_types/index.html.erb +66 -0
  298. data/app/views/admin/rails_den/reaction_types/new.html.erb +23 -0
  299. data/app/views/admin/rails_den/reaction_types/selected.html.erb +15 -0
  300. data/app/views/admin/rails_den/roles/_form.html.erb +64 -0
  301. data/app/views/admin/rails_den/roles/edit.html.erb +18 -0
  302. data/app/views/admin/rails_den/roles/index.html.erb +49 -0
  303. data/app/views/admin/rails_den/roles/new.html.erb +6 -0
  304. data/app/views/admin/rails_den/shared/_access_form.html.erb +59 -0
  305. data/app/views/admin/rails_den/themes/index.html.erb +129 -0
  306. data/app/views/admin/rails_den/trust_levels/_form.html.erb +80 -0
  307. data/app/views/admin/rails_den/trust_levels/edit.html.erb +14 -0
  308. data/app/views/admin/rails_den/trust_levels/index.html.erb +86 -0
  309. data/app/views/admin/rails_den/trust_levels/new.html.erb +14 -0
  310. data/app/views/administrate/application/_index_header.html.erb +68 -0
  311. data/app/views/administrate/application/new.html.erb +42 -0
  312. data/app/views/administrate/application/show.html.erb +89 -0
  313. data/app/views/layouts/administrate/application/_navigation.html.erb +61 -0
  314. data/app/views/layouts/administrate/application.html.erb +40 -0
  315. data/app/views/layouts/rails_den/_navbar.html.erb +293 -0
  316. data/app/views/layouts/rails_den/application.html.erb +198 -0
  317. data/app/views/rails_den/advertising/show.html.erb +13 -0
  318. data/app/views/rails_den/boards/_follow_button.html.erb +45 -0
  319. data/app/views/rails_den/boards/show.html.erb +150 -0
  320. data/app/views/rails_den/bookmarks/_post_control.html.erb +12 -0
  321. data/app/views/rails_den/bookmarks/_topic_control.html.erb +12 -0
  322. data/app/views/rails_den/bookmarks/index.html.erb +78 -0
  323. data/app/views/rails_den/chat_channels/_form.html.erb +174 -0
  324. data/app/views/rails_den/chat_channels/edit.html.erb +51 -0
  325. data/app/views/rails_den/chat_channels/index.html.erb +402 -0
  326. data/app/views/rails_den/chat_channels/new.html.erb +31 -0
  327. data/app/views/rails_den/chat_channels/show.html.erb +476 -0
  328. data/app/views/rails_den/home/index.html.erb +142 -0
  329. data/app/views/rails_den/member_profiles/_avatar.html.erb +18 -0
  330. data/app/views/rails_den/member_profiles/_custom_fields.html.erb +39 -0
  331. data/app/views/rails_den/member_profiles/_followed_boards.html.erb +15 -0
  332. data/app/views/rails_den/member_profiles/_followed_topics.html.erb +15 -0
  333. data/app/views/rails_den/member_profiles/_header.html.erb +103 -0
  334. data/app/views/rails_den/member_profiles/_recent_posts.html.erb +19 -0
  335. data/app/views/rails_den/member_profiles/_recent_topics.html.erb +15 -0
  336. data/app/views/rails_den/member_profiles/_stats.html.erb +11 -0
  337. data/app/views/rails_den/member_profiles/edit.html.erb +265 -0
  338. data/app/views/rails_den/member_profiles/show.html.erb +97 -0
  339. data/app/views/rails_den/member_reports/new.html.erb +1 -0
  340. data/app/views/rails_den/moderation/audit_events/index.html.erb +11 -0
  341. data/app/views/rails_den/moderation/member_histories/show.html.erb +314 -0
  342. data/app/views/rails_den/moderation/reports/index.html.erb +47 -0
  343. data/app/views/rails_den/moderation/reports/show.html.erb +344 -0
  344. data/app/views/rails_den/moderation/restrictions/new.html.erb +14 -0
  345. data/app/views/rails_den/moderation/topics/show.html.erb +14 -0
  346. data/app/views/rails_den/moderation/warnings/new.html.erb +10 -0
  347. data/app/views/rails_den/notifications/_empty_state.html.erb +13 -0
  348. data/app/views/rails_den/notifications/index.html.erb +308 -0
  349. data/app/views/rails_den/passwords/edit.html.erb +55 -0
  350. data/app/views/rails_den/passwords/new.html.erb +49 -0
  351. data/app/views/rails_den/passwords_mailer/reset.html.erb +22 -0
  352. data/app/views/rails_den/passwords_mailer/reset.text.erb +14 -0
  353. data/app/views/rails_den/post_reports/new.html.erb +1 -0
  354. data/app/views/rails_den/posts/_attachments.html.erb +94 -0
  355. data/app/views/rails_den/posts/edit.html.erb +95 -0
  356. data/app/views/rails_den/posts/history.html.erb +123 -0
  357. data/app/views/rails_den/presence/_directory_body.html.erb +155 -0
  358. data/app/views/rails_den/presence/_summary.html.erb +26 -0
  359. data/app/views/rails_den/presence/index.html.erb +29 -0
  360. data/app/views/rails_den/private_conversations/_composer.html.erb +28 -0
  361. data/app/views/rails_den/private_conversations/_conversation_row.html.erb +19 -0
  362. data/app/views/rails_den/private_conversations/_mailbox_nav.html.erb +7 -0
  363. data/app/views/rails_den/private_conversations/_message.html.erb +4 -0
  364. data/app/views/rails_den/private_conversations/_pagination.html.erb +6 -0
  365. data/app/views/rails_den/private_conversations/index.html.erb +11 -0
  366. data/app/views/rails_den/private_conversations/new.html.erb +5 -0
  367. data/app/views/rails_den/private_conversations/sent.html.erb +11 -0
  368. data/app/views/rails_den/private_conversations/show.html.erb +23 -0
  369. data/app/views/rails_den/reactions/_picker_options.html.erb +14 -0
  370. data/app/views/rails_den/reactions/_post_reactions.html.erb +87 -0
  371. data/app/views/rails_den/reactions/_reactor_list.html.erb +16 -0
  372. data/app/views/rails_den/reactions/index.html.erb +34 -0
  373. data/app/views/rails_den/reactions/picker.html.erb +14 -0
  374. data/app/views/rails_den/registrations/edit.html.erb +95 -0
  375. data/app/views/rails_den/registrations/new.html.erb +77 -0
  376. data/app/views/rails_den/reports/_form.html.erb +54 -0
  377. data/app/views/rails_den/search/index.html.erb +235 -0
  378. data/app/views/rails_den/sessions/new.html.erb +64 -0
  379. data/app/views/rails_den/shared/_ad_network_placement.html.erb +5 -0
  380. data/app/views/rails_den/shared/_composer.html.erb +266 -0
  381. data/app/views/rails_den/shared/_sponsor_placement.html.erb +44 -0
  382. data/app/views/rails_den/tags/show.html.erb +91 -0
  383. data/app/views/rails_den/topic_reports/new.html.erb +1 -0
  384. data/app/views/rails_den/topics/_author_badges.html.erb +15 -0
  385. data/app/views/rails_den/topics/_follow_button.html.erb +45 -0
  386. data/app/views/rails_den/topics/_summary.html.erb +121 -0
  387. data/app/views/rails_den/topics/_tags.html.erb +47 -0
  388. data/app/views/rails_den/topics/new.html.erb +181 -0
  389. data/app/views/rails_den/topics/show.html.erb +430 -0
  390. data/config/locales/rails_den.en.yml +65 -0
  391. data/config/routes.rb +442 -0
  392. data/db/migrate/20260820181527_create_rails_den_administrators.rb +12 -0
  393. data/db/migrate/20260820205806_create_rails_den_categories.rb +16 -0
  394. data/db/migrate/20260820212200_create_rails_den_boards.rb +23 -0
  395. data/db/migrate/20260820234758_create_rails_den_topics.rb +24 -0
  396. data/db/migrate/20260821001937_create_rails_den_posts.rb +11 -0
  397. data/db/migrate/20260822004306_add_soft_deletion_to_rails_den_posts.rb +9 -0
  398. data/db/migrate/20260823183944_create_rails_den_onebox_entries.rb +65 -0
  399. data/db/migrate/20260824025007_add_reply_to_to_rails_den_posts.rb +8 -0
  400. data/db/migrate/20260824092300_create_rails_den_post_revisions.rb +22 -0
  401. data/db/migrate/20260824103358_create_rails_den_post_drafts.rb +36 -0
  402. data/db/migrate/20260824142522_create_rails_den_topic_read_states.rb +32 -0
  403. data/db/migrate/20260824214941_create_rails_den_reader_visits.rb +16 -0
  404. data/db/migrate/20260824225733_create_rails_den_board_read_states.rb +34 -0
  405. data/db/migrate/20260824232113_create_rails_den_community_read_states.rb +26 -0
  406. data/db/migrate/20260825121109_create_rails_den_topic_follows.rb +36 -0
  407. data/db/migrate/20260825232623_create_rails_den_board_follows.rb +36 -0
  408. data/db/migrate/20260826003155_create_rails_den_mentions.rb +27 -0
  409. data/db/migrate/20260826080109_create_rails_den_member_profiles.rb +32 -0
  410. data/db/migrate/20260826092936_add_quoted_post_to_rails_den_posts_and_post_drafts.rb +17 -0
  411. data/db/migrate/20260826101755_create_rails_den_notification_events.rb +19 -0
  412. data/db/migrate/20260826121710_create_rails_den_notifications.rb +28 -0
  413. data/db/migrate/20260826140846_create_rails_den_notification_mutes.rb +25 -0
  414. data/db/migrate/20260826175426_create_rails_den_tags.rb +31 -0
  415. data/db/migrate/20260826175428_create_rails_den_topic_tags.rb +26 -0
  416. data/db/migrate/20260827021133_create_rails_den_roles.rb +16 -0
  417. data/db/migrate/20260827021135_create_rails_den_role_assignments.rb +22 -0
  418. data/db/migrate/20260827031134_create_rails_den_role_permissions.rb +28 -0
  419. data/db/migrate/20260827031136_create_rails_den_board_role_permissions.rb +35 -0
  420. data/db/migrate/20260827033116_provision_rails_den_built_in_roles_and_permissions.rb +108 -0
  421. data/db/migrate/20260827040515_create_rails_den_board_role_assignments.rb +33 -0
  422. data/db/migrate/20260827041710_create_rails_den_board_role_requirements.rb +27 -0
  423. data/db/migrate/20260827063901_create_rails_den_category_role_requirements.rb +23 -0
  424. data/db/migrate/20260827063909_create_rails_den_topic_role_requirements.rb +23 -0
  425. data/db/migrate/20260827203747_create_rails_den_report_reasons.rb +19 -0
  426. data/db/migrate/20260827203752_create_rails_den_reports.rb +24 -0
  427. data/db/migrate/20260827203807_provision_rails_den_default_report_reasons.rb +32 -0
  428. data/db/migrate/20260828030352_create_rails_den_moderation_events.rb +26 -0
  429. data/db/migrate/20260828030354_create_rails_den_moderation_notes.rb +13 -0
  430. data/db/migrate/20260828030357_create_rails_den_user_warnings.rb +17 -0
  431. data/db/migrate/20260828030359_create_rails_den_user_restrictions.rb +30 -0
  432. data/db/migrate/20260828030404_add_moderation_fields_to_rails_den_reports.rb +55 -0
  433. data/db/migrate/20260828030407_add_moderation_fields_to_rails_den_topics.rb +14 -0
  434. data/db/migrate/20260828031955_add_opening_post_to_rails_den_topics.rb +25 -0
  435. data/db/migrate/20260828083552_create_rails_den_reaction_types.rb +15 -0
  436. data/db/migrate/20260828083554_create_rails_den_reactions.rb +20 -0
  437. data/db/migrate/20260828083556_provision_rails_den_default_reaction_type.rb +18 -0
  438. data/db/migrate/20260828151420_create_rails_den_bookmarks.rb +20 -0
  439. data/db/migrate/20260828165545_create_rails_den_profile_fields.rb +37 -0
  440. data/db/migrate/20260828165547_create_rails_den_profile_field_values.rb +31 -0
  441. data/db/migrate/20260828191644_add_presence_fields_to_rails_den_member_profiles.rb +22 -0
  442. data/db/migrate/20260829003137_create_rails_den_private_conversations.rb +13 -0
  443. data/db/migrate/20260829003139_create_rails_den_private_conversation_participants.rb +35 -0
  444. data/db/migrate/20260829003141_create_rails_den_private_messages.rb +19 -0
  445. data/db/migrate/20260829003143_create_rails_den_private_message_recipients.rb +26 -0
  446. data/db/migrate/20260829003435_provision_rails_den_message_capability_defaults.rb +39 -0
  447. data/db/migrate/20260829013557_create_rails_den_chat_channels.rb +17 -0
  448. data/db/migrate/20260829014413_create_rails_den_chat_channel_memberships.rb +26 -0
  449. data/db/migrate/20260829014523_create_rails_den_chat_messages.rb +27 -0
  450. data/db/migrate/20260830155021_create_rails_den_chat_channel_read_states.rb +37 -0
  451. data/db/migrate/20260830155023_add_pinned_to_rails_den_chat_messages.rb +16 -0
  452. data/db/migrate/20260830185149_create_rails_den_badges.rb +44 -0
  453. data/db/migrate/20260830185151_create_rails_den_badge_awards.rb +43 -0
  454. data/db/migrate/20260830190053_create_rails_den_reputation_events.rb +52 -0
  455. data/db/migrate/20260830190055_create_rails_den_trust_levels.rb +52 -0
  456. data/db/migrate/20260830190057_create_rails_den_trust_level_achievements.rb +39 -0
  457. data/db/migrate/20260830190059_add_reputation_points_to_rails_den_member_profiles.rb +17 -0
  458. data/db/migrate/20260831120000_create_rails_den_advertising_and_sponsorships.rb +55 -0
  459. data/db/migrate/20260831120001_add_description_to_rails_den_sponsors.rb +5 -0
  460. data/db/migrate/20260831120002_add_advertising_page_to_rails_den_advertising_configurations.rb +7 -0
  461. data/db/migrate/20260831120003_add_destination_url_to_rails_den_sponsors.rb +5 -0
  462. data/db/migrate/20260831120004_move_destination_url_to_rails_den_sponsorships.rb +138 -0
  463. data/db/migrate/20260831204539_create_rails_den_theme_selections.rb +9 -0
  464. data/exe/rails_den +5 -0
  465. data/lib/generators/rails_den/install/install_generator.rb +42 -0
  466. data/lib/generators/rails_den/install/templates/rails_den.rb +75 -0
  467. data/lib/generators/rails_den/install/views/views_generator.rb +56 -0
  468. data/lib/rails/commands/rails_den/import/discourse/discourse_command.rb +48 -0
  469. data/lib/rails_den/cli.rb +174 -0
  470. data/lib/rails_den/configuration.rb +173 -0
  471. data/lib/rails_den/engine.rb +10 -0
  472. data/lib/rails_den/extensions/descriptor.rb +138 -0
  473. data/lib/rails_den/extensions/registry.rb +65 -0
  474. data/lib/rails_den/extensions.rb +49 -0
  475. data/lib/rails_den/formatters/markdown.rb +30 -0
  476. data/lib/rails_den/formatters/plain_text.rb +18 -0
  477. data/lib/rails_den/formatting.rb +130 -0
  478. data/lib/rails_den/gamification.rb +77 -0
  479. data/lib/rails_den/import/discourse/backup.rb +169 -0
  480. data/lib/rails_den/import/discourse/finalizer.rb +617 -0
  481. data/lib/rails_den/import/discourse/importer.rb +266 -0
  482. data/lib/rails_den/import/discourse/source_database.rb +320 -0
  483. data/lib/rails_den/import/discourse/staff_roles.rb +173 -0
  484. data/lib/rails_den/import/discourse/transformer.rb +940 -0
  485. data/lib/rails_den/import/discourse.rb +9 -0
  486. data/lib/rails_den/licensing/client.rb +128 -0
  487. data/lib/rails_den/licensing/result.rb +57 -0
  488. data/lib/rails_den/licensing.rb +28 -0
  489. data/lib/rails_den/metadata.rb +343 -0
  490. data/lib/rails_den/onebox/address_guard.rb +108 -0
  491. data/lib/rails_den/onebox/body_renderer.rb +135 -0
  492. data/lib/rails_den/onebox/embed_policy.rb +179 -0
  493. data/lib/rails_den/onebox/errors.rb +24 -0
  494. data/lib/rails_den/onebox/extractor.rb +130 -0
  495. data/lib/rails_den/onebox/fetcher.rb +199 -0
  496. data/lib/rails_den/onebox/http_transport.rb +164 -0
  497. data/lib/rails_den/onebox/metadata.rb +343 -0
  498. data/lib/rails_den/onebox/oembed/client.rb +46 -0
  499. data/lib/rails_den/onebox/oembed/discovery.rb +296 -0
  500. data/lib/rails_den/onebox/oembed/response.rb +375 -0
  501. data/lib/rails_den/onebox/oembed.rb +18 -0
  502. data/lib/rails_den/onebox/providers/base.rb +23 -0
  503. data/lib/rails_den/onebox/providers/bluesky.rb +402 -0
  504. data/lib/rails_den/onebox/providers/generic.rb +310 -0
  505. data/lib/rails_den/onebox/providers/giphy.rb +281 -0
  506. data/lib/rails_den/onebox/providers/github.rb +300 -0
  507. data/lib/rails_den/onebox/providers/mastodon.rb +505 -0
  508. data/lib/rails_den/onebox/providers/soundcloud.rb +199 -0
  509. data/lib/rails_den/onebox/providers/spotify.rb +196 -0
  510. data/lib/rails_den/onebox/providers/tenor.rb +296 -0
  511. data/lib/rails_den/onebox/providers/tiktok.rb +292 -0
  512. data/lib/rails_den/onebox/providers/twitch.rb +414 -0
  513. data/lib/rails_den/onebox/providers/vimeo.rb +295 -0
  514. data/lib/rails_den/onebox/providers/youtube.rb +201 -0
  515. data/lib/rails_den/onebox/registry.rb +41 -0
  516. data/lib/rails_den/onebox/renderer.rb +403 -0
  517. data/lib/rails_den/onebox/result.rb +86 -0
  518. data/lib/rails_den/onebox/synchronizer.rb +161 -0
  519. data/lib/rails_den/onebox/url.rb +35 -0
  520. data/lib/rails_den/onebox.rb +176 -0
  521. data/lib/rails_den/presence.rb +162 -0
  522. data/lib/rails_den/standalone_template.rb +700 -0
  523. data/lib/rails_den/themes.rb +80 -0
  524. data/lib/rails_den/version.rb +3 -0
  525. data/lib/rails_den/view_installer.rb +129 -0
  526. data/lib/rails_den.rb +113 -0
  527. data/lib/tasks/rails_den_tasks.rake +4 -0
  528. metadata +735 -0
@@ -0,0 +1,218 @@
1
+ module RailsDen
2
+ class ApplicationController < RailsDen.config.parent_controller.constantize
3
+ layout "rails_den/application"
4
+
5
+ prepend_before_action :authenticate_rails_den_user!
6
+ before_action :enforce_rails_den_account_access!
7
+ before_action :prepend_rails_den_theme_view_path
8
+ before_action :record_rails_den_page_view
9
+ before_action :load_global_advertising_placements,
10
+ if: -> { request.format.html? }
11
+
12
+ helper_method :current_rails_den_user,
13
+ :rails_den_user_signed_in?,
14
+ :rails_den_role_keys,
15
+ :rails_den_has_role?,
16
+ :rails_den_administrator?,
17
+ :rails_den_can_moderate_post?,
18
+ :rails_den_can_edit_post?,
19
+ :rails_den_can_delete_post?,
20
+ :rails_den_can_restore_post?,
21
+ :rails_den_can_view_post_history?,
22
+ :rails_den_can_react_to_post?,
23
+ :rails_den_active_theme
24
+
25
+ def current_rails_den_user
26
+ if RailsDen.config.current_user_resolver
27
+ instance_exec(&RailsDen.config.current_user_resolver)
28
+ else
29
+ send(RailsDen.config.current_user_method)
30
+ end
31
+ end
32
+
33
+ def rails_den_user_signed_in?
34
+ current_rails_den_user.present?
35
+ end
36
+
37
+ def rails_den_role_keys
38
+ return @rails_den_role_keys if defined?(@rails_den_role_keys)
39
+
40
+ @rails_den_role_keys =
41
+ RailsDen::RoleResolver.call(
42
+ user: current_rails_den_user
43
+ )
44
+ end
45
+
46
+ def rails_den_has_role?(role)
47
+ rails_den_role_keys.include?(
48
+ role.to_s
49
+ )
50
+ end
51
+
52
+ def rails_den_administrator?
53
+ rails_den_has_role?(
54
+ :administrator
55
+ )
56
+ end
57
+
58
+ def rails_den_can_moderate_post?(post)
59
+ return false unless rails_den_user_signed_in?
60
+
61
+ RailsDen::PermissionResolver.allowed?(
62
+ user: current_rails_den_user,
63
+ capability: :moderate,
64
+ board: post.topic.board
65
+ )
66
+ end
67
+
68
+ def rails_den_can_edit_post?(post)
69
+ return false unless rails_den_user_signed_in?
70
+ return false if post.deleted?
71
+
72
+ post.author == current_rails_den_user ||
73
+ rails_den_can_moderate_post?(post)
74
+ end
75
+
76
+ def rails_den_can_delete_post?(post)
77
+ return false unless rails_den_user_signed_in?
78
+ return false if post.deleted?
79
+
80
+ rails_den_can_moderate_post?(post) ||
81
+ (
82
+ post.author == current_rails_den_user &&
83
+ !post.opening_post?
84
+ )
85
+ end
86
+
87
+ def rails_den_can_restore_post?(post)
88
+ return false unless rails_den_user_signed_in?
89
+ return false unless post.deleted?
90
+
91
+ rails_den_can_moderate_post?(post) ||
92
+ (
93
+ post.author == current_rails_den_user &&
94
+ post.deleted_by == current_rails_den_user
95
+ )
96
+ end
97
+
98
+ def rails_den_can_view_post_history?(post)
99
+ RailsDen.config.public_revision_history ||
100
+ rails_den_can_moderate_post?(post)
101
+ end
102
+
103
+ def rails_den_can_react_to_post?(post)
104
+ rails_den_user_signed_in? &&
105
+ !post.deleted? &&
106
+ post.author != current_rails_den_user &&
107
+ RailsDen::RestrictionResolver.can_participate?(
108
+ user: current_rails_den_user
109
+ )
110
+ end
111
+
112
+ def rails_den_active_theme
113
+ return @rails_den_active_theme if defined?(@rails_den_active_theme)
114
+
115
+ @rails_den_active_theme = RailsDen::Themes.active
116
+ end
117
+
118
+ def authenticate_rails_den_user!
119
+ return if rails_den_user_signed_in?
120
+
121
+ if RailsDen.config.authentication_handler
122
+ instance_exec(
123
+ &RailsDen.config.authentication_handler
124
+ )
125
+ else
126
+ send(
127
+ RailsDen.config.authentication_method
128
+ )
129
+ end
130
+ end
131
+
132
+ def require_rails_den_participation!
133
+ return if RailsDen::RestrictionResolver.can_participate?(
134
+ user: current_rails_den_user
135
+ )
136
+
137
+ head :forbidden
138
+ end
139
+
140
+ private
141
+
142
+ def prepend_rails_den_theme_view_path
143
+ theme = rails_den_active_theme
144
+ prepend_view_path(theme.view_path.to_s) if theme&.view_path
145
+ end
146
+
147
+ def load_global_advertising_placements
148
+ @rails_den_advertising_configuration =
149
+ AdvertisingConfiguration.first
150
+
151
+ @rails_den_ad_network_code =
152
+ @rails_den_advertising_configuration&.ad_network_code&.presence
153
+
154
+ @rails_den_community_sponsorship =
155
+ CommunitySponsorship
156
+ .includes(:sponsor)
157
+ .active_on(Date.current)
158
+ .first
159
+ end
160
+
161
+ def load_contextual_sponsorship(board)
162
+ @contextual_sponsorship =
163
+ BoardSponsorship
164
+ .includes(:sponsor)
165
+ .active_on(Date.current)
166
+ .find_by(board: board)
167
+
168
+ if @contextual_sponsorship
169
+ @contextual_sponsorship_label =
170
+ "Board Sponsor"
171
+ else
172
+ @contextual_sponsorship =
173
+ CategorySponsorship
174
+ .includes(:sponsor)
175
+ .active_on(Date.current)
176
+ .find_by(
177
+ category: board.category
178
+ )
179
+
180
+ if @contextual_sponsorship
181
+ @contextual_sponsorship_label =
182
+ "Category Sponsor"
183
+ end
184
+ end
185
+ end
186
+
187
+ def enforce_rails_den_account_access!
188
+ return unless rails_den_user_signed_in?
189
+ return if RailsDen::RestrictionResolver.can_access?(
190
+ user: current_rails_den_user
191
+ )
192
+
193
+ state =
194
+ RailsDen::RestrictionResolver.account_state(
195
+ user: current_rails_den_user
196
+ )
197
+
198
+ render plain:
199
+ "Your community access is #{state}.",
200
+ status: :forbidden
201
+ end
202
+
203
+ def record_rails_den_page_view
204
+ return unless rails_den_page_navigation?
205
+ return unless rails_den_user_signed_in?
206
+
207
+ ReaderVisit.record_page_view!(
208
+ reader: current_rails_den_user
209
+ )
210
+ end
211
+
212
+ def rails_den_page_navigation?
213
+ request.get? &&
214
+ request.format.html? &&
215
+ !request.xhr?
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,128 @@
1
+ module RailsDen
2
+ class AttachmentsController < ApplicationController
3
+ SAFE_INLINE_IMAGE_TYPES = {
4
+ "jpg" => "image/jpeg",
5
+ "jpeg" => "image/jpeg",
6
+ "png" => "image/png",
7
+ "gif" => "image/gif",
8
+ "webp" => "image/webp"
9
+ }.freeze
10
+
11
+ def show
12
+ post =
13
+ find_viewable_post
14
+
15
+ attachment =
16
+ post.attachments.find(
17
+ params[:id]
18
+ )
19
+
20
+ inline =
21
+ inline_requested? &&
22
+ safe_inline_image?(attachment)
23
+
24
+ prepare_attachment_headers
25
+
26
+ send_data(
27
+ attachment.blob.download,
28
+ filename: attachment.filename.to_s,
29
+ type: response_content_type(
30
+ attachment,
31
+ inline:
32
+ ),
33
+ disposition:
34
+ inline ? "inline" : "attachment"
35
+ )
36
+ end
37
+
38
+ private
39
+
40
+ def find_viewable_post
41
+ post =
42
+ Post
43
+ .includes(
44
+ topic: {
45
+ board: :category
46
+ }
47
+ )
48
+ .find(
49
+ params[:post_id]
50
+ )
51
+
52
+ unless post.topic_id ==
53
+ params[:topic_id].to_i
54
+ raise ActiveRecord::RecordNotFound
55
+ end
56
+
57
+ readable_content =
58
+ ReadableCommunityContent.new(
59
+ viewer: current_rails_den_user
60
+ )
61
+
62
+ unless readable_content.post_readable?(
63
+ post
64
+ )
65
+ raise ActiveRecord::RecordNotFound
66
+ end
67
+
68
+ post
69
+ end
70
+
71
+ def inline_requested?
72
+ params[:disposition] ==
73
+ "inline"
74
+ end
75
+
76
+ def safe_inline_image?(attachment)
77
+ extension =
78
+ File
79
+ .extname(
80
+ attachment.filename.to_s
81
+ )
82
+ .delete_prefix(".")
83
+ .downcase
84
+
85
+ expected_content_type =
86
+ SAFE_INLINE_IMAGE_TYPES[
87
+ extension
88
+ ]
89
+
90
+ return false if expected_content_type.blank?
91
+
92
+ attachment
93
+ .blob
94
+ .content_type
95
+ .to_s
96
+ .downcase ==
97
+ expected_content_type
98
+ end
99
+
100
+ def response_content_type(
101
+ attachment,
102
+ inline:
103
+ )
104
+ if inline
105
+ attachment.blob.content_type
106
+ else
107
+ "application/octet-stream"
108
+ end
109
+ end
110
+
111
+ def prepare_attachment_headers
112
+ response.headers[
113
+ "Cache-Control"
114
+ ] =
115
+ "private, no-store"
116
+
117
+ response.headers[
118
+ "Pragma"
119
+ ] =
120
+ "no-cache"
121
+
122
+ response.headers[
123
+ "X-Content-Type-Options"
124
+ ] =
125
+ "nosniff"
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,119 @@
1
+ module RailsDen
2
+ class BoardFollowsController < ApplicationController
3
+ def create
4
+ @board = find_readable_board
5
+
6
+ result = board_following.follow
7
+
8
+ respond_to do |format|
9
+ format.html do
10
+ redirect_to board_path(@board),
11
+ status: :see_other,
12
+ notice: follow_notice(result)
13
+ end
14
+
15
+ format.json do
16
+ render json: {
17
+ following: true,
18
+ changed: result.changed,
19
+ board_id: @board.id
20
+ },
21
+ status: result.changed ? :created : :ok
22
+ end
23
+ end
24
+ rescue ActiveRecord::RecordInvalid => error
25
+ render_validation_error(error)
26
+ end
27
+
28
+ def destroy
29
+ @board = find_readable_board
30
+
31
+ result = board_following.unfollow
32
+
33
+ respond_to do |format|
34
+ format.html do
35
+ redirect_to board_path(@board),
36
+ status: :see_other,
37
+ notice: unfollow_notice(result)
38
+ end
39
+
40
+ format.json do
41
+ render json: {
42
+ following: false,
43
+ changed: result.changed,
44
+ board_id: @board.id
45
+ },
46
+ status: :ok
47
+ end
48
+ end
49
+ rescue ActiveRecord::RecordInvalid => error
50
+ render_validation_error(error)
51
+ end
52
+
53
+ private
54
+
55
+ def find_readable_board
56
+ board =
57
+ Board.find(
58
+ params[:board_id]
59
+ )
60
+
61
+ readable_content =
62
+ ReadableCommunityContent.new(
63
+ viewer: current_rails_den_user
64
+ )
65
+
66
+ unless readable_content.board_readable?(
67
+ board
68
+ )
69
+ raise ActiveRecord::RecordNotFound
70
+ end
71
+
72
+ board
73
+ end
74
+
75
+ def board_following
76
+ BoardFollowing.new(
77
+ board: @board,
78
+ follower: current_rails_den_user
79
+ )
80
+ end
81
+
82
+ def follow_notice(result)
83
+ key =
84
+ if result.changed
85
+ "rails_den.board_follows.followed_notice"
86
+ else
87
+ "rails_den.board_follows.already_following_notice"
88
+ end
89
+
90
+ I18n.t(key)
91
+ end
92
+
93
+ def unfollow_notice(result)
94
+ key =
95
+ if result.changed
96
+ "rails_den.board_follows.unfollowed_notice"
97
+ else
98
+ "rails_den.board_follows.already_unfollowed_notice"
99
+ end
100
+
101
+ I18n.t(key)
102
+ end
103
+
104
+ def render_validation_error(error)
105
+ respond_to do |format|
106
+ format.html do
107
+ head :unprocessable_entity
108
+ end
109
+
110
+ format.json do
111
+ render json: {
112
+ errors: error.record.errors.full_messages
113
+ },
114
+ status: :unprocessable_entity
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,36 @@
1
+ module RailsDen
2
+ class BoardReadStatesController < ApplicationController
3
+ def update
4
+ board = find_readable_board
5
+
6
+ board.mark_read!(
7
+ reader: current_rails_den_user
8
+ )
9
+
10
+ redirect_to board_path(board),
11
+ notice: "Board marked read."
12
+ end
13
+
14
+ private
15
+
16
+ def find_readable_board
17
+ board =
18
+ Board.find(
19
+ params[:board_id]
20
+ )
21
+
22
+ readable_content =
23
+ ReadableCommunityContent.new(
24
+ viewer: current_rails_den_user
25
+ )
26
+
27
+ unless readable_content.board_readable?(
28
+ board
29
+ )
30
+ raise ActiveRecord::RecordNotFound
31
+ end
32
+
33
+ board
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,103 @@
1
+ module RailsDen
2
+ class BoardsController < ApplicationController
3
+ skip_before_action :authenticate_rails_den_user!
4
+
5
+ def show
6
+ @board =
7
+ Board.find(
8
+ params[:id]
9
+ )
10
+
11
+ readable_content =
12
+ ReadableCommunityContent.new(
13
+ viewer: current_rails_den_user
14
+ )
15
+
16
+ unless readable_content.board_readable?(
17
+ @board
18
+ )
19
+ raise ActiveRecord::RecordNotFound
20
+ end
21
+
22
+ @board_presence =
23
+ RailsDen::Presence::BoardSummary.call(
24
+ board: @board,
25
+ viewer: current_rails_den_user
26
+ )
27
+
28
+ load_contextual_sponsorship(@board)
29
+
30
+ @board_following =
31
+ rails_den_user_signed_in? &&
32
+ @board.board_follows.exists?(
33
+ follower: current_rails_den_user
34
+ )
35
+
36
+ @topics =
37
+ readable_content
38
+ .topics
39
+ .where(board: @board)
40
+ .includes(:tags)
41
+ .order(
42
+ pinned: :desc,
43
+ created_at: :desc,
44
+ id: :desc
45
+ )
46
+ .to_a
47
+
48
+ @last_posts_by_topic_id =
49
+ @topics.each_with_object({}) do |topic, result|
50
+ result[topic.id] =
51
+ topic.last_visible_post
52
+ end
53
+
54
+ @topic_presence_map =
55
+ RailsDen::Presence::PageMap.call(
56
+ posts:
57
+ @last_posts_by_topic_id
58
+ .values
59
+ .compact,
60
+ viewer: current_rails_den_user
61
+ )
62
+
63
+ @topic_bookmarks_by_topic_id =
64
+ if rails_den_user_signed_in?
65
+ Bookmark
66
+ .where(
67
+ bookmarker: current_rails_den_user,
68
+ bookmarkable_type: "RailsDen::Topic",
69
+ bookmarkable_id: @topics.map(&:id)
70
+ )
71
+ .index_by(&:bookmarkable_id)
72
+ else
73
+ {}
74
+ end
75
+
76
+ @unread_counts_by_topic_id =
77
+ if rails_den_user_signed_in?
78
+ TopicUnreadCounts.call(
79
+ topics: @topics,
80
+ reader: current_rails_den_user
81
+ )
82
+ else
83
+ {}
84
+ end
85
+
86
+ @new_post_counts_by_topic_id =
87
+ if rails_den_user_signed_in?
88
+ reader_visit =
89
+ ReaderVisit.find_by(
90
+ reader: current_rails_den_user
91
+ )
92
+
93
+ TopicNewPostCounts.call(
94
+ topics: @topics,
95
+ reader: current_rails_den_user,
96
+ since: reader_visit&.previous_visit_at
97
+ )
98
+ else
99
+ {}
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,91 @@
1
+ module RailsDen
2
+ class BookmarksController < ApplicationController
3
+ before_action :load_bookmark, only: %i[update destroy]
4
+
5
+ def index
6
+ load_list
7
+ end
8
+
9
+ def update
10
+ ensure_bookmark_readable!
11
+
12
+ if @bookmark.update(bookmark_params)
13
+ redirect_to bookmarks_path(page: normalized_return_page)
14
+ else
15
+ @editing_bookmark_id = @bookmark.id
16
+ actual_page = BookmarkList.page_for(bookmarker: current_rails_den_user, bookmark: @bookmark)
17
+ load_list(page: actual_page, include_bookmark: @bookmark)
18
+ render :index, status: :unprocessable_entity
19
+ end
20
+ end
21
+
22
+ def destroy
23
+ target = @bookmark.bookmarkable
24
+ @bookmark.destroy!
25
+
26
+ respond_to do |format|
27
+ format.html { redirect_after_destroy(target) }
28
+ format.json { render json: { html: render_control(target) } }
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def load_bookmark
35
+ @bookmark = Bookmark.where(bookmarker: current_rails_den_user).find(params[:id])
36
+ end
37
+
38
+ def bookmark_params
39
+ params.require(:bookmark).permit(:note)
40
+ end
41
+
42
+ def normalized_return_page
43
+ [params[:page].to_i, 1].max
44
+ end
45
+
46
+ def load_list(page: params[:page], include_bookmark: nil)
47
+ result = BookmarkList.call(bookmarker: current_rails_den_user, page: page)
48
+ @bookmarks = result.bookmarks
49
+ @page = result.page
50
+ @total_pages = result.total_pages
51
+ @post_pages = result.post_pages
52
+ return unless include_bookmark
53
+
54
+ matching_index = @bookmarks.index { |bookmark| bookmark.id == include_bookmark.id }
55
+ if matching_index
56
+ @bookmarks[matching_index] = include_bookmark
57
+ else
58
+ raise ActiveRecord::RecordNotFound
59
+ end
60
+ end
61
+
62
+ def redirect_after_destroy(target)
63
+ case params[:origin]
64
+ when "topic"
65
+ return redirect_to bookmarks_path(page: normalized_return_page) unless target.is_a?(Topic)
66
+
67
+ options = {}
68
+ options[:page] = target.normalized_posts_page(params[:page]) if params[:page].present?
69
+ redirect_to topic_path(target, **options)
70
+ when "post"
71
+ return redirect_to bookmarks_path(page: normalized_return_page) unless target.is_a?(Post)
72
+
73
+ redirect_to topic_path(target.topic, page: target.topic.posts_page_for(target), anchor: "post-#{target.id}")
74
+ else
75
+ redirect_to bookmarks_path(page: normalized_return_page)
76
+ end
77
+ end
78
+
79
+ def render_control(target)
80
+ partial = target.is_a?(Topic) ? "topic_control" : "post_control"
81
+ render_to_string(partial: "rails_den/bookmarks/#{partial}", formats: [:html], locals: { (target.is_a?(Topic) ? :topic : :post) => target, bookmark: nil })
82
+ end
83
+
84
+ def ensure_bookmark_readable!
85
+ readable = ReadableCommunityContent.new(viewer: current_rails_den_user)
86
+ target = @bookmark.bookmarkable
87
+ allowed = target.is_a?(Topic) ? readable.topic_readable?(target) : readable.post_readable?(target)
88
+ raise ActiveRecord::RecordNotFound unless allowed
89
+ end
90
+ end
91
+ end