meetalendar 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rubocop.yml +96 -0
  4. data/.travis.yml +21 -0
  5. data/Gemfile +20 -0
  6. data/LICENSE +20 -0
  7. data/README.md +141 -0
  8. data/Rakefile +5 -0
  9. data/app/controllers/comfy/admin/meetalendar/meetups_controller.rb +154 -0
  10. data/app/models/comfy/admin/meetalendar/auth_credential.rb +22 -0
  11. data/app/models/comfy/admin/meetalendar/meetup_group.rb +3 -0
  12. data/app/models/comfy/admin/meetalendar/meetups_calendar_syncer.rb +178 -0
  13. data/app/models/comfy/admin/meetalendar/meetups_controller_logic.rb +77 -0
  14. data/app/models/google/auth/store/db_token_store.rb +37 -0
  15. data/app/views/comfy/admin/meetalendar/meetups/_authorize_calendar.slim +6 -0
  16. data/app/views/comfy/admin/meetalendar/meetups/_edit.slim +6 -0
  17. data/app/views/comfy/admin/meetalendar/meetups/_search_mask.slim +19 -0
  18. data/app/views/comfy/admin/meetalendar/meetups/_search_result.slim +57 -0
  19. data/app/views/comfy/admin/meetalendar/meetups/authorize_calendar.slim +6 -0
  20. data/app/views/comfy/admin/meetalendar/meetups/edit.slim +11 -0
  21. data/app/views/comfy/admin/meetalendar/meetups/index.slim +26 -0
  22. data/app/views/comfy/admin/meetalendar/meetups/search_mask.slim +5 -0
  23. data/app/views/comfy/admin/meetalendar/meetups/search_result.slim +5 -0
  24. data/app/views/comfy/admin/meetalendar/partials/_navigation.html.haml +9 -0
  25. data/app/views/layouts/comfy/meetalendar/application.html.erb +28 -0
  26. data/bin/bundle +3 -0
  27. data/bin/rails +4 -0
  28. data/bin/rake +4 -0
  29. data/bin/setup +36 -0
  30. data/bin/update +31 -0
  31. data/bin/yarn +11 -0
  32. data/comfy_meetalendar.gemspec +44 -0
  33. data/config.ru +6 -0
  34. data/config/application.rb +38 -0
  35. data/config/boot.rb +7 -0
  36. data/config/database.yml +11 -0
  37. data/config/environment.rb +7 -0
  38. data/config/environments/development.rb +64 -0
  39. data/config/environments/test.rb +51 -0
  40. data/config/initializers/meetalendar.rb +23 -0
  41. data/config/locales/en.yml +33 -0
  42. data/config/meetalendar_routes.rb +3 -0
  43. data/config/storage.yml +35 -0
  44. data/db/migrate/00_create_cms.rb +142 -0
  45. data/db/migrate/01_create_meetalendar_meetup_groups.rb +13 -0
  46. data/db/migrate/02_create_meetalendar_auth_credentials.rb +16 -0
  47. data/lib/generators/comfy/meetalendar/README +5 -0
  48. data/lib/generators/comfy/meetalendar/meetalendar_generator.rb +61 -0
  49. data/lib/meetalendar.rb +29 -0
  50. data/lib/meetalendar/configuration.rb +24 -0
  51. data/lib/meetalendar/engine.rb +32 -0
  52. data/lib/meetalendar/routes/meetalendar_admin.rb +30 -0
  53. data/lib/meetalendar/routing.rb +3 -0
  54. data/lib/meetalendar/version.rb +7 -0
  55. data/lib/tasks/meetalendar_tasks.rake +14 -0
  56. metadata +244 -0
@@ -0,0 +1,22 @@
1
+ require 'attr_encrypted'
2
+
3
+ class Comfy::Admin::Meetalendar::AuthCredential < ApplicationRecord
4
+ self.table_name = "meetalendar_auth_credentials"
5
+
6
+ # TODO(Schau): There might be a better place for this function
7
+ def self.expand_env(str)
8
+ str.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) { ENV[$1] }
9
+ end
10
+
11
+ attr_encrypted :access_token, key: Rails.application.secrets.secret_key_base.to_s.bytes[0..31].pack("c" * 32)
12
+ attr_encrypted :refresh_token, key: Rails.application.secrets.secret_key_base.to_s.bytes[0..31].pack("c" * 32)
13
+
14
+ def scope
15
+ # NOTE(Schau): Scope expected to be a json parsable string that results in an array.
16
+ parsed_scope = JSON.parse(self.scope_json)
17
+ parsed_scope = parsed_scope.empty? ? [] : parsed_scope
18
+ end
19
+ def scope=(new_scope)
20
+ self.scope_json = new_scope.to_json.to_s
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ class Comfy::Admin::Meetalendar::MeetupGroup < ApplicationRecord
2
+ self.table_name = "meetalendar_meetup_groups"
3
+ end
@@ -0,0 +1,178 @@
1
+ require 'rubygems'
2
+ require 'active_resource'
3
+
4
+ require "date"
5
+ require_relative "../../../google/auth/store/db_token_store"
6
+ require "google/apis/calendar_v3"
7
+ require "googleauth"
8
+
9
+ require_relative "../../../../controllers/comfy/admin/meetalendar/meetups_controller"
10
+
11
+ module Comfy::Admin::Meetalendar::MeetupsCalendarSyncer
12
+ GOOGLE_CALENDAR_AUTH_OOB_URI ||= "urn:ietf:wg:oauth:2.0:oob".freeze
13
+ GOOGLE_CALENDAR_AUTH_APPLICATION_NAME ||= "Google Calendar API Ruby MeetupSync".freeze
14
+ GOOGLE_CALENDAR_AUTH_SCOPE ||= Google::Apis::CalendarV3::AUTH_CALENDAR_EVENTS.freeze
15
+
16
+ def self.prepare_authorizer
17
+ client_id = ::MEETALENDAR_CREDENTIALS_GOOGLE_CALENDAR_CLIENT_ID
18
+ token_store = Google::Auth::Stores::DbTokenStore.new
19
+ authorizer = Google::Auth::UserAuthorizer.new client_id, GOOGLE_CALENDAR_AUTH_SCOPE, token_store
20
+ end
21
+
22
+ def self.get_authorization_url
23
+ self.prepare_authorizer.get_authorization_url base_url: GOOGLE_CALENDAR_AUTH_OOB_URI
24
+ end
25
+
26
+ def self.authorize_and_remember(key_code)
27
+ authorizer = self.prepare_authorizer
28
+ user_id = "default"
29
+
30
+ begin
31
+ authorizer.get_and_store_credentials_from_code(user_id: user_id, code: key_code, base_url: GOOGLE_CALENDAR_AUTH_OOB_URI)
32
+ rescue => exception
33
+ Rails.logger.error "Authorization of google calendar api failed with exception: #{exception.message}"
34
+ raise ::ActiveResource::UnauthorizedAccess, "Authorization at google calendar failed."
35
+ end
36
+ end
37
+
38
+ def self.authorize
39
+ authorizer = self.prepare_authorizer
40
+ user_id = "default"
41
+ credentials = authorizer.get_credentials user_id
42
+
43
+ if credentials.nil?
44
+ Rails.logger.error "Authorization failed as no google calendar api credentials are present."
45
+ raise ::ActiveResource::UnauthorizedAccess, "Please go to: <host>/admin/meetups in the admin interface of the website and renew the authorization of the calendar api."
46
+ end
47
+ credentials
48
+ end
49
+
50
+ def self.add_to_key(current_key_data, to_add_data)
51
+ return_key_data = current_key_data.nil? ? [] : current_key_data.class == [].class ? current_key_data : [].push(current_key_data)
52
+ return_key_data.push(to_add_data)
53
+ return_key_data
54
+ end
55
+
56
+ def self.get_path_authorized(path, args = {})
57
+ return_hash = {}
58
+ client = HTTPClient.new
59
+ token_store = Google::Auth::Stores::DbTokenStore.new
60
+ loaded_token = token_store.load("meetup")
61
+ if loaded_token.nil?
62
+ Rails.logger.error "Authorization failed as no currently authorized meetup api token was present."
63
+ raise ::ActiveResource::UnauthorizedAccess, "To access this path you need to have authenticated the Meetup API successfully."
64
+ parsed_path = {}.to_s
65
+ else
66
+ # try
67
+ current_tokens = JSON.parse(loaded_token)
68
+ request_uri = "https://api.meetup.com" + path.to_s
69
+ request_query_args = args.merge({"access_token" => (current_tokens["access_token"])})
70
+ result = client.request("GET", request_uri, request_query_args)
71
+
72
+ if result.nil? || result.status == Rack::Utils::SYMBOL_TO_STATUS_CODE[:unauthorized]
73
+ meetup_credentials = ::MEETALENDAR_CREDENTIALS_MEETUP
74
+ request_uri = "https://secure.meetup.com/oauth2/access"
75
+ request_query_args = {"client_id": meetup_credentials["client_id"], "client_secret": meetup_credentials["client_secret"], "grant_type": "refresh_token", "refresh_token": "#{current_tokens["refresh_token"]}"}
76
+ post_return = client.post_content(request_uri, request_query_args)
77
+
78
+ if post_return.nil? || post_return.status == 401
79
+ Rails.logger.error "Authorization with current token failed and token could not be refreshed. Was authorization to meetup api revoked?"
80
+ raise ::ActiveResource::UnauthorizedAccess, "To access this path you need to have authenticated the Meetup API successfully."
81
+ else
82
+ response = JSON.parse(post_return.to_s)
83
+ token_store.store("meetup", {"auth_id": "meetup", "client_id": meetup_credentials["client_id"], "access_token": response["access_token"], "refresh_token": response["refresh_token"], "scope": "", "expiration_time_millis": response["expires_in"] * 1000}.to_json.to_s)
84
+
85
+ # retry with refreshed token
86
+ current_tokens = JSON.parse(loaded_token)
87
+ request_uri = "https://api.meetup.com" + path.to_s
88
+ request_query_args = args.merge({"access_token" => (current_tokens["access_token"])})
89
+ result = client.request("GET", request_uri, request_query_args)
90
+
91
+ if result.nil? || result.status == 401
92
+ # really no success
93
+ Rails.logger.error "Authorization with current token failed, token was refreshed but authorization still fails. Was authorization to meetup api revoked?"
94
+ raise ::ActiveResource::UnauthorizedAccess, "To access this path you need to have authenticated the Meetup API successfully."
95
+ else
96
+ parsed_path = JSON.parse(result&.body.nil? ? {}.to_s : result.body.to_s)
97
+ end
98
+ end
99
+
100
+ else
101
+ parsed_path = JSON.parse(result&.body.nil? ? {}.to_s : result.body.to_s)
102
+ end
103
+ end
104
+
105
+ parsed_path
106
+ end
107
+
108
+ def self.gather_meetups_in_approved_cities(time_now)
109
+ @meetups = MeetupGroup.all
110
+ group_ids = @meetups.map{ |meetup| meetup.group_id }
111
+ group_ids_approved_cities = @meetups.map{|meetup| ["#{meetup.group_id}", meetup.approved_cities.downcase.split(%r{,\s*})]}.to_h
112
+
113
+ request_result = Comfy::Admin::Meetalendar::MeetupsCalendarSyncer.get_path_authorized("/find/upcoming_events", {"page": 200})
114
+
115
+ upcoming_events = request_result.nil? ? {} : request_result
116
+ upcoming_events = upcoming_events.nil? || upcoming_events.empty? ? [] : upcoming_events["events"]
117
+ upcoming_events_of_groups = upcoming_events.select{|event|
118
+ !event["group"].nil? &&
119
+ group_ids.include?(event["group"]["id"]) &&
120
+ !event["venue"].nil? &&
121
+ group_ids_approved_cities["#{event["group"]["id"]}"].include?(event['venue']['city'].to_s.downcase)}
122
+
123
+ grouped_upcoming_events = upcoming_events_of_groups.group_by{|event| event["group"]["id"]}
124
+ # NOTE(Schau): Very likely i will be able to refactor this to be more clear.
125
+ limited_upcoming_events = Hash[grouped_upcoming_events.map{|k, v| [k, v.select{|event| Time.at(Rational(event["time"].to_i, 1000)) > time_now}.sort_by{|event| event["time"].to_i}.take(2)]}].select{|k, v| v.any?}
126
+ listed_upcoming_events = limited_upcoming_events.map{|k, v| v.first}
127
+ end
128
+
129
+ def self.sync_meetups_to_calendar(listed_upcoming_events)
130
+ calendar_service = Google::Apis::CalendarV3::CalendarService.new
131
+ calendar_service.client_options.application_name = GOOGLE_CALENDAR_AUTH_APPLICATION_NAME
132
+ calendar_service.authorization = authorize
133
+
134
+ listed_upcoming_events.each{ |event|
135
+ if event.key?('venue')
136
+ venue_name_adress = event['venue']['name'] != event['venue']['address_1'] ? "#{event['venue']['name']}, #{event['venue']['address_1']}" : "#{event['venue']['address_1']}"
137
+ location = "#{venue_name_adress}, #{event['venue']['city']}, #{event['venue']['localized_country_name']}"
138
+ else
139
+ if event.key?('link')
140
+ location = event['link'].to_s
141
+ else
142
+ location = ""
143
+ end
144
+ end
145
+
146
+ description = event['description'].to_s + (defined?(event['link']) ? "\nLink: " + event['link'].to_s : "")
147
+ start_date_time = DateTime.parse(Time.at(Rational(event['time'].to_i, 1000)).to_s).to_s
148
+ end_date_time = DateTime.parse(Time.at(Rational(event['time'].to_i + event['duration'].to_i, 1000)).to_s).to_s
149
+
150
+ new_event_hash = {
151
+ id: Digest::MD5.hexdigest(event['id'].to_s),
152
+ summary: event['name'].to_s,
153
+ location: location,
154
+ description: description,
155
+ start: {
156
+ date_time: start_date_time,
157
+ time_zone: Time.zone.name
158
+ },
159
+ end: {
160
+ date_time: end_date_time,
161
+ time_zone: Time.zone.name
162
+ },
163
+ }
164
+
165
+ new_event = Google::Apis::CalendarV3::Event.new(new_event_hash)
166
+ begin
167
+ calendar_service.update_event('primary', new_event.id, new_event)
168
+ rescue # TODO(Schau): If possible, figure out the exact exceptions to minimize "braodness of healing"
169
+ begin
170
+ calendar_service.insert_event('primary', new_event)
171
+ rescue => exception
172
+ Rails.logger.error "An exception occurred while updating or inserting events into the google calendar. Exception: #{exception.message}"
173
+ raise ::ActiveResource::ClientError, "Could not update or insert event into the google calendar."
174
+ end
175
+ end
176
+ }
177
+ end
178
+ end
@@ -0,0 +1,77 @@
1
+ require 'rubygems'
2
+ require 'active_resource'
3
+
4
+ require 'multi_json'
5
+ require_relative "../../../google/auth/store/db_token_store"
6
+
7
+ module Comfy::Admin::Meetalendar::MeetupsControllerLogic
8
+ class Comfy::Admin::Meetalendar::MeetupsControllerLogic::SearchResult
9
+ attr_reader :groups_id_name
10
+ attr_reader :found_upcoming_grouped_events
11
+ attr_reader :found_last_grouped_events
12
+ attr_reader :meetup_groups
13
+
14
+ def initialize(groups_id_name, found_upcoming_grouped_events, found_last_grouped_events, meetup_groups)
15
+ @groups_id_name = groups_id_name
16
+ @found_upcoming_grouped_events = found_upcoming_grouped_events
17
+ @found_last_grouped_events = found_last_grouped_events
18
+ @meetup_groups = meetup_groups
19
+ end
20
+ end
21
+
22
+ def self.authorize_meetup(request, callback_path)
23
+ meetup_credentials = ::MEETALENDAR_CREDENTIALS_MEETUP
24
+
25
+ redirect_url = "https://secure.meetup.com/oauth2/authorize" +
26
+ "?client_id=#{meetup_credentials["client_id"]}" +
27
+ "&response_type=code" +
28
+ "&redirect_uri=#{request.protocol}#{request.host_with_port}#{callback_path.to_s}"
29
+ end
30
+
31
+ def self.callback(code, request, callback_path)
32
+ meetup_credentials = ::MEETALENDAR_CREDENTIALS_MEETUP
33
+
34
+ request_uri = "https://secure.meetup.com/oauth2/access"
35
+ request_query_hash = {"client_id": meetup_credentials["client_id"], "client_secret": meetup_credentials["client_secret"], "grant_type": "authorization_code", "redirect_uri": "#{request.protocol}#{request.host_with_port}#{callback_path.to_s}", "code": "#{code}"}
36
+
37
+ client = HTTPClient.new
38
+ begin
39
+ response = JSON.parse(client.post_content(request_uri, request_query_hash))
40
+ rescue => ex
41
+ Rails.logger.error "Failed to authorize Meetup API. Exception in callback: #{ex.message}"
42
+ raise ::ActiveResource::ClientError, "Failed to authorize Meetup API."
43
+ end
44
+
45
+ if !response.nil?
46
+ token_store = Google::Auth::Stores::DbTokenStore.new
47
+ token_store.store("meetup", {"auth_id": "meetup", "client_id": meetup_credentials["client_id"], "access_token": response["access_token"], "refresh_token": response["refresh_token"], "scope": "", "expiration_time_millis": response["expires_in"] * 1000}.to_json.to_s)
48
+ end
49
+ end
50
+
51
+ def self.search_result(parameters, time_now)
52
+ group_params = JSON.parse(parameters.nil? ? {} : parameters)
53
+
54
+ request_result = Comfy::Admin::Meetalendar::MeetupsCalendarSyncer.get_path_authorized("/find/groups", group_params.merge({"fields" => "last_event"}))
55
+ groups = request_result.nil? ? {} : request_result
56
+
57
+ groups_id_name = groups.map{|g| {id: g["id"].to_i, name: g["name"].to_s, link: g["link"].to_s} }
58
+ group_ids = groups.map{|g| g["id"]}
59
+ request_result = Comfy::Admin::Meetalendar::MeetupsCalendarSyncer.get_path_authorized("/find/upcoming_events", {"page": 200})
60
+ upcoming_events = request_result.nil? ? [] : request_result["events"]
61
+
62
+ upcoming_events_of_groups = upcoming_events.select{|e| !e["group"].nil? && group_ids.include?(e["group"]["id"])}
63
+
64
+ grouped_upcoming_events = upcoming_events_of_groups.group_by{|e| e["group"]["id"]}
65
+ limited_upcoming_events = Hash[grouped_upcoming_events.map{|k, v| [k, v.select{|e| Time.at(Rational(e["time"], 1000)) > time_now}.sort_by{|e| e["time"]}.take(2)]}].select{|k, v| v.any?}
66
+
67
+ found_upcoming_grouped_events = limited_upcoming_events
68
+
69
+ last_events = Hash[groups.group_by{|g| g["id"]}.map{|k, v| [k, v.map{|g| g["last_event"]}]}].select{|k, v| v.any?}
70
+ found_last_grouped_events = last_events
71
+
72
+ meetup_groups = groups.map{|g| Comfy::Admin::Meetalendar::MeetupGroup.new({"group_id": g["id"], "name": g["name"], "approved_cities": "", "group_link": g["link"]})}
73
+
74
+ search_result = Comfy::Admin::Meetalendar::MeetupsControllerLogic::SearchResult.new(groups_id_name, found_upcoming_grouped_events, found_last_grouped_events, meetup_groups)
75
+ end
76
+
77
+ end
@@ -0,0 +1,37 @@
1
+ require "googleauth/token_store"
2
+
3
+ module Google
4
+ module Auth
5
+ module Stores
6
+ class DbTokenStore < Google::Auth::TokenStore
7
+ # (see Google::Auth::Stores::TokenStore#load)
8
+ def load id
9
+ credentials = Comfy::Admin::Meetalendar::AuthCredential.find_by(auth_id: id)
10
+ !credentials.nil? ?
11
+ {
12
+ # NOTE(Schau): The encryption algorithm must decipher the encrypted tokens! It does so in this function call: credentials.access_token and credentials.refresh_token
13
+ auth_id: credentials.auth_id,
14
+ client_id: credentials.client_id,
15
+ access_token: credentials.access_token,
16
+ refresh_token: credentials.refresh_token,
17
+ scope: credentials.scope,
18
+ expiration_time_millis: credentials.expiration_time_millis
19
+ }.to_json.to_s
20
+ : nil
21
+ end
22
+
23
+ # (see Google::Auth::Stores::TokenStore#store)
24
+ def store id, token
25
+ token_hash = JSON.parse(token).symbolize_keys
26
+ Comfy::Admin::Meetalendar::AuthCredential.find_or_initialize_by(auth_id: id).update(token_hash)
27
+ end
28
+
29
+ # (see Google::Auth::Stores::TokenStore#delete)
30
+ def delete id
31
+ credentials = User.find_by(auth_id: id)
32
+ credentials.destroy
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,6 @@
1
+ . Enter Returned Key Code here:
2
+ = text_field_tag "key_code", nil, class: "custom-control"
3
+
4
+ = form.form_actions do
5
+ = form.submit "Save Key", class: "btn btn-primary"
6
+ = link_to 'Cancel', comfy_admin_meetalendar_meetups_path(@site), class: "btn btn-link"
@@ -0,0 +1,6 @@
1
+ . Currently approved cities:
2
+ = text_field_tag "edited_approved_cities", @meetup.approved_cities.to_s, class: "custom-control"
3
+
4
+ = form.form_actions do
5
+ = form.submit "Save Edited", class: "btn btn-primary"
6
+ = link_to 'Cancel', edit_comfy_admin_meetalendar_meetup_path(@site), class: "btn btn-link"
@@ -0,0 +1,19 @@
1
+ ruby:
2
+ group_params = '{
3
+ "country": "DE",
4
+ "state": "Sachsen",
5
+ "city": "Dresden",
6
+ "category_id": 34,
7
+ "page": 200
8
+ }'
9
+
10
+ = form.text_area :parameters, cols: 80, rows: 21, value: group_params
11
+
12
+ / TODO(Schau): One could change the text area with the json approach to text fields so it equals the meetup api (and web console test kit) more closely.
13
+ / = form.text_field :city
14
+ / = form.text_field :category_id
15
+ / ...
16
+
17
+ = form.form_actions do
18
+ = form.submit "Search for Meetup Groups", class: "btn btn-primary"
19
+ = link_to 'Cancel', comfy_admin_meetalendar_meetups_path(@site), class: "btn btn-link"
@@ -0,0 +1,57 @@
1
+ / TODO(Schau): How to integrate pagination into this form view? (Is this even possible with data that doesn't live in the database?)
2
+
3
+ ul.list
4
+ - @groups_id_name&.each do |group|
5
+ li
6
+ .row
7
+ .col-md-12.d-flex.item
8
+ .item-content.meetup_group
9
+
10
+ .item-title
11
+ .inputs
12
+ .checkbox_label
13
+ .col-md-8 class="custom-control custom-checkbox float-left"
14
+ = check_box_tag "[selected_groups_ids][]", "#{group[:id]}", false, id: "group_#{group[:id]}", class: "custom-control-input"
15
+ = label_tag "group_#{group[:id]}", group[:name], class: "custom-control-label"
16
+ = hidden_field_tag "[groups]#{group[:id]}[name]", group[:name]
17
+ = hidden_field_tag "[groups]#{group[:id]}[group_link]", group[:link]
18
+ .col-md-4
19
+ = link_to "View on Meetup", group[:link], class: 'btn btn-secondary float-right ml-4'
20
+ .approved_cities
21
+ = text_field_tag "[groups]#{group[:id]}[approved_cities]", nil, class: "custom-control"
22
+ / NOTE(Schau): Link_to's that look like buttons, have the city names that where found in the venue hash and on click add that city name to the input field followed by a coma
23
+
24
+ .item_meta
25
+ / TODO(Schau): Make all additional info unfoldable
26
+ .events
27
+
28
+ .past_events
29
+ h5 Last Event
30
+ - if @found_last_grouped_events&.key?(group[:id])
31
+ - @found_last_grouped_events[group[:id]].each do |event|
32
+ .event
33
+ .name = event["name"].to_s
34
+ .time
35
+ .start = l(Time.at(Rational(event["time"].to_i, 1000), Rational(event["utc_offset"].to_i, 24*60*601000)), format: :long)
36
+ .yes_rsvp_count = event["yes_rsvp_count"].to_s + " Teilnemher"
37
+
38
+ span class="border-top my-3"
39
+
40
+ .upcoming_events
41
+ h5 Upcoming
42
+ - if @found_upcoming_grouped_events&.key?(group[:id])
43
+ - @found_upcoming_grouped_events[group[:id]].each do |event|
44
+ .event
45
+ .name = link_to event["name"], event["link"]
46
+ .time
47
+ .city = "In ".to_s + event.dig("venue", "city").to_s + ", Von ".to_s
48
+ .start = l(Time.at(event["time"].to_i.div(1000)), format: :long)
49
+ .to bis
50
+ .end = l(Time.at((event["time"].to_i + event["duration"].to_i).div(1000)), format: :short)
51
+ / TODO(Schau): One could make the city name of the found events clickable so that the user easily can add this city's name to the list of approved cities. (One should research if Meetup has a way to represent each city on the planet with an id that the system gave this city's name. (Or Meetup realy relies on strings, then this doesn't quite make the effort worth.))
52
+ .description
53
+ = sanitize(event["description"])
54
+
55
+ = form.form_actions do
56
+ = form.submit "Add selected Meetup Groups", class: "btn btn-primary"
57
+ = link_to 'Cancel', comfy_admin_meetalendar_meetups_path(@site), class: "btn btn-link"
@@ -0,0 +1,6 @@
1
+ .page-header
2
+ h2= 'Authorize Calendar Access'
3
+
4
+ .goto_url = link_to "Please follow this link and grant this application access to your calendar.", @goto_url, target: "_blank"
5
+ = comfy_form_with url: :authorize_calendar_comfy_admin_meetalendar_meetups, method: :get do |authorize_calendar|
6
+ = render 'authorize_calendar', form: authorize_calendar
@@ -0,0 +1,11 @@
1
+ .page-header
2
+ h2 Edit Meetup
3
+
4
+ .row
5
+ .col-md-8.item
6
+ .item-content
7
+ .item-title
8
+ = link_to @meetup.name, @meetup.group_link, target: "_blank"
9
+ .item_meta
10
+ = comfy_form_with url: :comfy_admin_meetalendar_meetup, method: :patch do |edit|
11
+ = render 'edit', form: edit
@@ -0,0 +1,26 @@
1
+ .page-header
2
+ = link_to 'Add Groups', search_mask_comfy_admin_meetalendar_meetups_path(@site), class: 'btn btn-primary float-right ml-1'
3
+ = link_to 'Authorize Calendar', authorize_calendar_comfy_admin_meetalendar_meetups_path(@site), class: 'btn btn-secondary float-right ml-1'
4
+ = link_to 'Authorize Meetup', authorize_meetup_comfy_admin_meetalendar_meetups_path(@site), class: 'btn btn-secondary float-right'
5
+ h2= 'Subscribed Meetup Groups'
6
+
7
+
8
+ = paginate @meetups, theme: 'comfy'
9
+
10
+ ul.list
11
+ - @meetups&.each do |meetup_group|
12
+ li
13
+ .row
14
+ .col-md-8.item
15
+ .item-content
16
+ .item-title
17
+ = link_to meetup_group.name, meetup_group.group_link, target: "_blank"
18
+ .item_meta
19
+ = "Approved Cities: " + meetup_group.approved_cities.to_s
20
+
21
+ .col-md-4.d-flex.align-items-center.justify-content-md-end
22
+ .btn-group.btn-group-sm
23
+ = link_to 'Edit', edit_comfy_admin_meetalendar_meetup_path(@site, meetup_group), class: 'btn btn-outline-secondary'
24
+ = link_to 'Delete', comfy_admin_meetalendar_meetup_path(@site, meetup_group), method: :delete, data: {confirm: 'Are you sure?'}, class: 'btn btn-danger'
25
+
26
+ = paginate @meetups, theme: 'comfy'