droom 0.4.3 → 0.5.3

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 (396) hide show
  1. checksums.yaml +7 -0
  2. data/app/assets/images/droom/admin_menu.png +0 -0
  3. data/app/assets/images/droom/applications_menu.png +0 -0
  4. data/app/assets/images/droom/arrows_small.png +0 -0
  5. data/app/assets/images/droom/config_menu.png +0 -0
  6. data/app/assets/images/droom/dropdown.png +0 -0
  7. data/app/assets/images/droom/menu/asterisk.png +0 -0
  8. data/app/assets/images/droom/{smalladd.png → menu/smalladd.png} +0 -0
  9. data/app/assets/images/droom/{small_asterisk.png → menu/smallasterisk.png} +0 -0
  10. data/app/assets/images/droom/{smalldelete.png → menu/smalldelete.png} +0 -0
  11. data/app/assets/images/droom/{smalldownload.png → menu/smalldownload.png} +0 -0
  12. data/app/assets/images/droom/{smalledit.png → menu/smalledit.png} +0 -0
  13. data/app/assets/images/droom/message.png +0 -0
  14. data/app/assets/images/droom/missing/scrap.jpg +0 -0
  15. data/app/assets/images/droom/missing/scrap.png +0 -0
  16. data/app/assets/images/droom/missing/thumb.png +0 -0
  17. data/app/assets/images/droom/missing/user.png +0 -0
  18. data/app/assets/images/droom/object_icons.png +0 -0
  19. data/app/assets/images/droom/panel/add.png +0 -0
  20. data/app/assets/images/droom/panel/edit.png +0 -0
  21. data/app/assets/images/droom/panel/manage.png +0 -0
  22. data/app/assets/images/droom/pointer_upwards.png +0 -0
  23. data/app/assets/images/droom/search_menu.png +0 -0
  24. data/app/assets/images/droom/setter.png +0 -0
  25. data/app/assets/images/droom/small_icons.png +0 -0
  26. data/app/assets/images/droom/stream/closer.png +0 -0
  27. data/app/assets/images/droom/stream/next.png +0 -0
  28. data/app/assets/images/droom/stream/prev.png +0 -0
  29. data/app/assets/images/droom/stream/previous.png +0 -0
  30. data/app/assets/images/droom/subscribe.png +0 -0
  31. data/app/assets/javascripts/droom.js.coffee +23 -13
  32. data/app/assets/javascripts/droom/actions.js.coffee +104 -10
  33. data/app/assets/javascripts/droom/ajax.js.coffee +36 -24
  34. data/app/assets/javascripts/droom/extensions.js.coffee +13 -0
  35. data/app/assets/javascripts/droom/lib/jquery.datepicker.js +1026 -0
  36. data/app/assets/javascripts/droom/lib/jquery.deserialize.js +142 -0
  37. data/app/assets/javascripts/droom/lib/jquery_ujs.js +416 -0
  38. data/app/assets/javascripts/droom/lib/swipe.js +568 -0
  39. data/app/assets/javascripts/droom/lib/underscore.js +1276 -0
  40. data/app/assets/javascripts/droom/popups.js.coffee +98 -37
  41. data/app/assets/javascripts/droom/stream.js.coffee +119 -77
  42. data/app/assets/javascripts/droom/utilities.js.coffee +25 -3
  43. data/app/assets/javascripts/droom/widgets.js.coffee +318 -253
  44. data/app/assets/stylesheets/droom.css.sass +482 -955
  45. data/app/assets/stylesheets/droom/_mixins.css.sass +12 -42
  46. data/app/assets/stylesheets/droom/_popups.css.sass +28 -11
  47. data/app/assets/stylesheets/droom/_stream.css.sass +375 -0
  48. data/app/assets/stylesheets/droom/_toolbar.css.sass +0 -6
  49. data/app/assets/stylesheets/droom/_variables.css.sass +4 -2
  50. data/app/controllers/droom/agenda_categories_controller.rb +6 -14
  51. data/app/controllers/droom/api/api_controller.rb +56 -0
  52. data/app/controllers/droom/api/events_controller.rb +74 -0
  53. data/app/controllers/droom/api/users_controller.rb +92 -0
  54. data/app/controllers/droom/api/venues_controller.rb +71 -0
  55. data/app/controllers/droom/calendars_controller.rb +4 -10
  56. data/app/controllers/droom/{user_confirmations_controller.rb → confirmations_controller.rb} +8 -7
  57. data/app/controllers/droom/dashboard_controller.rb +2 -19
  58. data/app/controllers/droom/documents_controller.rb +12 -60
  59. data/app/controllers/droom/dropbox_tokens_controller.rb +3 -4
  60. data/app/controllers/droom/engine_controller.rb +39 -11
  61. data/app/controllers/droom/events_controller.rb +62 -84
  62. data/app/controllers/droom/folders_controller.rb +30 -40
  63. data/app/controllers/droom/group_invitations_controller.rb +6 -23
  64. data/app/controllers/droom/group_permissions_controller.rb +26 -0
  65. data/app/controllers/droom/groups_controller.rb +10 -14
  66. data/app/controllers/droom/invitations_controller.rb +7 -25
  67. data/app/controllers/droom/memberships_controller.rb +13 -18
  68. data/app/controllers/droom/organisations_controller.rb +6 -25
  69. data/app/controllers/droom/permissions_controller.rb +50 -0
  70. data/app/controllers/droom/preferences_controller.rb +9 -10
  71. data/app/controllers/droom/scraps_controller.rb +23 -26
  72. data/app/controllers/droom/services_controller.rb +50 -0
  73. data/app/controllers/droom/suggestions_controller.rb +6 -4
  74. data/app/controllers/droom/users/confirmations_controller.rb +24 -0
  75. data/app/controllers/droom/users/passwords_controller.rb +39 -0
  76. data/app/controllers/droom/users/sessions_controller.rb +7 -0
  77. data/app/controllers/droom/users_controller.rb +60 -38
  78. data/app/controllers/droom/venues_controller.rb +8 -15
  79. data/app/controllers/droom/youtube_controller.rb +1 -1
  80. data/app/helpers/droom/droom_helper.rb +22 -17
  81. data/app/models/droom/ability.rb +81 -0
  82. data/app/models/droom/agenda_category.rb +0 -1
  83. data/app/models/droom/calendar.rb +12 -7
  84. data/app/models/droom/category.rb +4 -10
  85. data/app/models/droom/concerns/slugged.rb +21 -0
  86. data/app/models/droom/document.rb +15 -17
  87. data/app/models/droom/dropbox_document.rb +3 -6
  88. data/app/models/droom/dropbox_token.rb +2 -3
  89. data/app/models/droom/event.rb +117 -163
  90. data/app/models/droom/event_type.rb +25 -0
  91. data/app/models/droom/folder.rb +36 -48
  92. data/app/models/droom/group.rb +35 -27
  93. data/app/models/droom/group_invitation.rb +7 -10
  94. data/app/models/droom/group_permission.rb +29 -0
  95. data/app/models/droom/invitation.rb +14 -23
  96. data/app/models/droom/mailing_list_membership.rb +0 -1
  97. data/app/models/droom/membership.rb +32 -31
  98. data/app/models/droom/organisation.rb +6 -3
  99. data/app/models/droom/permission.rb +19 -0
  100. data/app/models/droom/personal_folder.rb +9 -21
  101. data/app/models/droom/preference.rb +1 -2
  102. data/app/models/droom/scrap.rb +31 -26
  103. data/app/models/droom/service.rb +31 -0
  104. data/app/models/droom/tag.rb +10 -9
  105. data/app/models/droom/tagging.rb +1 -1
  106. data/app/models/droom/user.rb +453 -130
  107. data/app/models/droom/user_permission.rb +8 -0
  108. data/app/models/droom/venue.rb +21 -32
  109. data/app/serializers/droom/user_serializer.rb +45 -0
  110. data/app/serializers/droom/venue_serializer.rb +5 -0
  111. data/app/views/droom/dashboard/_documents.html.haml +1 -1
  112. data/app/views/droom/dashboard/_folders.html.haml +1 -1
  113. data/app/views/droom/dashboard/_future_events.html.haml +10 -8
  114. data/app/views/droom/dashboard/_groups.html.haml +1 -1
  115. data/app/views/droom/dashboard/_past_events.haml +19 -12
  116. data/app/views/droom/dashboard/_stream.html.haml +2 -2
  117. data/app/views/droom/{users/_extra_columns.html.haml → dashboard/_welcome.html.haml} +0 -0
  118. data/app/views/droom/dashboard/index.html.haml +3 -0
  119. data/app/views/droom/documents/_document.html.haml +0 -2
  120. data/app/views/droom/documents/_documents.html.haml +1 -1
  121. data/app/views/droom/documents/_documents_list.html.haml +1 -1
  122. data/app/views/droom/documents/_documents_table.html.haml +1 -1
  123. data/app/views/droom/documents/_form.html.haml +8 -5
  124. data/app/views/droom/documents/_listing.html.haml +4 -2
  125. data/app/views/droom/documents/index.html.haml +13 -7
  126. data/app/views/droom/documents/new.html.haml +1 -4
  127. data/app/views/droom/events/_action_menu.html.haml +2 -2
  128. data/app/views/droom/events/_attachments.html.haml +8 -0
  129. data/app/views/droom/events/_calendar.html.haml +4 -8
  130. data/app/views/droom/events/_event.html.haml +44 -63
  131. data/app/views/droom/events/_event_line.html.haml +1 -1
  132. data/app/views/droom/events/_events.html.haml +18 -2
  133. data/app/views/droom/events/_form.html.haml +26 -19
  134. data/app/views/droom/events/_invitations.html.haml +7 -3
  135. data/app/views/droom/events/_scrap_events.html.haml +8 -0
  136. data/app/views/droom/events/_suggested.html.haml +4 -1
  137. data/app/views/droom/events/calendar.html.haml +8 -0
  138. data/app/views/droom/events/index.html.haml +24 -26
  139. data/app/views/droom/events/show.html.haml +6 -6
  140. data/app/views/droom/folders/_contents.html.haml +34 -4
  141. data/app/views/droom/folders/_folder.html.haml +7 -2
  142. data/app/views/droom/folders/_form.html.haml +11 -8
  143. data/app/views/droom/folders/index.html.haml +19 -22
  144. data/app/views/droom/group_invitations/_attending_groups.html.haml +1 -1
  145. data/app/views/droom/group_permissions/_toggle.html.haml +18 -0
  146. data/app/views/droom/groups/_action_menu.haml +0 -2
  147. data/app/views/droom/groups/_created.html.haml +0 -1
  148. data/app/views/droom/groups/_form.html.haml +3 -0
  149. data/app/views/droom/groups/_group.html.haml +10 -8
  150. data/app/views/droom/groups/_groups.html.haml +1 -1
  151. data/app/views/droom/groups/_search_result.html.haml +1 -1
  152. data/app/views/droom/groups/_suggested.html.haml +1 -1
  153. data/app/views/droom/groups/index.html.haml +15 -16
  154. data/app/views/droom/invitations/_form.html.haml +1 -1
  155. data/app/views/droom/invitations/_invitation.html.haml +1 -1
  156. data/app/views/droom/invitations/_invitations.html.haml +4 -4
  157. data/app/views/droom/memberships/_button.html.haml +5 -5
  158. data/app/views/droom/memberships/_form.html.haml +26 -7
  159. data/app/views/droom/memberships/_member.html.haml +1 -1
  160. data/app/views/droom/memberships/{_membership_toggle.html.haml → _toggle.html.haml} +4 -4
  161. data/app/views/droom/memberships/index.html.haml +9 -0
  162. data/app/views/droom/organisations/_action_menu.html.haml +1 -1
  163. data/app/views/droom/organisations/_organisation.html.haml +2 -2
  164. data/app/views/droom/organisations/_suggested.html.haml +1 -1
  165. data/app/views/droom/organisations/index.html.haml +11 -11
  166. data/app/views/droom/panels/_admin.html.haml +2 -2
  167. data/app/views/droom/panels/_configuration.html.haml +28 -0
  168. data/app/views/droom/panels/_devices.html.haml +2 -4
  169. data/app/views/droom/panels/_search.html.haml +9 -0
  170. data/app/views/droom/permissions/_action_menu.html.haml +10 -0
  171. data/app/views/droom/permissions/_created.html.haml +3 -0
  172. data/app/views/droom/permissions/_form.html.haml +17 -0
  173. data/app/views/droom/permissions/edit.html.haml +7 -0
  174. data/app/views/droom/permissions/new.html.haml +8 -0
  175. data/app/views/droom/permissions/show.html.haml +4 -0
  176. data/app/views/droom/preferences/_checkbox.html.haml +4 -2
  177. data/app/views/droom/scraps/_caption.html.haml +0 -2
  178. data/app/views/droom/scraps/_credit.html.haml +1 -1
  179. data/app/views/droom/scraps/_form.html.haml +68 -69
  180. data/app/views/droom/scraps/_preload.html.haml +4 -0
  181. data/app/views/droom/scraps/_stream.html.haml +11 -3
  182. data/app/views/droom/scraps/_thumb.html.haml +33 -1
  183. data/app/views/droom/scraps/edit.html.haml +6 -1
  184. data/app/views/droom/scraps/full/_document.html.haml +4 -3
  185. data/app/views/droom/scraps/full/_event.html.haml +17 -16
  186. data/app/views/droom/scraps/full/_image.html.haml +1 -2
  187. data/app/views/droom/scraps/full/_link.html.haml +4 -3
  188. data/app/views/droom/scraps/full/_quote.html.haml +4 -7
  189. data/app/views/droom/scraps/full/_text.html.haml +3 -2
  190. data/app/views/droom/scraps/full/_video.html.haml +2 -2
  191. data/app/views/droom/scraps/new.html.haml +6 -1
  192. data/app/views/droom/scraps/show.html.haml +4 -3
  193. data/app/views/droom/services/_action_menu.html.haml +12 -0
  194. data/app/views/droom/services/_created.html.haml +3 -0
  195. data/app/views/droom/services/_form.html.haml +18 -0
  196. data/app/views/droom/services/_services.html.haml +44 -0
  197. data/app/views/droom/services/edit.html.haml +7 -0
  198. data/app/views/droom/services/index.html.haml +5 -0
  199. data/app/views/droom/{people → services}/new.html.haml +4 -3
  200. data/app/views/droom/services/show.html.haml +4 -0
  201. data/app/views/droom/shared/_controls.html.haml +2 -2
  202. data/app/views/droom/shared/_navigation.html.haml +2 -2
  203. data/app/views/droom/shared/_toolbar.html.haml +1 -1
  204. data/app/views/droom/suggestions/index.html.haml +1 -0
  205. data/app/views/droom/users/_action_menu.html.haml +21 -0
  206. data/app/views/droom/users/_suggested.html.haml +14 -0
  207. data/app/views/droom/users/_user.html.haml +2 -2
  208. data/app/views/droom/users/_users.html.haml +7 -0
  209. data/app/views/droom/users/_users_table.html.haml +21 -22
  210. data/app/views/droom/users/admin.html.haml +10 -0
  211. data/app/views/droom/users/confirmations/_after_confirmation.html.haml +2 -0
  212. data/app/views/droom/users/confirmations/_password_required.html.haml +30 -0
  213. data/app/views/droom/users/confirmations/failure.en.html.haml +22 -0
  214. data/app/views/droom/users/confirmations/show.html.haml +13 -0
  215. data/app/views/droom/users/edit.html.haml +2 -10
  216. data/app/views/droom/users/edit/_details.html.haml +13 -0
  217. data/app/views/droom/users/edit/_memberships.html.haml +13 -0
  218. data/app/views/droom/users/edit/_password_fields.html.haml +16 -0
  219. data/app/views/droom/users/edit/_preferences.html.haml +37 -0
  220. data/app/views/droom/users/edit/_profile.html.haml +26 -0
  221. data/app/views/droom/users/edit/_user_fields.html.haml +66 -0
  222. data/app/views/droom/users/index.html.haml +6 -32
  223. data/app/views/droom/users/new.html.haml +3 -0
  224. data/app/views/droom/users/passwords/completed.html.haml +13 -0
  225. data/app/views/{devise → droom/users}/passwords/edit.html.haml +10 -4
  226. data/app/views/{devise → droom/users}/passwords/new.html.haml +4 -3
  227. data/app/views/droom/users/passwords/show.html.haml +6 -0
  228. data/app/views/droom/users/show.html.haml +2 -0
  229. data/app/views/droom/users/show/_listed.html.haml +46 -0
  230. data/app/views/droom/users/show/_profile.html.haml +33 -0
  231. data/app/views/droom/users/show/_tabled.html.haml +28 -0
  232. data/config/initializers/chronic.rb +2 -0
  233. data/config/initializers/devise.rb +35 -2
  234. data/config/initializers/paperclip.rb +11 -6
  235. data/config/locales/devise.en.yml +1 -1
  236. data/config/locales/en.yml +198 -103
  237. data/config/routes.rb +37 -45
  238. data/db/migrate/20130207123614_stream.rb +5 -1
  239. data/db/migrate/20130225095328_create_droom_calendars.rb +0 -1
  240. data/db/migrate/20130627065459_permissions.rb +34 -0
  241. data/db/migrate/20130627071938_users_take_over.rb +38 -0
  242. data/db/migrate/20130627073759_user_properties.rb +34 -0
  243. data/db/migrate/20130627080021_no_more_people.rb +11 -0
  244. data/db/migrate/20130701122935_directory_groups.rb +8 -0
  245. data/db/migrate/20130701123152_remove_old_access_control.rb +16 -0
  246. data/db/migrate/20130729102857_more_scrap_data.rb +12 -0
  247. data/db/migrate/20130904080340_international_names.rb +17 -0
  248. data/db/migrate/20130904080341_honours.rb +5 -0
  249. data/db/migrate/20131124082222_simple_addresses.rb +25 -0
  250. data/db/migrate/20131201093351_event_time_zone.rb +5 -0
  251. data/db/migrate/20131217060311_session_revocable.rb +5 -0
  252. data/db/migrate/20131219180311_event_types.rb +15 -0
  253. data/lib/devise/strategies/cookie_authenticatable.rb +46 -0
  254. data/lib/droom.rb +48 -33
  255. data/lib/droom/auth_cookie.rb +82 -0
  256. data/lib/droom/cropper.rb +24 -0
  257. data/lib/droom/engine.rb +20 -2
  258. data/lib/droom/folders.rb +6 -6
  259. data/lib/droom/lazy_hash.rb +11 -1
  260. data/lib/droom/monkeys.rb +6 -6
  261. data/lib/droom/taggability.rb +1 -1
  262. data/lib/droom/validators.rb +1 -1
  263. data/lib/droom/version.rb +1 -1
  264. data/lib/generators/droom/install/templates/droom_initializer.rb +10 -2
  265. data/lib/paperclip/geometry_transformation.rb +80 -0
  266. data/lib/paperclip/validators/attachment_height_validator.rb +89 -0
  267. data/lib/paperclip/validators/attachment_width_validator.rb +89 -0
  268. data/lib/paperclip_processors/offset_thumbnail.rb +85 -0
  269. data/spec/dummy/config/application.rb +0 -7
  270. data/spec/dummy/config/environments/development.rb +1 -10
  271. data/spec/dummy/config/environments/production.rb +1 -0
  272. data/spec/dummy/config/environments/test.rb +2 -7
  273. data/spec/dummy/db/migrate/20130130120631_folder_ancestry_to_parents.droom.rb +1 -1
  274. data/spec/dummy/db/migrate/20130724124758_dropbox_tokens.droom.rb +10 -0
  275. data/spec/dummy/db/migrate/20130724124759_preference_uuids.droom.rb +6 -0
  276. data/spec/dummy/db/migrate/20130724124760_stream.droom.rb +14 -0
  277. data/spec/dummy/db/migrate/20130724124761_access_token_secret.droom.rb +6 -0
  278. data/spec/dummy/db/migrate/20130724124762_person_image.droom.rb +10 -0
  279. data/spec/dummy/db/migrate/20130724124763_devise_confirmable.droom.rb +14 -0
  280. data/spec/dummy/db/migrate/20130724124764_create_droom_calendars.droom.rb +20 -0
  281. data/spec/dummy/db/migrate/20130724124765_give_scraps_document.droom.rb +6 -0
  282. data/spec/dummy/db/migrate/20130724124766_privateness.droom.rb +10 -0
  283. data/spec/dummy/db/migrate/20130724124767_store_metadata.droom.rb +6 -0
  284. data/spec/dummy/db/migrate/20130724124768_folders_nicely.droom.rb +7 -0
  285. data/spec/dummy/db/migrate/20130724124769_niceties.droom.rb +8 -0
  286. data/spec/dummy/db/migrate/20130724124770_create_droom_dropbox_documents.droom.rb +12 -0
  287. data/spec/dummy/db/migrate/20130724124771_dropbox_documents_changed.droom.rb +6 -0
  288. data/spec/dummy/db/migrate/20130724124772_user_titles.droom.rb +6 -0
  289. data/spec/dummy/db/migrate/20130724124773_permissions.droom.rb +35 -0
  290. data/spec/dummy/db/migrate/20130724124774_users_take_over.droom.rb +39 -0
  291. data/spec/dummy/db/migrate/20130724124775_user_properties.droom.rb +35 -0
  292. data/spec/dummy/db/migrate/20130724124776_no_more_people.droom.rb +12 -0
  293. data/spec/dummy/db/migrate/20130724124777_directory_groups.droom.rb +9 -0
  294. data/spec/dummy/db/migrate/20130724124778_remove_old_access_control.droom.rb +17 -0
  295. data/spec/dummy/db/schema.rb +217 -140
  296. data/spec/factories/documents.rb +0 -5
  297. data/spec/factories/events.rb +0 -9
  298. data/spec/factories/user.rb +4 -2
  299. data/spec/lib/droom/folders_spec.rb +8 -8
  300. data/spec/models/droom/document_spec.rb +6 -43
  301. data/spec/models/droom/event_spec.rb +0 -34
  302. data/spec/models/droom/mailing_list_membership_spec.rb +4 -4
  303. data/spec/models/droom/membership_spec.rb +2 -2
  304. data/spec/spec_helper.rb +1 -10
  305. metadata +322 -282
  306. data/app/assets/images/droom/Untitled-1.png +0 -0
  307. data/app/assets/images/droom/asterisk.png +0 -0
  308. data/app/assets/images/droom/smallmanage.png +0 -0
  309. data/app/assets/javascripts/droom/lib/kalendae.js +0 -1692
  310. data/app/assets/stylesheets/lib/_kalendae.css.sass +0 -139
  311. data/app/controllers/droom/pages_controller.rb +0 -61
  312. data/app/controllers/droom/people_controller.rb +0 -111
  313. data/app/models/droom/document_attachment.rb +0 -37
  314. data/app/models/droom/page.rb +0 -26
  315. data/app/models/droom/person.rb +0 -309
  316. data/app/models/droom/recurrence_rule.rb +0 -82
  317. data/app/models/droom/user_action_observer.rb +0 -12
  318. data/app/views/devise/confirmations/failure.en.html.haml +0 -20
  319. data/app/views/devise/confirmations/show.html.haml +0 -19
  320. data/app/views/droom/confirmations/show.html.haml +0 -40
  321. data/app/views/droom/documents/_document_line.html.haml +0 -2
  322. data/app/views/droom/events/_created.html.haml +0 -2
  323. data/app/views/droom/folders/_attachments.html.haml +0 -3
  324. data/app/views/droom/pages/_contents.html.haml +0 -10
  325. data/app/views/droom/pages/_form.html.haml +0 -36
  326. data/app/views/droom/pages/_full_page.html.haml +0 -17
  327. data/app/views/droom/pages/_page.html.haml +0 -5
  328. data/app/views/droom/pages/_pages.html.haml +0 -2
  329. data/app/views/droom/pages/admin.html.haml +0 -24
  330. data/app/views/droom/pages/edit.html.haml +0 -1
  331. data/app/views/droom/pages/index.html.haml +0 -10
  332. data/app/views/droom/pages/new.html.haml +0 -4
  333. data/app/views/droom/pages/show.html.haml +0 -5
  334. data/app/views/droom/panels/_account.html.haml +0 -16
  335. data/app/views/droom/panels/_readers.html.haml +0 -18
  336. data/app/views/droom/panels/_suggestions.html.haml +0 -15
  337. data/app/views/droom/people/_action_menu.html.haml +0 -22
  338. data/app/views/droom/people/_created.html.haml +0 -6
  339. data/app/views/droom/people/_form.html.haml +0 -66
  340. data/app/views/droom/people/_listing.html.haml +0 -19
  341. data/app/views/droom/people/_memberships.html.haml +0 -24
  342. data/app/views/droom/people/_people.html.haml +0 -5
  343. data/app/views/droom/people/_person.html.haml +0 -32
  344. data/app/views/droom/people/_search_result.html.haml +0 -14
  345. data/app/views/droom/people/_suggested.html.haml +0 -14
  346. data/app/views/droom/people/edit.html.haml +0 -9
  347. data/app/views/droom/people/index.html.haml +0 -8
  348. data/app/views/droom/people/show.html.haml +0 -31
  349. data/app/views/droom/scraps/_heading.html.haml +0 -16
  350. data/app/views/droom/scraps/thumbs/_document.html.haml +0 -9
  351. data/app/views/droom/scraps/thumbs/_event.html.haml +0 -17
  352. data/app/views/droom/scraps/thumbs/_image.html.haml +0 -6
  353. data/app/views/droom/scraps/thumbs/_link.html.haml +0 -10
  354. data/app/views/droom/scraps/thumbs/_quote.html.haml +0 -8
  355. data/app/views/droom/scraps/thumbs/_text.html.haml +0 -8
  356. data/app/views/droom/scraps/thumbs/_video.html.haml +0 -6
  357. data/app/views/droom/users/_extra_columns_header.html.haml +0 -0
  358. data/app/views/droom/users/_extra_columns_note.html.haml +0 -0
  359. data/app/views/droom/users/_form.html.haml +0 -14
  360. data/app/views/droom/users/_password_fields.html.haml +0 -15
  361. data/app/views/droom/users/_user_or_person.html.haml +0 -72
  362. data/app/views/kaminari/_paginator.html.haml +0 -16
  363. data/lib/droom/dav_resource.rb +0 -36
  364. data/lib/droom/model_helpers.rb +0 -25
  365. data/lib/droom/routing.rb +0 -13
  366. data/spec/datasets/calendar_events_dataset.rb +0 -44
  367. data/spec/datasets/calendar_pages_dataset.rb +0 -8
  368. data/spec/datasets/calendar_sites_dataset.rb +0 -6
  369. data/spec/datasets/calendars_dataset.rb +0 -34
  370. data/spec/datasets/documents_dataset.rb +0 -28
  371. data/spec/datasets/recurrence_dataset.rb +0 -7
  372. data/spec/dummy/config/sunspot.yml +0 -17
  373. data/spec/dummy/log/dav.log +0 -1
  374. data/spec/dummy/log/development.log +0 -2
  375. data/spec/dummy/log/test.log +0 -1
  376. data/spec/dummy/solr/conf/admin-extra.html +0 -31
  377. data/spec/dummy/solr/conf/elevate.xml +0 -36
  378. data/spec/dummy/solr/conf/mapping-ISOLatin1Accent.txt +0 -246
  379. data/spec/dummy/solr/conf/protwords.txt +0 -21
  380. data/spec/dummy/solr/conf/schema.xml +0 -238
  381. data/spec/dummy/solr/conf/scripts.conf +0 -24
  382. data/spec/dummy/solr/conf/solrconfig.xml +0 -934
  383. data/spec/dummy/solr/conf/spellings.txt +0 -2
  384. data/spec/dummy/solr/conf/stopwords.txt +0 -58
  385. data/spec/dummy/solr/conf/synonyms.txt +0 -31
  386. data/spec/dummy/solr/data/development/index/segments.gen +0 -0
  387. data/spec/dummy/solr/data/development/index/segments_1 +0 -0
  388. data/spec/dummy/solr/data/development/spellchecker/segments.gen +0 -0
  389. data/spec/dummy/solr/data/development/spellchecker/segments_1 +0 -0
  390. data/spec/dummy/solr/data/test/index/segments.gen +0 -0
  391. data/spec/dummy/solr/data/test/index/segments_3nu +0 -0
  392. data/spec/dummy/solr/data/test/spellchecker/segments.gen +0 -0
  393. data/spec/dummy/solr/data/test/spellchecker/segments_1 +0 -0
  394. data/spec/factories/recurrence_rules.rb +0 -25
  395. data/spec/models/droom/person_spec.rb +0 -72
  396. data/spec/models/droom/recurrence_rule_spec.rb +0 -81
@@ -1,9 +1,12 @@
1
1
  module Droom
2
2
  class Organisation < ActiveRecord::Base
3
- attr_accessible :name, :description, :created_by, :owner, :url
4
- has_many :people
5
- belongs_to :owner, :class_name => 'Droom::Person'
3
+ has_many :users
4
+ belongs_to :owner, :class_name => 'Droom::User'
6
5
  belongs_to :created_by, :class_name => 'Droom::User'
6
+
7
+ scope :added_since, -> date { where("created_at > ?", date)}
8
+
9
+ default_scope -> {order("name ASC")}
7
10
 
8
11
  def self.for_selection
9
12
  organisations = self.order("name asc").map{|f| [f.name, f.id] }
@@ -0,0 +1,19 @@
1
+ require 'acts_as_list'
2
+
3
+ module Droom
4
+ class Permission < ActiveRecord::Base
5
+ belongs_to :service
6
+ has_many :group_permissions, :dependent => :destroy
7
+ has_many :user_permissions, :dependent => :destroy
8
+ acts_as_list :scope => :service_id
9
+ before_save :set_slug
10
+
11
+ validates :slug, :uniqueness => true
12
+
13
+ protected
14
+
15
+ def set_slug
16
+ self.slug = [service.slug, self.name].join('.')
17
+ end
18
+ end
19
+ end
@@ -1,31 +1,19 @@
1
1
  module Droom
2
2
  class PersonalFolder < ActiveRecord::Base
3
- belongs_to :person
3
+ belongs_to :user
4
4
  belongs_to :folder
5
5
 
6
- def copy_to_dropbox
7
-
8
- end
6
+ scope :of_folder, -> folder {
7
+ where(["folder_id = ?", folder.id])
8
+ }
9
+
10
+ scope :for_user, -> user {
11
+ where(["user_id = ?", user.id])
12
+ }
9
13
 
10
- def copy_to_dav
14
+ def copy_to_dropbox
11
15
 
12
16
  end
13
17
 
14
- #todo: lifted from Person and awaiting translation
15
- #
16
- # def create_and_update_dav_directories
17
- # document_links.each do |dl|
18
- # p "-> creating DAV directory #{dl.slug}"
19
- # create_dav_directory(dl.slug)
20
- # end
21
- # end
22
- #
23
- # def create_dav_directory(name)
24
- # FileUtils.mkdir_p(Rails.root + "#{Droom.dav_root}/#{self.id}/#{name}")
25
- # end
26
- #
27
-
28
- # association helpers give us more readable logic elsewhere.
29
-
30
18
  end
31
19
  end
@@ -2,9 +2,8 @@
2
2
 
3
3
  module Droom
4
4
  class Preference < ActiveRecord::Base
5
- attr_accessible :key, :value, :uuid
6
5
  belongs_to :created_by, :class_name => "Droom::User"
7
- validates :key, :presence => true, :uniqueness => true
6
+ validates :key, :presence => true, :uniqueness => {:scope => :created_by_id}
8
7
 
9
8
  def set(value)
10
9
  if boolean?
@@ -1,47 +1,48 @@
1
1
  module Droom
2
2
  class Scrap < ActiveRecord::Base
3
3
  belongs_to :created_by, :class_name => "Droom::User"
4
- belongs_to :event, :class_name => "Droom::Event"
5
- accepts_nested_attributes_for :event
6
4
 
5
+ belongs_to :event, :class_name => "Droom::Event", :dependent => :destroy
6
+ accepts_nested_attributes_for :event
7
7
  belongs_to :document, :class_name => "Droom::Document", :dependent => :destroy
8
8
  accepts_nested_attributes_for :document
9
9
 
10
- has_upload :image,
11
- :geometry => "580x326#",
12
- :styles => {
13
- :icon => "32x18#",
14
- :thumb => "160x90#",
15
- :precrop => "1200x1200^"
16
- }
10
+ has_attached_file :image,
11
+ :styles => {
12
+ :stream => "1280x1280>",
13
+ :popup => "1280x1280>",
14
+ :icon => "32x32#",
15
+ :thumb => "130x73#"
16
+ }
17
17
 
18
- attr_accessible :name, :body, :image, :description, :scraptype, :note, :created_by, :event, :event_attributes, :document, :document_attributes
19
18
  before_save :get_youtube_thumbnail
19
+ before_validation :name_associates
20
20
 
21
- scope :by_date, order("droom_scraps.created_at DESC")
21
+ scope :by_date, -> { order("droom_scraps.created_at DESC") }
22
22
 
23
- scope :later_than, lambda { |scrap|
24
- where(["created_at > ?", scrap.created_at]).order("droom_scraps.created_at ASC")
25
- }
26
-
27
- scope :earlier_than, lambda { |scrap|
28
- where(["created_at < ?", scrap.created_at]).order("droom_scraps.created_at DESC")
29
- }
23
+ scope :later_than, -> scrap { where(["created_at > ?", scrap.created_at]).order("droom_scraps.created_at ASC") }
30
24
 
31
- scope :matching, lambda { |fragment|
25
+ scope :earlier_than, -> scrap { where(["created_at < ?", scrap.created_at]).order("droom_scraps.created_at DESC") }
26
+
27
+ scope :matching, -> fragment {
32
28
  fragment = "%#{fragment}%"
33
29
  where('droom_scraps.name LIKE :f OR droom_scraps.body LIKE :f OR droom_scraps.note LIKE :f', :f => fragment)
34
30
  }
35
31
 
36
- scope :visible_to, lambda { |person|
37
- where("1=1")
32
+ scope :visible_to, -> user { where("1=1") }
33
+
34
+ scope :with_future_or_current_event, -> {
35
+ joins(:event)
36
+ .where(["droom_scraps.scraptype = 'event' AND (droom_events.finish > :now) OR (droom_events.finish IS NULL AND droom_events.start > :now)", :now => Time.zone.now])
37
+ .order('droom_events.start ASC')
38
38
  }
39
39
 
40
40
  Droom.scrap_types.each do |t|
41
- define_method(:"#{t}?") { scraptype == t.to_s }
42
- scope t.pluralize.to_sym, where(["scraptype == ?", t])
41
+ scope t.pluralize.to_sym, -> { where(:scraptype => t.to_s) }
43
42
  end
44
43
 
44
+ default_scope -> { includes(:event, :document) }
45
+
45
46
  def wordiness
46
47
  if body.length < 48
47
48
  'word'
@@ -90,11 +91,15 @@ module Droom
90
91
  protected
91
92
 
92
93
  def get_youtube_thumbnail
93
- # youtube id is held in the 'body' column.
94
- if scraptype == "video" && body?
95
- self.image = URI("http://img.youtube.com/vi/#{body}/0.jpg")
94
+ if scraptype == "video" && youtube_id?
95
+ self.image = URI("http://img.youtube.com/vi/#{youtube_id}/0.jpg")
96
96
  end
97
97
  end
98
98
 
99
+ def name_associates
100
+ event.name = name if event
101
+ document.name = name if document
102
+ end
103
+
99
104
  end
100
105
  end
@@ -0,0 +1,31 @@
1
+ module Droom
2
+ class Service < ActiveRecord::Base
3
+ has_many :permissions, -> {order(:position)}, :dependent => :destroy
4
+ before_save :set_slug
5
+ after_create :create_basic_permissions
6
+ after_save :update_permissions
7
+ validates :slug, :uniqueness => true
8
+
9
+ def self.for_selection
10
+ self.order("name asc").map{|f| [f.name, f.id] }
11
+ end
12
+
13
+ protected
14
+
15
+ def set_slug
16
+ self.slug ||= name.parameterize
17
+ end
18
+
19
+ def create_basic_permissions
20
+ permissions.create(:name => 'login')
21
+ permissions.create(:name => 'admin')
22
+ end
23
+
24
+ def update_permissions
25
+ permissions.all.each do |p|
26
+ p.send :set_slug
27
+ p.save
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,9 +1,9 @@
1
1
  # Tags are simple one-word descriptors. There is no hierarchy or other structure among them.
2
- # They are strictly catalogue data, used to locate people but not treated as part of the person record.
2
+ # They are strictly catalogue data, used to locate people but not treated as part of the user record.
3
3
 
4
4
  module Droom
5
5
  class Tag < ActiveRecord::Base
6
- # They are attached to people through many-to-many taggings.
6
+ # They are attached to people and other things through many-to-many taggings.
7
7
  #
8
8
  has_many :taggings
9
9
  has_many :taggees, :through => :taggings
@@ -14,24 +14,25 @@ module Droom
14
14
  # There is a tag-suggesting mechanism in the front end to encourage tag reuse and consistency.
15
15
  # It rests on this simple scope, which returns all tags matching a given string fragment.
16
16
  #
17
- scope :matching, lambda { |fragment|
17
+ scope :matching, -> fragment {
18
18
  fragment = "%#{fragment}%"
19
19
  where('tags.name like ?', fragment)
20
20
  }
21
21
 
22
22
  # The public-facing search engine is faceted and relies on a similar but broader suggestion mechanism
23
23
  # that offers both tags and institutions. In that situation we only want to display tags that will give
24
- # give results, so we limit the suggestions to only those tags that have been applied to a person.
24
+ # give results, so we limit the suggestions to only those tags that have been applied to a user.
25
25
  #
26
- scope :in_use,
26
+ scope :in_use, -> {
27
27
  joins("INNER JOIN taggings ON taggings.tag_id = tags.id")
28
28
  .group('tags.id')
29
29
  .having('count(taggings.tag_id) > 0')
30
+ }
30
31
 
31
32
  # This returns a list of all the tags attached to any of a given set of objects.
32
33
  # In future it will support cloud-weighting.
33
34
  #
34
- scope :attached_to_any_of, lambda {|these|
35
+ scope :attached_to_any_of, -> these {
35
36
  these = [these].flatten
36
37
  type = these.first.class.to_s
37
38
  placeholders = these.map{"?"}.join(',')
@@ -72,9 +73,9 @@ module Droom
72
73
  #
73
74
  def self.for(name, or_create=true)
74
75
  if or_create
75
- find_or_create_by_name(name)
76
+ where(name: name).first_or_create
76
77
  else
77
- find_by_name(name)
78
+ find_by(name: name)
78
79
  end
79
80
  end
80
81
 
@@ -86,7 +87,7 @@ module Droom
86
87
  # display logic can be found in the [application_helper](../controllers/application_helper.html).
87
88
  #
88
89
  attr_accessor :cloud_size
89
- scope :with_usage_count, lambda { |limit|
90
+ scope :with_usage_count, -> limit {
90
91
  select("tags.*, count(tt.id) AS weight").joins("INNER JOIN taggings as tt ON tt.tag_id = tags.id").group("tt.tag_id").order("weight DESC").limit(limit)
91
92
  }
92
93
 
@@ -1,4 +1,4 @@
1
- # Taggings are the many to many links that associated tags with people. There isn't much to see here.
1
+ # Taggings are the many to many links that associated tags with people and other things. There isn't much to see here.
2
2
 
3
3
  module Droom
4
4
  class Tagging < ActiveRecord::Base
@@ -1,87 +1,108 @@
1
1
  module Droom
2
2
  class User < ActiveRecord::Base
3
+ validates :family_name, :presence => true
4
+ validates :given_name, :presence => true
5
+ validates :email, :uniqueness => true, :presence => true
6
+ validates :uid, :uniqueness => true, :presence => true
3
7
 
4
- attr_accessible :name, :forename, :email, :password, :password_confirmation, :admin, :newly_activated, :update_person_email, :preferences_attributes, :confirm, :old_id, :remove_person
5
- has_one :person
6
- has_many :dropbox_tokens, :foreign_key => "created_by_id"
7
8
  has_many :preferences, :foreign_key => "created_by_id"
8
9
  accepts_nested_attributes_for :preferences, :allow_destroy => true
9
-
10
- validates :email, :uniqueness => true, :presence => true
11
- validates_format_of :email, :with => /@/
12
- validates :name, :presence => true
13
- # validates :password, :presence => true, :length => { :minimum => 6 }, :confirmation => true, :if => :password_required?
14
-
15
- receives_messages# :groups => [:unconfirmed, :personed, :administrative]
16
-
10
+
11
+ ## Authentication
12
+ #
17
13
  devise :database_authenticatable,
18
- :encryptable,
14
+ :cookie_authenticatable,
19
15
  :recoverable,
20
- :rememberable,
21
16
  :trackable,
22
17
  :confirmable,
23
- :token_authenticatable,
24
- :encryptor => :sha512
25
-
26
- before_create :ensure_authentication_token # provided by devise
27
- before_create :ensure_confirmation_token # provided by devise
28
-
29
- attr_accessor :newly_activated, :update_person_email, :confirm, :remove_person
30
-
31
-
32
- scope :unconfirmed, where("confirmed_at IS NULL")
33
- scope :administrative, where(:admin => true)
34
-
35
- scope :personed, select("droom_users.*")
36
- .joins("INNER JOIN droom_people as dp ON dp.user_id = droom_users.id")
37
-
38
- scope :unpersoned, select("droom_users.*")
39
- .joins("LEFT OUTER JOIN droom_people as dp ON dp.user_id = droom_users.id")
40
- .having("count(dp.id) = 0")
18
+ :rememberable,
19
+ :reconfirmable => false
41
20
 
42
- scope :with_person_in_group, lambda { |group|
43
- group = group.id if group.is_a? Droom::Group
44
- select("droom_users.*")
45
- .joins("INNER JOIN droom_people as dp ON dp.user_id = droom_users.id")
46
- .joins("INNER JOIN droom_memberships as dm ON dp.id = dm.person_id")
47
- .where("dm.group_id" => group)
48
- }
21
+ before_validation :ensure_uid!
22
+ before_save :ensure_authentication_token
23
+ after_save :send_confirmation_if_directed
49
24
 
50
- # Messaging groups are normally scopes passed through when receives_messages is called,
51
- # but anything will work that can be called on the class and return a set of instances.
52
- # Here we're overriding the getter so as to offer sending by group membership as well as
53
- # the usual scoping.
54
- #
55
- def self.messaging_groups
56
- unless @messaging_groups
57
- @messaging_groups = {
58
- :unconfirmed => lambda { Droom::User.unconfirmed},
59
- :personed => lambda { Droom::User.personed },
60
- :administrative => lambda { Droom::User.administrative }
61
- }
62
- Droom::Group.all.each do |group|
63
- @messaging_groups[group.slug.to_sym] = lambda { Droom::User.with_person_in_group(group.id) }
64
- end
65
- end
66
- @messaging_groups
25
+ # People are often invited into the system in batches or after offline contact.
26
+ # set user.defer_confirmation to a true or call user.defer_confirmation! +before saving+
27
+ # if you want to create a user account without sending out any messages yet.
28
+ #
29
+ # When you do want to invite that person, call user.resend_confirmation_token or
30
+ # set the send_confirmation flag on a save.
31
+ #
32
+ attr_accessor :defer_confirmation, :send_confirmation
33
+
34
+ def defer_confirmation!
35
+ self.defer_confirmation = true
67
36
  end
68
37
 
69
- def confirm=(confirmed)
70
- confirm! if confirmed
38
+ def defer_confirmation?
39
+ !!self.defer_confirmation
71
40
  end
72
-
73
- def remove_person=(boolean)
74
- if boolean
75
- p = Droom::Person.find(person.id)
76
- p.user = nil
77
- p.save!
78
- end
41
+
42
+ # Called after save by our own late-confirmation mechanism.
43
+ # If the send_confirmation flag has been set, we confirm.
44
+ #
45
+ def send_confirmation?
46
+ !!self.send_confirmation
47
+ end
48
+
49
+ # Called on create by devise's immediate confirmation mechanism.
50
+ # If the defer_confirmation flag has been set, we postpone.
51
+ #
52
+ def send_confirmation_notification?
53
+ super && !defer_confirmation?
54
+ end
55
+
56
+ def active_for_authentication?
57
+ true
79
58
  end
80
59
 
81
- # Password is not required on creation, contrary to the devise defaults.
82
60
  def password_required?
83
61
  confirmed? && (!password.blank?)
84
62
  end
63
+
64
+ def password_set?
65
+ encrypted_password?
66
+ end
67
+
68
+ ## Session ID
69
+ #
70
+ # Allows us to invalidate a session by remote control if someone signs out on a satellite site.
71
+
72
+ def reset_session_id!
73
+ token = generate_authentication_token
74
+ self.update_column(:session_id, token)
75
+ token
76
+ end
77
+
78
+ def clear_session_id!
79
+ self.update_column(:session_id, "")
80
+ end
81
+
82
+ ## Auth tokens
83
+ #
84
+ # Are no longer native to devise but we use them for domain-cookie auth.
85
+
86
+ def authenticate_token(token)
87
+ Devise.secure_compare(self.authentication_token, token)
88
+ end
89
+
90
+ def reset_authentication_token!
91
+ $cache.delete "/api/authenticate/#{authentication_token}" if $cache && authentication_token
92
+ token = generate_authentication_token
93
+ self.update_column(:authentication_token, token)
94
+ token
95
+ end
96
+
97
+ def ensure_authentication_token
98
+ if authentication_token.blank?
99
+ self.authentication_token = generate_authentication_token
100
+ end
101
+ end
102
+
103
+ def confirmed=(value)
104
+ self.confirmed_at = Time.now if value.present? and value != "false"
105
+ end
85
106
 
86
107
  def password_match?
87
108
  self.errors[:password] << "can't be blank" if password.blank?
@@ -90,48 +111,155 @@ module Droom
90
111
  password == password_confirmation && !password.blank?
91
112
  end
92
113
 
93
- def is_person?(person)
94
- person == self.person
95
- end
114
+ # Our old user accounts store passwords as salted sha512 digests. Current best practice uses BCrypt
115
+ # so we migrate user accounts across in this rescue block whenever we hear BCrypt grumbling about the old hash.
116
+
117
+ def valid_password?(password)
118
+ begin
119
+ super(password)
120
+ rescue BCrypt::Errors::InvalidHash
121
+ Rails.logger.warn "...trying sha512 on password input"
122
+ stretches = 10
123
+ salt = self.password_salt
124
+ pepper = nil
125
+ old_digest = Devise::Encryptable::Encryptors::Sha512.digest(password, stretches, salt, pepper)
126
+ if old_digest == self.encrypted_password
127
+ self.password = password
128
+ self.save
129
+ return true
130
+ else
131
+ # Doesn't match the old format either: password is just wrong.
132
+ return false
133
+ end
134
+ end
135
+ end
136
+
137
+ scope :unconfirmed, -> { where("confirmed_at IS NULL") }
138
+ scope :administrative, -> { where(:admin => true) }
139
+ scope :this_month, -> { where("created_at > ?", Time.now - 1.month) }
140
+ scope :this_week, -> { where("created_at > ?", Time.now - 1.week) }
141
+
142
+ scope :in_name_order, -> {
143
+ order("family_name ASC, given_name ASC")
144
+ }
96
145
 
97
- def organisation
98
- person.organisation if person
146
+ def as_json_for_coca(options={})
147
+ ensure_uid!
148
+ ensure_authentication_token
149
+ {
150
+ uid: uid,
151
+ authentication_token: authentication_token,
152
+ title: title,
153
+ given_name: given_name,
154
+ family_name: family_name,
155
+ chinese_name: chinese_name,
156
+ email: email,
157
+ permissions: permission_codes.join(','),
158
+ image: thumbnail
159
+ }
99
160
  end
100
161
 
101
- # Current user is pushed into here to make it available in models
102
- # such as the UserActionObserver that sets ownership before save.
162
+ ## Organisation affiliation
103
163
  #
104
- def self.current
105
- Thread.current[:user]
164
+ belongs_to :organisation
165
+ has_many :organisations, :foreign_key => :owner_id
166
+
167
+ ## Group memberships
168
+ #
169
+ has_many :memberships, :dependent => :destroy
170
+ has_many :groups, :through => :memberships
171
+ has_many :mailing_list_memberships, :through => :memberships
172
+
173
+ scope :in_any_directory_group, -> {
174
+ joins(:groups).where(droom_groups: {directory: true}).group("droom_users.id")
175
+ }
176
+
177
+ def admit_to(groups)
178
+ groups = [groups].flatten
179
+ groups.each do |group|
180
+ memberships.where(group_id: group.id).first_or_create
181
+ end
106
182
  end
107
- def self.current=(user)
108
- Thread.current[:user] = user
183
+
184
+ def expel_from(group)
185
+ memberships.of_group(group).destroy_all
109
186
  end
110
187
 
111
- # Personal DAV repository is accessed via a DAV4rack endpoint but we have to take care of its creation and population.
188
+ def member_of?(group)
189
+ group && memberships.of_group(group).any?
190
+ end
191
+
192
+ def membership_of(group)
193
+ memberships.find_by(group_id: group.id)
194
+ end
195
+
196
+ scope :in_group, -> group {
197
+ group = group.id if group.is_a? Droom::Group
198
+ select("droom_users.*")
199
+ .joins("INNER JOIN droom_memberships as dm ON droom_users.id = dm.user_id")
200
+ .where("dm.group_id" => group)
201
+ }
202
+
203
+ ## Event invitations
112
204
  #
113
- def dav_root
114
- dav_path = Rails.root + "webdav/#{id}"
115
- Dir.mkdir(dav_path, 0600) unless File.exist?(dav_path)
116
- dav_path
205
+ has_many :invitations, :dependent => :destroy
206
+ has_many :events, :through => :invitations
207
+
208
+ def invite_to(event)
209
+ invitations.where(event_id: event.id).first_or_create if event
117
210
  end
118
-
119
- def full_name
120
- [forename, name].compact.join(' ').strip
211
+
212
+ def uninvite_from(event)
213
+ invitations.to_event(event).destroy_all
121
214
  end
122
215
 
123
- def formal_name
124
- [title, forename, name].compact.join(' ').strip
216
+ def invited_to?(event)
217
+ event && !!invitation_to(event)
125
218
  end
126
219
 
127
- def informal_name
128
- if Droom.use_forenames
129
- forename
130
- else
131
- name
132
- end
220
+ def invitation_to(event)
221
+ invitations.to_event(event).first
133
222
  end
223
+
224
+ scope :personally_invited_to_event, -> event {
225
+ joins('LEFT OUTER JOIN droom_invitations on droom_users.id = droom_invitations.user_id').where('droom_invitations.group_invitation_id is null AND droom_invitations.event_id = ?', event.id)
226
+ }
227
+
228
+ ## Folder permissions
229
+ #
230
+ # To simplify the business of showing and listing documents, we have adopted the convention that all
231
+ # documents live in a folder. User accounts have links to those folders through the very thin
232
+ # PersonalFolder joining class, and at the view level we only ever show folder and subfolder lists.
233
+ # The only place we ever need a list of all the documents visible to this person is when searching, and
234
+ # for that we use the Document.visible_to scope, usually by way of the #documents method defined below.
235
+ #
236
+ # Personal folders are created and destroyed along with invitations and memberships.
237
+ #
238
+ has_many :personal_folders
239
+ has_many :folders, :through => :personal_folders
134
240
 
241
+ def add_personal_folders(folders=[])
242
+ self.folders << folders if folders
243
+ end
244
+
245
+ def remove_personal_folders(folders=[])
246
+ self.folders.delete(folders) if folders
247
+ end
248
+
249
+ def has_folder?(folder)
250
+ folder && personal_folders.of_folder(folder).any?
251
+ end
252
+
253
+ def documents
254
+ Document.visible_to(self)
255
+ end
256
+
257
+
258
+ ## Dropbox links
259
+ #
260
+ has_many :dropbox_tokens, :foreign_key => "created_by_id"
261
+ has_many :dropbox_documents
262
+
135
263
  def dropbox_token
136
264
  unless @dropbox_token
137
265
  @dropbox_token = dropbox_tokens.by_date.last || 'nope'
@@ -142,12 +270,188 @@ module Droom
142
270
  def dropbox_client
143
271
  dropbox_token.dropbox_client if dropbox_token
144
272
  end
273
+
274
+ ## Mugshot
275
+ #
276
+ has_attached_file :image,
277
+ :default_url => ActionController::Base.helpers.image_path("droom/missing/:style.png"),
278
+ :styles => {
279
+ :standard => "520x520#",
280
+ :icon => "32x32#",
281
+ :thumb => "130x130#"
282
+ }
283
+
284
+ do_not_validate_attachment_file_type :image
285
+
286
+ def thumbnail
287
+ image.url(:thumb) if image?
288
+ end
289
+
290
+ def icon
291
+ image.url(:icon) if image?
292
+ end
293
+
294
+ # For suggestion box
295
+ #
296
+ scope :matching, -> fragment {
297
+ where('droom_users.given_name LIKE :f OR droom_users.family_name LIKE :f OR droom_users.chinese_name LIKE :f OR droom_users.title LIKE :f OR droom_users.email LIKE :f OR droom_users.phone LIKE :f OR CONCAT(droom_users.given_name, " ", droom_users.family_name) LIKE :f OR CONCAT(droom_users.family_name, " ", droom_users.given_name) LIKE :f', :f => "%#{fragment}%")
298
+ }
299
+
300
+ scope :matching_in_col, -> col, fragment {
301
+ where("droom_users.#{col} LIKE :f", :f => "%#{fragment}%")
302
+ }
303
+
304
+ def as_suggestion
305
+ {
306
+ :type => 'person',
307
+ :prompt => formal_name,
308
+ :value => formal_name,
309
+ :id => id
310
+ }
311
+ end
312
+
313
+ def as_search_result
314
+ {
315
+ :type => 'person',
316
+ :prompt => name,
317
+ :value => name,
318
+ :id => id
319
+ }
320
+ end
321
+
322
+
323
+ # For select box
324
+ #
325
+ def self.for_selection
326
+ self.published.map{|p| [p.name, p.id] }
327
+ end
328
+
329
+
330
+ ## Names
331
+ #
332
+ # With Anglo-Chinese Hong Kong names it is difficult to be sure of the right presentation.
333
+ #
334
+ # We hold the name in three fields: title, given name and family name. People with both a Chinese and an
335
+ # English forename are encouraged to enter their given name in the form Tai Wan, Jimmy. The family_name
336
+ # should always be a single, usually Chinese, surname: Chan or Smith.
337
+ #
338
+ # When a comma is found in the given name, we assume that they have followed the chinese, english format.
339
+ # If not, we assume the whole name is Chinese.
340
+ #
341
+ # ### Polite informality
342
+ #
343
+ # People with an English forename would normally be addressed as Jimmy Chan. People with only a Chinese
344
+ # forename should be addressed as Chan Tai Wan.
345
+ #
346
+ def informal_name
347
+ # The standard form of the given name is Tai Wan, Ray
348
+ chinese, english = given_name.split(/,\s*/)
349
+ # But some people are known only as Ray.
350
+ # Here we can't tell the difference between people with one chinese given name and one anglo given name
351
+ # but the order of names is reversed in the latter case. For now we assume that the presence of a chinese
352
+ # name indicates that the chinese word ordering should be used.
353
+ unless chinese_name?
354
+ english ||= chinese.strip.split(/\s+/).first
355
+ end
356
+ if english
357
+ # People with an english name are called Ray Chan, by default
358
+ [english, family_name].join(' ')
359
+ else
360
+ # People without are called Chan Tai Wan
361
+ [family_name, chinese].join(' ')
362
+ end
363
+ end
364
+
365
+ def name
366
+ chinese, english = given_name.split(/,\s*/)
367
+ unless chinese_name?
368
+ english ||= chinese.split(/\s+/).first
369
+ end
370
+ if english
371
+ [english, family_name].join(' ')
372
+ else
373
+ [family_name, chinese].join(' ')
374
+ end
375
+ end
376
+
377
+ # ### Formality
378
+ #
379
+ # The family name is held separately becaose for most purposes we will address people using the relatively
380
+ # reliable 'Dr Chan' or 'Mr Smith'.
381
+ #
145
382
 
146
- def privileged?
147
- admin? || person && person.privileged?
383
+ def title_ordinary?
384
+ ['Mr', 'Ms', 'Mrs', '', nil].include?(title)
385
+ end
386
+
387
+ def title_if_it_matters
388
+ title unless title_ordinary?
389
+ end
390
+
391
+ def title
392
+ title = read_attribute(:title)
393
+ if title.blank?
394
+ title = (gender == 'f') ? 'Ms' : 'Mr'
395
+ end
396
+ title
397
+ end
398
+
399
+ # This shuold be a reasonable formal first-person form of address.
400
+ #
401
+ def formal_name
402
+ [title, family_name].join(' ')
403
+ end
404
+
405
+ # This is our best shot at a representation of the usual third person form of this person's name. It combines
406
+ # the informal name (which includes some logic to show chinese, anglo and mixed names correctly) with the title,
407
+ # if the title is not ordinary.
408
+ #
409
+ def colloquial_name
410
+ [title_if_it_matters, informal_name, honours].compact.join(' ')
411
+ end
412
+
413
+ # ### Completeness
414
+ #
415
+ # For record-keeping purposes we show the whole name: Chan Tai Wan, Jimmy.
416
+ #
417
+ def full_name
418
+ [family_name, given_name].compact.join(' ')
419
+ end
420
+
421
+ # ### Compatibility
422
+ #
423
+ # An HKID card will normally show only the translitered Chinese name: Chan Tai Wan
424
+ #
425
+ def official_name
426
+ chinese, english = given_name.split(/,\s*/)
427
+ [family_name, chinese].join(' ')
148
428
  end
149
429
 
430
+ def to_vcf
431
+ @vcard ||= Vcard::Vcard::Maker.make2 do |maker|
432
+ maker.add_name do |n|
433
+ n.given = name || ""
434
+ end
435
+ maker.add_addr {|a|
436
+ a.location = 'home' # until we do this properly with multiple contact sets
437
+ a.country = post_country || ""
438
+ a.region = post_region || ""
439
+ a.locality = post_city || ""
440
+ a.street = "#{post_line1}, #{post_line2}"
441
+ a.postalcode = post_code || ""
442
+ }
443
+ maker.add_tel phone { |t| t.location = 'home' } unless phone.blank?
444
+ maker.add_tel mobile { |t| t.location = 'cell' } unless mobile.blank?
445
+ maker.add_email email { |e| t.location = 'home' }
446
+ end
447
+ @vcard.to_s
448
+ end
150
449
 
450
+ def self.vcards_for(users=[])
451
+ users.map(&:vcf).join("\n")
452
+ end
453
+
454
+
151
455
  ## Preferences
152
456
  #
153
457
  # User settings are held as an association with Preference objects, which are simple key:value pairs.
@@ -166,13 +470,17 @@ module Droom
166
470
  # = link_to "copy to dropbox", dropbox_folder_url(folder)
167
471
  #
168
472
  def pref(key)
169
- if pref = preferences.find_by_key(key)
473
+ if pref = preferences.find_by(key: key)
170
474
  pref.value
171
475
  else
172
476
  Droom.user_default(key)
173
477
  end
174
478
  end
175
-
479
+
480
+ def pref?(key)
481
+ !!pref(key)
482
+ end
483
+
176
484
  # `User#preference(key)` always returns a preference object and is used to build control panels. If no preference
177
485
  # is saved for the given key, we return a new (unsaved) one with that key and the default value.
178
486
  #
@@ -181,7 +489,7 @@ module Droom
181
489
  pref.value = Droom.user_default(key) unless pref.persisted?
182
490
  pref
183
491
  end
184
-
492
+
185
493
  # Setting preferences is normally handled either by the PreferencesController or by nesting preferences
186
494
  # in a user form. `User#set_pref` is a convenient console method but not otherwise used much.
187
495
  #
@@ -190,46 +498,61 @@ module Droom
190
498
  # user.set_pref("email:enabled", true)
191
499
  #
192
500
  def set_pref(key, value)
193
- preferences.find_or_create_by_key(key).set(value)
501
+ preferences.where(key: key).first_or_create.set(value)
194
502
  end
195
-
196
- ## Email
197
- #
198
- # If using `msg`, this defines the variables available in message templates.
503
+
504
+
505
+
506
+ ## Permissions
199
507
  #
200
- def for_email
201
- generate_confirmation_token! unless confirmation_token?
202
- {
203
- :informal_name => informal_name,
204
- :formal_name => formal_name,
205
- :forename => forename,
206
- :name => name,
207
- :email => email,
208
- :confirmation_url => Droom::Engine.routes.url_helpers.welcome_url(:id => self.id, :confirmation_token => self.confirmation_token, :host => ActionMailer::Base.default_url_options[:host]),
209
- :sign_in_url => Droom::Engine.routes.url_helpers.new_user_session_path(:host => ActionMailer::Base.default_url_options[:host]),
210
- :password_reset_url => Droom::Engine.routes.url_helpers.edit_user_password_url(:reset_password_token => self.reset_password_token, :host => ActionMailer::Base.default_url_options[:host])
211
- }
508
+ # Permissions are usually assigned by way of group membership, but the effect of this is to create a user-permission
509
+ # object. Additional user-permission objects can be created: all we need to do here is return that set.
510
+
511
+ has_many :user_permissions
512
+ has_many :permissions, :through => :user_permissions
513
+
514
+ def permission_codes
515
+ permissions.map(&:slug).compact.uniq
212
516
  end
213
517
 
214
- ## Omniauth package
215
- #
216
- # This is returned to the client application in the final stage of oauth authentication, and may be used to create
217
- # a new local account.
518
+ def permission_codes=(codes)
519
+ #TODO
520
+ end
218
521
 
219
- def credentials(options={})
220
- {
221
- id: id,
222
- title: title,
223
- name: name,
224
- forename: forename,
225
- email: email,
226
- admin: admin?,
227
- image: thumbnail
228
- }
522
+ def permitted?(key)
523
+ permission_codes.include?(key)
229
524
  end
230
525
 
231
- def thumbnail
232
- person.image.url(:icon) if person
526
+ def permissions_elsewhere?
527
+ permission_codes.select{|pc| pc !~ /droom/}.any?
528
+ end
529
+
530
+ def data_room_user?
531
+ admin? || permitted?('droom.login')
532
+ end
533
+
534
+ ## Other ownership
535
+ #
536
+ has_many :scraps, :foreign_key => "created_by_id"
537
+ has_many :documents, :foreign_key => "created_by_id"
538
+
539
+ protected
540
+
541
+ def ensure_uid!
542
+ self.uid = SecureRandom.uuid unless self.uid?
543
+ end
544
+
545
+ def send_confirmation_if_directed
546
+ self.send_confirmation_instructions if send_confirmation?
547
+ end
548
+
549
+ private
550
+
551
+ def generate_authentication_token
552
+ loop do
553
+ token = Devise.friendly_token
554
+ break token unless User.where(authentication_token: token).first
555
+ end
233
556
  end
234
557
 
235
558
  end