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,150 @@
1
+ module RailsDen
2
+ class ProfileField < ApplicationRecord
3
+ FIELD_TYPES = %w[
4
+ text
5
+ textarea
6
+ url
7
+ select
8
+ checkbox
9
+ ].freeze
10
+
11
+ RESERVED_KEYS = %w[
12
+ username
13
+ avatar
14
+ avatar_url
15
+ bio
16
+ display_name
17
+ mention_name
18
+ joined_at
19
+ last_activity_at
20
+ topics_count
21
+ posts_count
22
+ reactions_received
23
+ ].freeze
24
+
25
+ has_many :profile_field_values,
26
+ class_name: "RailsDen::ProfileFieldValue",
27
+ inverse_of: :profile_field,
28
+ dependent: :restrict_with_exception
29
+
30
+ validates :key,
31
+ presence: true,
32
+ uniqueness: true,
33
+ length: {
34
+ maximum: 50
35
+ },
36
+ format: {
37
+ with: /\A[a-z][a-z0-9_]*\z/,
38
+ message: "must start with a letter and contain only lowercase letters, numbers, and underscores"
39
+ }
40
+
41
+ validates :label,
42
+ presence: true
43
+
44
+ validates :field_type,
45
+ presence: true,
46
+ inclusion: {
47
+ in: FIELD_TYPES
48
+ }
49
+
50
+ validates :required,
51
+ :enabled,
52
+ inclusion: {
53
+ in: [ true, false ]
54
+ }
55
+
56
+ validates :position,
57
+ numericality: {
58
+ only_integer: true
59
+ }
60
+
61
+ validate :key_is_not_reserved
62
+ validate :key_is_immutable,
63
+ on: :update
64
+ validate :select_has_options
65
+
66
+ before_validation :normalize_attributes
67
+
68
+ scope :ordered,
69
+ -> { order(:position, :id) }
70
+
71
+ scope :enabled,
72
+ -> { where(enabled: true) }
73
+
74
+ def options
75
+ options_text
76
+ .to_s
77
+ .lines
78
+ .map(&:strip)
79
+ .reject(&:blank?)
80
+ .uniq
81
+ end
82
+
83
+ def host_adapter_key
84
+ key
85
+ end
86
+
87
+ def self.model_name
88
+ @model_name ||= ActiveModel::Name.new(
89
+ self,
90
+ nil,
91
+ "RailsDen::ProfileField"
92
+ )
93
+ end
94
+
95
+ private
96
+
97
+ def normalize_attributes
98
+ self.key =
99
+ key
100
+ .to_s
101
+ .strip
102
+ .downcase
103
+
104
+ self.label =
105
+ label
106
+ .to_s
107
+ .strip
108
+
109
+ self.field_type =
110
+ field_type
111
+ .to_s
112
+ .strip
113
+ .downcase
114
+
115
+ self.position ||= 0
116
+
117
+ if field_type == "select"
118
+ self.options_text = options.join("\n")
119
+ end
120
+ end
121
+
122
+ def key_is_not_reserved
123
+ return unless RESERVED_KEYS.include?(key)
124
+
125
+ errors.add(
126
+ :key,
127
+ "is reserved by RailsDen"
128
+ )
129
+ end
130
+
131
+ def key_is_immutable
132
+ return unless will_save_change_to_key?
133
+
134
+ errors.add(
135
+ :key,
136
+ "cannot be changed after the profile field is created"
137
+ )
138
+ end
139
+
140
+ def select_has_options
141
+ return unless field_type == "select"
142
+ return if options.any?
143
+
144
+ errors.add(
145
+ :options_text,
146
+ "must contain at least one option"
147
+ )
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,136 @@
1
+ require "uri"
2
+
3
+ module RailsDen
4
+ class ProfileFieldValue < ApplicationRecord
5
+ belongs_to :member_profile,
6
+ class_name: "RailsDen::MemberProfile",
7
+ inverse_of: :profile_field_values
8
+
9
+ belongs_to :profile_field,
10
+ class_name: "RailsDen::ProfileField",
11
+ inverse_of: :profile_field_values
12
+
13
+ validates :profile_field_id,
14
+ uniqueness: {
15
+ scope: :member_profile_id
16
+ }
17
+
18
+ validate :value_matches_profile_field
19
+
20
+ before_validation :normalize_value
21
+
22
+ def checkbox_value?
23
+ value == "1"
24
+ end
25
+
26
+ private
27
+
28
+ def normalize_value
29
+ return unless profile_field
30
+
31
+ self.value =
32
+ case profile_field.field_type
33
+ when "checkbox"
34
+ ActiveModel::Type::Boolean.new.cast(value) ? "1" : "0"
35
+ else
36
+ value.to_s.strip
37
+ end
38
+ end
39
+
40
+ def value_matches_profile_field
41
+ return unless profile_field
42
+
43
+ case profile_field.field_type
44
+ when "text"
45
+ validate_text
46
+ when "textarea"
47
+ validate_textarea
48
+ when "url"
49
+ validate_url
50
+ when "select"
51
+ validate_select
52
+ when "checkbox"
53
+ validate_checkbox
54
+ end
55
+
56
+ validate_required
57
+ end
58
+
59
+ def validate_text
60
+ return if value.to_s.length <= 255
61
+
62
+ errors.add(
63
+ :value,
64
+ "must be 255 characters or fewer"
65
+ )
66
+ end
67
+
68
+ def validate_textarea
69
+ return if value.to_s.length <= 2_000
70
+
71
+ errors.add(
72
+ :value,
73
+ "must be 2,000 characters or fewer"
74
+ )
75
+ end
76
+
77
+ def validate_url
78
+ return if value.blank?
79
+
80
+ if value.length > 2_048
81
+ errors.add(
82
+ :value,
83
+ "must be 2,048 characters or fewer"
84
+ )
85
+
86
+ return
87
+ end
88
+
89
+ uri = URI.parse(value)
90
+
91
+ return if uri.is_a?(URI::HTTP) &&
92
+ uri.host.present? &&
93
+ %w[http https].include?(uri.scheme)
94
+
95
+ errors.add(
96
+ :value,
97
+ "must be a valid HTTP or HTTPS URL"
98
+ )
99
+ rescue URI::InvalidURIError
100
+ errors.add(
101
+ :value,
102
+ "must be a valid HTTP or HTTPS URL"
103
+ )
104
+ end
105
+
106
+ def validate_select
107
+ return if value.blank?
108
+ return if profile_field.options.include?(value)
109
+
110
+ errors.add(
111
+ :value,
112
+ "is not one of the available options"
113
+ )
114
+ end
115
+
116
+ def validate_checkbox
117
+ return if %w[0 1].include?(value)
118
+
119
+ errors.add(
120
+ :value,
121
+ "must be checked or unchecked"
122
+ )
123
+ end
124
+
125
+ def validate_required
126
+ return unless profile_field.required?
127
+ return if profile_field.field_type == "checkbox"
128
+ return if value.present?
129
+
130
+ errors.add(
131
+ :value,
132
+ "is required"
133
+ )
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,56 @@
1
+ module RailsDen
2
+ class Reaction < ApplicationRecord
3
+ belongs_to :post,
4
+ class_name: "RailsDen::Post",
5
+ inverse_of: :reactions
6
+
7
+ belongs_to :reaction_type,
8
+ class_name: "RailsDen::ReactionType",
9
+ inverse_of: :reactions
10
+
11
+ belongs_to :reactor,
12
+ polymorphic: true
13
+
14
+ validates :reaction_type_id,
15
+ uniqueness: {
16
+ scope: %i[
17
+ post_id
18
+ reactor_type
19
+ reactor_id
20
+ ]
21
+ }
22
+
23
+ after_commit :emit_notification_event,
24
+ on: :create
25
+
26
+ after_commit :record_gamification_activity,
27
+ on: :create
28
+
29
+ after_commit :remove_notification_event,
30
+ on: :destroy
31
+
32
+ private
33
+
34
+ def emit_notification_event
35
+ RailsDen::NotificationEvents::Emitter.call(
36
+ reaction: self
37
+ )
38
+ end
39
+
40
+ def record_gamification_activity
41
+ RailsDen::Gamification::ActivityRecorder.reaction_created(
42
+ self
43
+ )
44
+ end
45
+
46
+ def remove_notification_event
47
+ event =
48
+ RailsDen::NotificationEvent.find_by(
49
+ dedupe_key:
50
+ "post_reacted:reaction:#{id}"
51
+ )
52
+
53
+ event&.destroy!
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,23 @@
1
+ module RailsDen
2
+ class ReactionType < ApplicationRecord
3
+ has_many :reactions,
4
+ class_name: "RailsDen::Reaction",
5
+ inverse_of: :reaction_type,
6
+ dependent: :restrict_with_error
7
+
8
+ validates :name, :key, :emoji, presence: true
9
+ validates :key,
10
+ uniqueness: true,
11
+ format: { with: /\A[a-z0-9_]+\z/ }
12
+ validates :position,
13
+ numericality: { only_integer: true, greater_than_or_equal_to: 0 }
14
+ validates :enabled, inclusion: { in: [true, false] }
15
+
16
+ scope :ordered, -> { order(:position, :id) }
17
+ scope :enabled, -> { where(enabled: true) }
18
+
19
+ def self.model_name
20
+ @model_name ||= ActiveModel::Name.new(self, nil, "RailsDen::ReactionType")
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,61 @@
1
+ module RailsDen
2
+ class ReaderVisit < ApplicationRecord
3
+ belongs_to :reader, polymorphic: true
4
+
5
+ validates :last_seen_at, presence: true
6
+
7
+ def self.record_page_view!(reader:, at: Time.current)
8
+ ensure_positive_inactivity_window!
9
+
10
+ visit =
11
+ create_or_find_by!(reader: reader) do |new_visit|
12
+ new_visit.previous_visit_at = at
13
+ new_visit.last_seen_at = at
14
+ end
15
+
16
+ visit.record_page_view!(at: at)
17
+ end
18
+
19
+ def self.ensure_positive_inactivity_window!
20
+ inactivity_window =
21
+ RailsDen.config.visit_inactivity_window
22
+
23
+ return if inactivity_window.respond_to?(:positive?) &&
24
+ inactivity_window.positive?
25
+
26
+ raise ArgumentError,
27
+ "visit_inactivity_window must be positive"
28
+ end
29
+ private_class_method :ensure_positive_inactivity_window!
30
+
31
+ def record_page_view!(at: Time.current)
32
+ inactivity_window =
33
+ RailsDen.config.visit_inactivity_window
34
+
35
+ unless inactivity_window.respond_to?(:positive?) &&
36
+ inactivity_window.positive?
37
+ raise ArgumentError,
38
+ "visit_inactivity_window must be positive"
39
+ end
40
+
41
+ with_lock do
42
+ attributes = {}
43
+
44
+ if previous_visit_at.nil?
45
+ attributes[:previous_visit_at] = created_at
46
+ end
47
+
48
+ if at >= last_seen_at + inactivity_window
49
+ attributes[:previous_visit_at] = last_seen_at
50
+ attributes[:last_seen_at] = at
51
+ elsif at > last_seen_at
52
+ attributes[:last_seen_at] = at
53
+ end
54
+
55
+ update!(attributes) if attributes.any?
56
+ end
57
+
58
+ self
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,127 @@
1
+ module RailsDen
2
+ class Report < ApplicationRecord
3
+ STATUSES = %w[open resolved dismissed].freeze
4
+
5
+ belongs_to :reporter, polymorphic: true
6
+ belongs_to :reportable, polymorphic: true
7
+ belongs_to :report_reason,
8
+ class_name: "RailsDen::ReportReason",
9
+ inverse_of: :reports
10
+ belongs_to :subject_user, polymorphic: true
11
+ belongs_to :assigned_to, polymorphic: true, optional: true
12
+ belongs_to :closed_by, polymorphic: true, optional: true
13
+
14
+ has_many :moderation_notes,
15
+ as: :subject,
16
+ class_name: "RailsDen::ModerationNote",
17
+ dependent: :restrict_with_error
18
+
19
+ before_validation :derive_subject_user, on: :create
20
+
21
+ validates :status, inclusion: { in: STATUSES }
22
+ validates :subject_label, presence: true
23
+ validates :details, length: { maximum: 5_000 }
24
+
25
+ validate :reason_accepts_new_report, on: :create
26
+ validate :details_present_for_other_reason, on: :create
27
+ validate :reporter_is_not_target_owner, on: :create
28
+ validate :reporter_can_read_target, on: :create
29
+ validate :no_duplicate_open_report, on: :create
30
+
31
+ scope :open, -> { where(status: "open") }
32
+ scope :history, -> { where(status: %w[resolved dismissed]) }
33
+
34
+ STATUSES.each do |value|
35
+ define_method("#{value}?") { status == value }
36
+ end
37
+
38
+ validate :assignment_fields_are_consistent
39
+
40
+ private
41
+
42
+ def derive_subject_user
43
+ self.subject_user =
44
+ case reportable
45
+ when RailsDen::Topic, RailsDen::Post
46
+ reportable.author
47
+ else
48
+ reportable
49
+ end
50
+ end
51
+
52
+ def assignment_fields_are_consistent
53
+ return if assigned_to.nil? == assigned_at.nil?
54
+
55
+ errors.add(:assigned_at, "must be present exactly when an assignee is present")
56
+ end
57
+
58
+ def reason_accepts_new_report
59
+ return unless report_reason && reportable
60
+
61
+ errors.add(:report_reason, "is not available") unless report_reason.enabled?
62
+ unless report_reason.applicable_to?(reportable)
63
+ errors.add(:report_reason, "is not applicable to this report")
64
+ end
65
+ end
66
+
67
+ def details_present_for_other_reason
68
+ return unless report_reason&.key == "other"
69
+ return if details.present?
70
+
71
+ errors.add(:details, "must be provided when the reason is Other")
72
+ end
73
+
74
+ def reporter_is_not_target_owner
75
+ return unless reporter && reportable
76
+
77
+ owner =
78
+ case reportable
79
+ when RailsDen::Topic, RailsDen::Post
80
+ reportable.author
81
+ else
82
+ reportable
83
+ end
84
+
85
+ if owner == reporter
86
+ errors.add(:reportable, "cannot be your own content or profile")
87
+ end
88
+ end
89
+
90
+ def reporter_can_read_target
91
+ return unless reporter && reportable
92
+
93
+ readable =
94
+ case reportable
95
+ when RailsDen::Topic
96
+ RailsDen::ReadableCommunityContent.new(viewer: reporter)
97
+ .topic_readable?(reportable)
98
+ when RailsDen::Post
99
+ RailsDen::ReadableCommunityContent.new(viewer: reporter)
100
+ .post_readable?(reportable)
101
+ else
102
+ visible_member_identity?(reportable)
103
+ end
104
+
105
+ errors.add(:reportable, "is not available") unless readable
106
+ end
107
+
108
+ def visible_member_identity?(identity)
109
+ mention_name = RailsDen.user_mention_name(identity)
110
+
111
+ mention_name.present? &&
112
+ RailsDen.resolve_user_mention(mention_name) == identity
113
+ end
114
+
115
+ def no_duplicate_open_report
116
+ return unless reporter && reportable
117
+ return unless status == "open"
118
+
119
+ duplicate = self.class.open.where(
120
+ reporter: reporter,
121
+ reportable: reportable
122
+ )
123
+
124
+ errors.add(:reportable, "has already been reported by you") if duplicate.exists?
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,52 @@
1
+ module RailsDen
2
+ class ReportReason < ApplicationRecord
3
+ TARGET_KINDS = %w[all content user].freeze
4
+
5
+ has_many :reports,
6
+ class_name: "RailsDen::Report",
7
+ inverse_of: :report_reason,
8
+ dependent: :restrict_with_error
9
+
10
+ validates :name, :key, presence: true
11
+ validates :key,
12
+ uniqueness: true,
13
+ format: {
14
+ with: /\A[a-z0-9]+(?:_[a-z0-9]+)*\z/,
15
+ message: "must contain only lowercase letters, numbers, and underscores"
16
+ }
17
+ validates :target_kind, inclusion: { in: TARGET_KINDS }
18
+ validates :position,
19
+ numericality: {
20
+ only_integer: true,
21
+ greater_than_or_equal_to: 0
22
+ }
23
+ validates :enabled, inclusion: { in: [true, false] }
24
+
25
+ scope :ordered, -> { order(:position, :id) }
26
+
27
+ scope :available_for,
28
+ ->(target) {
29
+ kinds =
30
+ if target.is_a?(RailsDen::Topic) ||
31
+ target.is_a?(RailsDen::Post)
32
+ %w[all content]
33
+ else
34
+ %w[all user]
35
+ end
36
+
37
+ where(enabled: true, target_kind: kinds).ordered
38
+ }
39
+
40
+ def applicable_to?(target)
41
+ self.class.available_for(target).where(id: id).exists?
42
+ end
43
+
44
+ def self.model_name
45
+ @model_name ||= ActiveModel::Name.new(
46
+ self,
47
+ nil,
48
+ "RailsDen::ReportReason"
49
+ )
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,92 @@
1
+ module RailsDen
2
+ class ReputationEvent < ApplicationRecord
3
+ EVENT_TYPES = %w[
4
+ topic_created
5
+ reply_created
6
+ manual_adjustment
7
+ ].freeze
8
+
9
+ belongs_to :recipient,
10
+ polymorphic: true
11
+
12
+ belongs_to :actor,
13
+ polymorphic: true,
14
+ optional: true
15
+
16
+ belongs_to :source,
17
+ polymorphic: true,
18
+ optional: true
19
+
20
+ validates :event_type,
21
+ presence: true,
22
+ inclusion: {
23
+ in: EVENT_TYPES
24
+ }
25
+
26
+ validates :points,
27
+ numericality: {
28
+ only_integer: true,
29
+ other_than: 0
30
+ }
31
+
32
+ validates :dedupe_key,
33
+ presence: true,
34
+ uniqueness: true
35
+
36
+ validates :reason,
37
+ length: {
38
+ maximum: 1_000
39
+ }
40
+
41
+ validate :manual_adjustment_has_actor_and_reason
42
+ validate :automatic_event_has_source
43
+
44
+ before_update :prevent_mutation
45
+ before_destroy :prevent_mutation
46
+
47
+ private
48
+
49
+ def manual_adjustment_has_actor_and_reason
50
+ return unless event_type == "manual_adjustment"
51
+
52
+ if actor.nil?
53
+ errors.add(
54
+ :actor,
55
+ "must be present for a manual adjustment"
56
+ )
57
+ end
58
+
59
+ if reason.blank?
60
+ errors.add(
61
+ :reason,
62
+ "must be present for a manual adjustment"
63
+ )
64
+ end
65
+ end
66
+
67
+ def automatic_event_has_source
68
+ return unless event_type.in?(
69
+ %w[
70
+ topic_created
71
+ reply_created
72
+ ]
73
+ )
74
+
75
+ return if source.present?
76
+
77
+ errors.add(
78
+ :source,
79
+ "must be present for an automatic reputation event"
80
+ )
81
+ end
82
+
83
+ def prevent_mutation
84
+ errors.add(
85
+ :base,
86
+ "Reputation events are immutable"
87
+ )
88
+
89
+ throw :abort
90
+ end
91
+ end
92
+ end