koalagator 4.0.0 → 5.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 (134) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.md +661 -0
  3. data/README.md +74 -21
  4. data/app/assets/config/calagator/manifest.js +5 -1
  5. data/app/assets/images/external_sites/mastodon.png +0 -0
  6. data/app/assets/images/nav_marker.png +0 -0
  7. data/app/assets/javascripts/calagator/forms.js +7 -0
  8. data/app/assets/stylesheets/calagator/custom/calendar.css +137 -0
  9. data/app/assets/stylesheets/calagator/errors.css +2 -4
  10. data/app/assets/stylesheets/calagator/forms.scss +5 -0
  11. data/app/assets/stylesheets/calagator/layout.scss +31 -9
  12. data/app/assets/stylesheets/calagator/typography.scss +39 -9
  13. data/app/assets/stylesheets/calagator/utils.scss +44 -0
  14. data/app/controllers/calagator/admin/curations_controller.rb +62 -0
  15. data/app/controllers/calagator/admin/users_controller.rb +79 -0
  16. data/app/controllers/calagator/application_controller.rb +29 -5
  17. data/app/controllers/calagator/curations_controller.rb +32 -0
  18. data/app/controllers/calagator/events_controller.rb +6 -0
  19. data/app/controllers/calagator/paper_trail_manager_controller.rb +5 -0
  20. data/app/controllers/calagator/passwords_controller.rb +4 -0
  21. data/app/controllers/calagator/registrations_controller.rb +5 -0
  22. data/app/controllers/calagator/sessions_controller.rb +4 -0
  23. data/app/controllers/calagator/site_controller.rb +10 -0
  24. data/app/controllers/calagator/sources_controller.rb +2 -0
  25. data/app/controllers/calagator/venues_controller.rb +5 -1
  26. data/app/controllers/calagator/versions_controller.rb +1 -1
  27. data/app/controllers/paper_trail_manager/changes_controller.rb +16 -16
  28. data/app/helpers/calagator/application_helper.rb +32 -3
  29. data/app/helpers/calagator/time_range_helper.rb +1 -1
  30. data/app/helpers/paper_trail_manager/changes_helper.rb +7 -7
  31. data/app/javascript/calagator/calendar/calendar.js +82 -0
  32. data/app/javascript/calagator/calendar/event.js +94 -0
  33. data/app/javascript/calagator/calendar/lib/components.js +120 -0
  34. data/app/javascript/calagator/calendar/lib/utils.js +67 -0
  35. data/app/models/calagator/curation.rb +32 -0
  36. data/app/models/calagator/event/browse.rb +3 -2
  37. data/app/models/calagator/event/cloner.rb +1 -1
  38. data/app/models/calagator/event/ical_renderer.rb +1 -1
  39. data/app/models/calagator/event/overview.rb +3 -1
  40. data/app/models/calagator/event/saver.rb +6 -1
  41. data/app/models/calagator/event/search.rb +1 -1
  42. data/app/models/calagator/event/search_engine.rb +1 -1
  43. data/app/models/calagator/event.rb +22 -5
  44. data/app/models/calagator/source/parser/hcal.rb +1 -1
  45. data/app/models/calagator/source/parser.rb +3 -3
  46. data/app/models/calagator/source.rb +23 -6
  47. data/app/models/calagator/user.rb +50 -0
  48. data/app/models/calagator/venue/geocoder.rb +1 -1
  49. data/app/models/calagator/venue/search.rb +1 -1
  50. data/app/models/calagator/venue/search_engine.rb +1 -1
  51. data/app/models/calagator/venue.rb +20 -1
  52. data/app/models/concerns/calagator/event_filterable.rb +22 -0
  53. data/app/views/calagator/admin/curations/_form.html.erb +56 -0
  54. data/app/views/calagator/admin/curations/_index.html.erb +12 -0
  55. data/app/views/calagator/admin/curations/edit.html.erb +2 -0
  56. data/app/views/calagator/admin/curations/index.html.erb +21 -0
  57. data/app/views/calagator/admin/curations/new.html.erb +2 -0
  58. data/app/views/calagator/admin/index.html.erb +6 -2
  59. data/app/views/calagator/admin/users/_form.html.erb +28 -0
  60. data/app/views/calagator/admin/users/edit.html.erb +7 -0
  61. data/app/views/calagator/admin/users/index.html.erb +38 -0
  62. data/app/views/calagator/admin/users/invite.html.erb +19 -0
  63. data/app/views/calagator/admin/users/new.html.erb +3 -0
  64. data/app/views/calagator/curations/show.html.erb +17 -0
  65. data/app/views/calagator/events/_index.html.erb +59 -0
  66. data/app/views/calagator/events/_item.html.erb +10 -3
  67. data/app/views/calagator/events/_subnav.html.erb +7 -2
  68. data/app/views/calagator/events/_subnav_custom.html.erb +0 -0
  69. data/app/views/calagator/events/index.atom.builder +1 -1
  70. data/app/views/calagator/events/index.html.erb +9 -60
  71. data/app/views/calagator/events/show.html.erb +5 -3
  72. data/app/views/calagator/shared/_calendar.html.erb +7 -0
  73. data/app/views/calagator/shared/_subnav_curations.html.erb +5 -0
  74. data/app/views/calagator/shared/_subnav_pinned_venues.html.erb +5 -0
  75. data/app/views/calagator/site/_contact.html.erb +1 -0
  76. data/app/views/calagator/site/_description.html.erb +2 -2
  77. data/app/views/calagator/site/about.html.erb +9 -0
  78. data/app/views/calagator/site/closed_registrations.html.erb +2 -0
  79. data/app/views/calagator/site/embed.html.erb +16 -0
  80. data/app/views/calagator/site/index.html.erb +1 -1
  81. data/app/views/calagator/sources/index.html.erb +1 -1
  82. data/app/views/calagator/sources/show.html.erb +1 -1
  83. data/app/views/calagator/venues/_form.html.erb +5 -1
  84. data/app/views/calagator/venues/_subnav.html.erb +9 -1
  85. data/app/views/calagator/venues/_subnav_custom.html.erb +0 -0
  86. data/app/views/calagator/venues/show.html.erb +1 -1
  87. data/app/views/layouts/calagator/_devise.html.erb +17 -0
  88. data/app/views/layouts/calagator/_footer.html.erb +3 -1
  89. data/app/views/layouts/calagator/_head.html.erb +0 -0
  90. data/app/views/layouts/calagator/_header.html.erb +3 -0
  91. data/app/views/layouts/calagator/application.html.erb +3 -0
  92. data/app/views/layouts/calagator/embed.html.erb +15 -0
  93. data/app/views/paper_trail_manager/changes/_version.html.erb +5 -5
  94. data/app/views/paper_trail_manager/changes/index.atom.builder +12 -12
  95. data/bin/{calagator → koalagator} +14 -9
  96. data/config/importmap.rb +11 -0
  97. data/config/initializers/admin_user.rb +15 -0
  98. data/config/initializers/observers.rb +1 -1
  99. data/config/initializers/paper_trail_manager.rb +1 -1
  100. data/config/locales/devise.en.yml +65 -0
  101. data/config/routes.rb +26 -1
  102. data/db/migrate/20240319042449_devise_create_calagator_users.rb +43 -0
  103. data/db/migrate/20240319061154_add_admin_flag_to_calagator_user.rb +5 -0
  104. data/db/migrate/20240320043535_add_name_to_calagator_user.rb +8 -0
  105. data/db/migrate/20240322035554_add_created_by_to_records.rb +12 -0
  106. data/db/migrate/20240510051940_create_calagator_curations.rb +15 -0
  107. data/db/migrate/20240628055300_add_pinned_to_venue.rb +5 -0
  108. data/db/seeds.rb +49 -0
  109. data/lib/calagator/decode_html_entities_hack.rb +1 -1
  110. data/lib/calagator/engine.rb +16 -1
  111. data/lib/calagator/machine_tag.rb +1 -1
  112. data/lib/calagator/strip_whitespace.rb +1 -1
  113. data/lib/calagator/vcalendar.rb +4 -4
  114. data/lib/calagator/version.rb +5 -2
  115. data/lib/generators/calagator/install_generator.rb +9 -1
  116. data/lib/generators/calagator/templates/app/views/devise/registrations/edit.html.erb +48 -0
  117. data/lib/generators/calagator/templates/app/views/devise/registrations/new.html.erb +29 -0
  118. data/lib/generators/calagator/templates/config/initializers/01_calagator.rb +34 -6
  119. data/lib/generators/calagator/templates/config/initializers/04_devise.rb +314 -0
  120. data/lib/{calagator.rb → koalagator.rb} +15 -3
  121. data/lib/paper_trail_manager.rb +11 -11
  122. data/lib/theme_reader.rb +1 -1
  123. data/rails_template.rb +6 -6
  124. data/vendor/javascript/@event-calendar--core.js +10 -0
  125. data/vendor/javascript/@event-calendar--day-grid.js +2 -0
  126. data/vendor/javascript/@event-calendar--list.js +2 -0
  127. data/vendor/javascript/ical.js.js +2 -0
  128. metadata +166 -105
  129. data/MIT-LICENSE.txt +0 -23
  130. data/app/models/calagator/event/search_engine/apache_sunspot.rb +0 -106
  131. data/app/models/calagator/venue/search_engine/apache_sunspot.rb +0 -85
  132. data/lib/tasks/sunspot_reindex_calagator.rake +0 -20
  133. data/lib/tasks/sunspot_solr_restart_enhancements.rake +0 -20
  134. data/lib/wait_for_solr.rb +0 -26
@@ -0,0 +1,120 @@
1
+ import { Optional, toTitleCase } from "calendar/lib/utils"
2
+
3
+ class BaseComponent extends HTMLElement {
4
+ static observe(...attr) {
5
+ this._attrSet = (this._attrSet || new Set)
6
+ attr.forEach(e => {this._attrSet.add(e)})
7
+ this.observedAttributes = Array.from(this._attrSet)
8
+ }
9
+
10
+ constructor() {
11
+ super()
12
+ }
13
+
14
+ attributeChangedCallback(name, oldValue, newValue) {
15
+ const callback = `on${toTitleCase(name)}Changed`
16
+ if (typeof this[callback] == "function") {
17
+ this[callback](oldValue, newValue)
18
+ }
19
+ }
20
+
21
+ emit(name, data) {
22
+ this.dispatchEvent(new CustomEvent(name, {detail: data}))
23
+ }
24
+
25
+ on(name, callback) {
26
+ this.addEventListener(name, callback)
27
+ }
28
+ }
29
+
30
+ class SelectComponent extends BaseComponent {
31
+ constructor() {
32
+ super()
33
+ }
34
+
35
+ _children = new Set
36
+ selected = new Set
37
+
38
+ select(child) {
39
+ child.setAttribute("selected", true)
40
+ }
41
+
42
+ deselect(child) {
43
+ child.removeAttribute("selected")
44
+ }
45
+
46
+ _select(child) {
47
+ this.selected.add(child)
48
+ this.emit("child-selected", child)
49
+ }
50
+
51
+ _deselect(child) {
52
+ this.selected.delete(child)
53
+ this.emit("child-deselected", child)
54
+ }
55
+
56
+ _registerChild(child) {
57
+ this._children.add(child)
58
+ }
59
+ _removeChild(child) {
60
+ this._children.delete(child)
61
+ }
62
+ }
63
+
64
+ class SingleSelectComponent extends SelectComponent {
65
+ constructor() {
66
+ super()
67
+ }
68
+
69
+ selected = Optional.empty()
70
+
71
+ _select(selected) {
72
+ this.selected = Optional.of(selected)
73
+ this._children.forEach(child => {
74
+ if (child == selected) { return }
75
+ this.deselect(child)
76
+ })
77
+ this.emit("child-selected", selected)
78
+ }
79
+
80
+ _deselect(child) {
81
+ if (child == this.selected.get()) {
82
+ this.selected = Optional.empty()
83
+ }
84
+ this.emit("child-deselected", child)
85
+ }
86
+ }
87
+
88
+ class OptionComponent extends BaseComponent {
89
+ static {
90
+ this.observe("selected")
91
+ }
92
+ constructor(parentType) {
93
+ super()
94
+ const parent = this.closest(parentType)
95
+ this.parent = Optional.try(parent instanceof SelectComponent, parent)
96
+ this.parent.ifPresent(p => { p._registerChild(this) })
97
+ }
98
+ selected = false
99
+
100
+ onSelectedChanged(_, value) {
101
+ this.selected = (value == null ? false : true)
102
+ if (value) {
103
+ this.parent.ifPresent(p => { p._select(this) })
104
+ this.emit("selected", {})
105
+ } else {
106
+ this.parent.ifPresent(p => { p._deselect(this) })
107
+ this.emit("deselected", {})
108
+ }
109
+ }
110
+
111
+ select() {
112
+ this.setAttribute("selected", true)
113
+ }
114
+
115
+ deselect() {
116
+ this.removeAttribute("selected")
117
+ }
118
+ }
119
+
120
+ export { BaseComponent, SelectComponent, SingleSelectComponent, OptionComponent }
@@ -0,0 +1,67 @@
1
+ function toTitleCase(str) {
2
+ return str.replace(
3
+ /\w\S*/g,
4
+ text => text.charAt(0).toUpperCase() + text.substring(1).toLowerCase()
5
+ );
6
+ }
7
+
8
+ class Optional {
9
+ static empty() {
10
+ return new this(null)
11
+ }
12
+
13
+ static of(value) {
14
+ return new this(value)
15
+ }
16
+
17
+ static try(condition, value) {
18
+ if (condition) {
19
+ return this.of(value)
20
+ } else {
21
+ return this.empty()
22
+ }
23
+ }
24
+
25
+ constructor(value) {
26
+ if (value != undefined) {
27
+ this.#value = value
28
+ }
29
+ }
30
+ #value = null
31
+
32
+ isPresent() {
33
+ return !(this.#value === null)
34
+ }
35
+
36
+ get() {
37
+ return this.#value
38
+ }
39
+
40
+ ifPresent(callback) {
41
+ if (!this.isPresent()) { return }
42
+ callback(this.#value)
43
+ }
44
+
45
+ unlessPresent(callback) {
46
+ if (this.isPresent()) { return }
47
+ callback()
48
+ }
49
+
50
+ orElse(value) {
51
+ if (this.isPresent()) {
52
+ return this.#value
53
+ } else {
54
+ return value
55
+ }
56
+ }
57
+
58
+ orElseGet(callback) {
59
+ if (this.isPresent()) {
60
+ return this.#value
61
+ } else {
62
+ return callback()
63
+ }
64
+ }
65
+ }
66
+
67
+ export { Optional, toTitleCase }
@@ -0,0 +1,32 @@
1
+ # == Schema Information
2
+ #
3
+ # Table name: calagator_curations
4
+ #
5
+ # id :integer not null, primary key
6
+ # description :string
7
+ # display_name :string
8
+ # name :string not null
9
+ # priority :integer default(0), not null
10
+ # unlisted :boolean default(FALSE), not null
11
+ # created_at :datetime not null
12
+ # updated_at :datetime not null
13
+ #
14
+ # Indexes
15
+ #
16
+ # index_calagator_curations_on_name (name) UNIQUE
17
+ #
18
+ module Calagator
19
+ class Curation < Calagator::ApplicationRecord
20
+ include EventFilterable
21
+ scope :listed, -> { where(unlisted: false) }
22
+
23
+ validates :name, :display_name, :priority, presence: true
24
+ validates :name,
25
+ format: {with: /\A[a-z0-9\-_]+\z/, message: "only allows ASCII letters, numbers, dashes and underscores"},
26
+ uniqueness: true,
27
+ length: {maximum: 64}
28
+
29
+ scope :order_by_priority, ->(direction = :desc) { order(priority: direction) }
30
+ scope :listed, ->(listed = true) { where(unlisted: !listed) }
31
+ end
32
+ end
@@ -3,10 +3,11 @@
3
3
  module Calagator
4
4
  class Event < Calagator::ApplicationRecord
5
5
  class Browse < Struct.new(:order, :date, :time)
6
- def initialize(attributes = {})
6
+ def initialize(attributes = {}, scope = nil)
7
7
  members.each do |key|
8
- send "#{key}=", attributes[key]
8
+ send :"#{key}=", attributes[key]
9
9
  end
10
+ @scope = scope
10
11
  end
11
12
 
12
13
  def events
@@ -15,7 +15,7 @@ module Calagator
15
15
  def clone
16
16
  clone = Event.new
17
17
  ATTRIBUTES.each do |attribute|
18
- clone.send "#{attribute}=", event.send(attribute)
18
+ clone.send :"#{attribute}=", event.send(attribute)
19
19
  end
20
20
  if event.start_time
21
21
  clone.start_time = clone_time_for_today(event.start_time)
@@ -25,7 +25,7 @@ module Calagator
25
25
 
26
26
  def self.render_icalendar(events, opts)
27
27
  cal = RiCal.Calendar do |calendar|
28
- calendar.prodid = "-//Calagator//EN"
28
+ calendar.prodid = "-//#{Calagator::NAME}//EN"
29
29
 
30
30
  Array(events).each do |event|
31
31
  calendar.event do |entry|
@@ -20,7 +20,9 @@ module Calagator
20
20
  end
21
21
 
22
22
  def tags
23
- @tags ||= Event.tag_counts_on(:tags, limit: 100, conditions: "tags_count >= 10").sort_by(&:name)
23
+ @tags ||= Event.tag_counts_on(
24
+ :tags, limit: 100, conditions: "tags_count >= #{Calagator.tag_cloud_min}"
25
+ ).sort_by(&:name)
24
26
  end
25
27
 
26
28
  private
@@ -25,7 +25,12 @@ module Calagator
25
25
  if params[:event] && params[:event][:venue_id].present?
26
26
  Venue.find(params[:event][:venue_id]).originator
27
27
  else
28
- Venue.find_or_initialize_by(title: params[:venue_name]).originator
28
+ venue = Venue.find_or_initialize_by(title: params[:venue_name]).originator
29
+ # Set the created_by to match the event, if creating the venue.
30
+ if event.created_by_id? && !venue.id?
31
+ venue.created_by = event.created_by
32
+ end
33
+ venue
29
34
  end
30
35
  end
31
36
 
@@ -5,7 +5,7 @@ module Calagator
5
5
  class Search < Struct.new(:query, :tag, :order, :current)
6
6
  def initialize(attributes = {})
7
7
  members.each do |key|
8
- send "#{key}=", attributes[key]
8
+ send :"#{key}=", attributes[key]
9
9
  end
10
10
  self.order ||= "date"
11
11
  validate!
@@ -21,7 +21,7 @@ module Calagator
21
21
  private_class_method
22
22
 
23
23
  def self.search_engine
24
- (kind == :sunspot) ? ApacheSunspot : Sql
24
+ Sql
25
25
  end
26
26
  end
27
27
  end
@@ -5,6 +5,7 @@
5
5
  # Table name: events
6
6
  #
7
7
  # id :integer not null, primary key
8
+ # created_by_name :string
8
9
  # description :text
9
10
  # end_time :datetime
10
11
  # locked :boolean default(FALSE)
@@ -15,10 +16,19 @@
15
16
  # venue_details :text
16
17
  # created_at :datetime
17
18
  # updated_at :datetime
19
+ # created_by_id :integer
18
20
  # duplicate_of_id :integer
19
21
  # source_id :integer
20
22
  # venue_id :integer
21
23
  #
24
+ # Indexes
25
+ #
26
+ # index_events_on_created_by_id (created_by_id)
27
+ #
28
+ # Foreign Keys
29
+ #
30
+ # created_by_id (created_by_id => calagator_users.id)
31
+ #
22
32
 
23
33
  require "calagator/denylist_validator"
24
34
  require "calagator/duplicate_checking"
@@ -49,6 +59,7 @@ module Calagator
49
59
  include ActiveModel::Serializers::Xml
50
60
 
51
61
  # Associations
62
+ belongs_to :created_by, class_name: "Calagator::User", optional: true
52
63
  belongs_to :venue, counter_cache: true, optional: true
53
64
  belongs_to :source, optional: true
54
65
 
@@ -60,6 +71,7 @@ module Calagator
60
71
  format: {with: %r{\Ahttps?://(\w+:?\w*@)?(\S+)(:[0-9]+)?(/|/([\w#!:.?+=&%@\-/]))?\Z},
61
72
  allow_blank: true}
62
73
 
74
+ before_save :set_created_by_name, if: :created_by_id?
63
75
  before_destroy :check_if_locked_before_destroy # prevent locked events from being destroyed
64
76
 
65
77
  # Duplicates
@@ -105,25 +117,25 @@ module Calagator
105
117
  #---[ Overrides ]-------------------------------------------------------
106
118
 
107
119
  def url=(value)
108
- super UrlPrefixer.prefix(value)
120
+ super(UrlPrefixer.prefix(value))
109
121
  end
110
122
 
111
123
  # Set the start_time to the given +value+, which could be a Time, Date,
112
124
  # DateTime, String, Array of Strings, or nil.
113
125
  def start_time=(value)
114
- super time_for(value)
126
+ super(time_for(value))
115
127
  rescue ArgumentError
116
128
  errors.add :start_time, "is invalid"
117
- super nil
129
+ super(nil)
118
130
  end
119
131
 
120
132
  # Set the end_time to the given +value+, which could be a Time, Date,
121
133
  # DateTime, String, Array of Strings, or nil.
122
134
  def end_time=(value)
123
- super time_for(value)
135
+ super(time_for(value))
124
136
  rescue ArgumentError
125
137
  errors.add :end_time, "is invalid"
126
- super nil
138
+ super(nil)
127
139
  end
128
140
 
129
141
  def time_for(value)
@@ -182,5 +194,10 @@ module Calagator
182
194
 
183
195
  (end_time - start_time)
184
196
  end
197
+
198
+ def set_created_by_name
199
+ self.created_by_name = created_by.name_with_email
200
+ end
201
+ private :set_created_by_name
185
202
  end
186
203
  end
@@ -27,7 +27,7 @@ module Calagator
27
27
  next unless hcal.respond_to?(hcal_field)
28
28
  next unless (value = decoded_field(hcal, hcal_field))
29
29
 
30
- event.send "#{field}=", value
30
+ event.send :"#{field}=", value
31
31
  end
32
32
  event_or_duplicate(event)
33
33
  end.uniq do |event|
@@ -113,6 +113,6 @@ module Calagator
113
113
  end
114
114
  end
115
115
 
116
- require "calagator/source/parser/not_found"
117
- require "calagator/source/parser/ical"
118
- require "calagator/source/parser/hcal"
116
+ require_relative "parser/not_found"
117
+ require_relative "parser/ical"
118
+ require_relative "parser/hcal"
@@ -4,12 +4,22 @@
4
4
  #
5
5
  # Table name: sources
6
6
  #
7
- # id :integer not null, primary key
8
- # imported_at :datetime
9
- # title :string
10
- # url :string
11
- # created_at :datetime
12
- # updated_at :datetime
7
+ # id :integer not null, primary key
8
+ # created_by_name :string
9
+ # imported_at :datetime
10
+ # title :string
11
+ # url :string
12
+ # created_at :datetime
13
+ # updated_at :datetime
14
+ # created_by_id :integer
15
+ #
16
+ # Indexes
17
+ #
18
+ # index_sources_on_created_by_id (created_by_id)
19
+ #
20
+ # Foreign Keys
21
+ #
22
+ # created_by_id (created_by_id => calagator_users.id)
13
23
  #
14
24
 
15
25
  # == Source
@@ -26,11 +36,14 @@ module Calagator
26
36
 
27
37
  validate :assert_url
28
38
 
39
+ belongs_to :created_by, class_name: "Calagator::User", optional: true
29
40
  has_many :events, dependent: :destroy
30
41
  has_many :venues, dependent: :destroy
31
42
 
32
43
  scope :listing, -> { order("created_at DESC") }
33
44
 
45
+ before_save :set_created_by_name, if: :created_by_id?
46
+
34
47
  has_paper_trail
35
48
 
36
49
  xss_foliate
@@ -76,5 +89,9 @@ module Calagator
76
89
  errors.add :url, "has invalid format"
77
90
  false
78
91
  end
92
+
93
+ def set_created_by_name
94
+ self.created_by_name = created_by.name_with_email
95
+ end
79
96
  end
80
97
  end
@@ -0,0 +1,50 @@
1
+ # == Schema Information
2
+ #
3
+ # Table name: calagator_users
4
+ #
5
+ # id :integer not null, primary key
6
+ # admin :boolean
7
+ # display_name :string
8
+ # email :string default(""), not null
9
+ # encrypted_password :string default(""), not null
10
+ # name :string not null
11
+ # remember_created_at :datetime
12
+ # reset_password_sent_at :datetime
13
+ # reset_password_token :string
14
+ # created_at :datetime not null
15
+ # updated_at :datetime not null
16
+ #
17
+ # Indexes
18
+ #
19
+ # index_calagator_users_on_email (email) UNIQUE
20
+ # index_calagator_users_on_name (name) UNIQUE
21
+ # index_calagator_users_on_reset_password_token (reset_password_token) UNIQUE
22
+ #
23
+ module Calagator
24
+ class User < ApplicationRecord
25
+ if Calagator.devise_enabled
26
+ # Include default devise modules. Others available are:
27
+ # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
28
+ devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable
29
+ end
30
+
31
+ scope :admin, -> { where(admin: true) }
32
+ validates :name, :email, presence: true
33
+ validates :name, format: {with: /\A[a-z0-9\-_]+\z/, message: "only allows ASCII letters, numbers, dashes and underscores"}
34
+
35
+ has_many :events, foreign_key: :created_by_id, dependent: :nullify
36
+ has_many :venues, foreign_key: :created_by_id, dependent: :nullify
37
+ has_many :sources, foreign_key: :created_by_id, dependent: :nullify
38
+
39
+ before_validation -> { name&.downcase! }, on: :create
40
+
41
+ def display_name
42
+ attribute = read_attribute(:display_name)
43
+ attribute.present? ? attribute : name
44
+ end
45
+
46
+ def name_with_email
47
+ "@#{name} <#{email}>"
48
+ end
49
+ end
50
+ end
@@ -45,7 +45,7 @@ module Calagator
45
45
  def should_geocode?
46
46
  [
47
47
  perform_geocoding,
48
- (venue.location.blank? || venue.force_geocoding == "1"),
48
+ venue.location.blank? || venue.force_geocoding == "1",
49
49
  venue.geocode_address.present?,
50
50
  venue.duplicate_of.blank?
51
51
  ].all?
@@ -5,7 +5,7 @@ module Calagator
5
5
  class Search < Struct.new(:tag, :query, :wifi, :all, :closed, :include_closed)
6
6
  def initialize(attributes = {})
7
7
  members.each do |key|
8
- send "#{key}=", attributes[key]
8
+ send :"#{key}=", attributes[key]
9
9
  end
10
10
  end
11
11
 
@@ -17,7 +17,7 @@ module Calagator
17
17
  private_class_method
18
18
 
19
19
  def self.search_engine
20
- (kind == :sunspot) ? ApacheSunspot : Sql
20
+ Sql
21
21
  end
22
22
  end
23
23
  end
@@ -9,12 +9,14 @@
9
9
  # address :string
10
10
  # closed :boolean default(FALSE)
11
11
  # country :string
12
+ # created_by_name :string
12
13
  # description :text
13
14
  # email :string
14
15
  # events_count :integer
15
16
  # latitude :decimal(7, 4)
16
17
  # locality :string
17
18
  # longitude :decimal(7, 4)
19
+ # pinned :boolean default(FALSE), not null
18
20
  # postal_code :string
19
21
  # region :string
20
22
  # street_address :string
@@ -24,9 +26,18 @@
24
26
  # wifi :boolean default(FALSE)
25
27
  # created_at :datetime
26
28
  # updated_at :datetime
29
+ # created_by_id :integer
27
30
  # duplicate_of_id :integer
28
31
  # source_id :integer
29
32
  #
33
+ # Indexes
34
+ #
35
+ # index_venues_on_created_by_id (created_by_id)
36
+ #
37
+ # Foreign Keys
38
+ #
39
+ # created_by_id (created_by_id => calagator_users.id)
40
+ #
30
41
  require "calagator/decode_html_entities_hack"
31
42
  require "calagator/strip_whitespace"
32
43
  require "calagator/url_prefixer"
@@ -51,11 +62,13 @@ module Calagator
51
62
 
52
63
  # Associations
53
64
  has_many :events, -> { non_duplicates }, dependent: :nullify
65
+ belongs_to :created_by, class_name: "Calagator::User", optional: true
54
66
  belongs_to :source, optional: true
55
67
 
56
68
  # Triggers
57
69
  strip_whitespace! :title, :description, :address, :url, :street_address, :locality, :region, :postal_code, :country, :email, :telephone
58
70
  before_save :geocode!
71
+ before_save :set_created_by_name, if: :created_by_id?
59
72
 
60
73
  # Validations
61
74
  validates :title, presence: true
@@ -75,13 +88,14 @@ module Calagator
75
88
  scope :with_public_wifi, -> { where(wifi: true) }
76
89
  scope :in_business, -> { where(closed: false) }
77
90
  scope :out_of_business, -> { where(closed: true) }
91
+ scope :pinned, ->(pinned = true) { where(pinned: pinned) }
78
92
 
79
93
  def self.search(query, opts = {})
80
94
  SearchEngine.search(query, opts)
81
95
  end
82
96
 
83
97
  def url=(value)
84
- super UrlPrefixer.prefix(value)
98
+ super(UrlPrefixer.prefix(value))
85
99
  end
86
100
 
87
101
  # Display a single line address.
@@ -112,5 +126,10 @@ module Calagator
112
126
  def update_events_count!
113
127
  update_attribute(:events_count, events.non_duplicates.count)
114
128
  end
129
+
130
+ def set_created_by_name
131
+ self.created_by_name = created_by.name_with_email
132
+ end
133
+ private :set_created_by_name
115
134
  end
116
135
  end
@@ -0,0 +1,22 @@
1
+ module Calagator
2
+ module EventFilterable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ acts_as_taggable_on :blocks, :requires, :denies, :allows
7
+ end
8
+
9
+ def events
10
+ event_list = Event.all
11
+ event_list.merge!(Event.tagged_with(block_list, exclude: true)) if block_list.any?
12
+ event_list.merge!(Event.tagged_with(require_list, any: true)) if require_list.any?
13
+ if deny_list.any?
14
+ deny = event_list.tagged_with(deny_list, exclude: true).or(
15
+ event_list.tagged_with(allow_list, any: true).except(:select, :order)
16
+ )
17
+ event_list.merge!(deny)
18
+ end
19
+ event_list
20
+ end
21
+ end
22
+ end