cal-invite 0.1.2 → 0.1.4
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.
- checksums.yaml +4 -4
- data/.DS_Store +0 -0
- data/.rdoc_options +85 -0
- data/CACHING.md +148 -0
- data/CHANGELOG.md +16 -0
- data/README.md +154 -17
- data/Rakefile +26 -0
- data/lib/.DS_Store +0 -0
- data/lib/cal_invite/.DS_Store +0 -0
- data/lib/cal_invite/caching.rb +74 -0
- data/lib/cal_invite/configuration.rb +67 -3
- data/lib/cal_invite/event.rb +144 -53
- data/lib/cal_invite/providers/base_provider.rb +73 -56
- data/lib/cal_invite/providers/google.rb +60 -4
- data/lib/cal_invite/providers/ical.rb +107 -16
- data/lib/cal_invite/providers/ics.rb +107 -18
- data/lib/cal_invite/providers/ics_content.rb +184 -0
- data/lib/cal_invite/providers/office365.rb +64 -10
- data/lib/cal_invite/providers/outlook.rb +70 -6
- data/lib/cal_invite/providers/yahoo.rb +64 -4
- data/lib/cal_invite/providers.rb +9 -2
- data/lib/cal_invite/version.rb +1 -1
- data/lib/cal_invite.rb +39 -0
- data/lib/tasks/rdoc.rake +13 -0
- metadata +54 -4
@@ -1,13 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# lib/cal_invite/configuration.rb
|
2
|
-
|
4
|
+
# Configuration class for the CalInvite gem.
|
5
|
+
# Handles all configurable options including cache settings, timezone, and webhook secrets.
|
6
|
+
#
|
7
|
+
# @attr_reader [ActiveSupport::Cache::Store, nil] cache_store The cache store to use
|
8
|
+
# @attr_reader [String] cache_prefix The prefix to use for cache keys
|
9
|
+
# @attr_reader [Integer] cache_expires_in The default cache expiration time in seconds
|
10
|
+
# @attr_reader [String, nil] webhook_secret The secret key for webhook verification
|
11
|
+
# @attr_reader [String] timezone The default timezone for events
|
12
|
+
module CalInvite
|
3
13
|
class Configuration
|
4
|
-
|
14
|
+
attr_reader :cache_store, :cache_prefix, :cache_expires_in, :webhook_secret, :timezone
|
5
15
|
|
16
|
+
# Initializes a new Configuration instance with default values.
|
6
17
|
def initialize
|
7
|
-
@cache_store =
|
18
|
+
@cache_store = nil
|
8
19
|
@cache_prefix = 'cal_invite'
|
20
|
+
@cache_expires_in = 24.hours # now this will work with active_support loaded
|
9
21
|
@webhook_secret = nil
|
10
22
|
@timezone = 'UTC'
|
11
23
|
end
|
24
|
+
|
25
|
+
# Sets the cache store to use for caching calendar URLs.
|
26
|
+
#
|
27
|
+
# @param store [Symbol, #read, #write, #delete] The cache store to use
|
28
|
+
# @raise [ArgumentError] If an unsupported cache store is provided
|
29
|
+
#
|
30
|
+
# @example Set memory store
|
31
|
+
# config.cache_store = :memory_store
|
32
|
+
def cache_store=(store)
|
33
|
+
@cache_store = case store
|
34
|
+
when :memory_store
|
35
|
+
ActiveSupport::Cache::MemoryStore.new
|
36
|
+
when :null_store
|
37
|
+
ActiveSupport::Cache::NullStore.new
|
38
|
+
when Symbol
|
39
|
+
raise ArgumentError, "Unsupported cache store: #{store}"
|
40
|
+
else
|
41
|
+
# Allow custom cache store objects that respond to read/write/delete
|
42
|
+
unless store.respond_to?(:read) && store.respond_to?(:write) && store.respond_to?(:delete)
|
43
|
+
raise ArgumentError, "Custom cache store must implement read/write/delete methods"
|
44
|
+
end
|
45
|
+
store
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Sets the prefix used for cache keys.
|
50
|
+
#
|
51
|
+
# @param prefix [#to_s] The prefix to use for cache keys
|
52
|
+
def cache_prefix=(prefix)
|
53
|
+
@cache_prefix = prefix.to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
# Sets the default cache expiration time.
|
57
|
+
#
|
58
|
+
# @param duration [#to_i] The duration in seconds
|
59
|
+
def cache_expires_in=(duration)
|
60
|
+
@cache_expires_in = duration.to_i
|
61
|
+
end
|
62
|
+
|
63
|
+
# Sets the webhook secret for verification.
|
64
|
+
#
|
65
|
+
# @param secret [String, nil] The secret key
|
66
|
+
def webhook_secret=(secret)
|
67
|
+
@webhook_secret = secret
|
68
|
+
end
|
69
|
+
|
70
|
+
# Sets the default timezone for events.
|
71
|
+
#
|
72
|
+
# @param tz [#to_s] The timezone identifier
|
73
|
+
def timezone=(tz)
|
74
|
+
@timezone = tz.to_s
|
75
|
+
end
|
12
76
|
end
|
13
77
|
end
|
data/lib/cal_invite/event.rb
CHANGED
@@ -1,8 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'digest'
|
4
|
+
|
3
5
|
# lib/cal_invite/event.rb
|
6
|
+
# Represents a calendar event with all its attributes and generation capabilities.
|
7
|
+
#
|
8
|
+
# @attr_accessor [String] title The title of the event
|
9
|
+
# @attr_accessor [Time] start_time The start time of the event
|
10
|
+
# @attr_accessor [Time] end_time The end time of the event
|
11
|
+
# @attr_accessor [String] description The description of the event
|
12
|
+
# @attr_accessor [String] location The location of the event
|
13
|
+
# @attr_accessor [String] url The URL associated with the event
|
14
|
+
# @attr_accessor [Array<String>] attendees The list of attendee email addresses
|
15
|
+
# @attr_accessor [String] timezone The timezone for the event
|
16
|
+
# @attr_accessor [Boolean] show_attendees Whether to include attendees in calendar invites
|
17
|
+
# @attr_accessor [String] notes Additional notes for the event
|
18
|
+
# @attr_accessor [Array<Hash>] multi_day_sessions Sessions for multi-day events
|
19
|
+
# @attr_accessor [Boolean] all_day Whether this is an all-day event
|
4
20
|
module CalInvite
|
5
|
-
# Represents a calendar event with its properties and validation rules
|
6
21
|
class Event
|
7
22
|
attr_accessor :title,
|
8
23
|
:start_time,
|
@@ -17,7 +32,7 @@ module CalInvite
|
|
17
32
|
:multi_day_sessions,
|
18
33
|
:all_day
|
19
34
|
|
20
|
-
#
|
35
|
+
# Initializes a new Event instance with the given attributes.
|
21
36
|
#
|
22
37
|
# @param attributes [Hash] The attributes to initialize the event with
|
23
38
|
# @option attributes [String] :title The event title
|
@@ -26,27 +41,20 @@ module CalInvite
|
|
26
41
|
# @option attributes [String] :description The event description
|
27
42
|
# @option attributes [String] :location The event location
|
28
43
|
# @option attributes [String] :url The event URL
|
29
|
-
# @option attributes [Array<String>] :attendees
|
30
|
-
# @option attributes [String] :timezone The event timezone
|
31
|
-
# @option attributes [Boolean] :show_attendees Whether to show attendees
|
32
|
-
# @option attributes [String] :notes Additional notes
|
33
|
-
# @option attributes [Array<Hash>] :multi_day_sessions
|
34
|
-
# @option attributes [Boolean] :all_day Whether
|
35
|
-
#
|
44
|
+
# @option attributes [Array<String>] :attendees The event attendees
|
45
|
+
# @option attributes [String] :timezone ('UTC') The event timezone
|
46
|
+
# @option attributes [Boolean] :show_attendees (false) Whether to show attendees
|
47
|
+
# @option attributes [String] :notes Additional notes
|
48
|
+
# @option attributes [Array<Hash>] :multi_day_sessions Multi-day session details
|
49
|
+
# @option attributes [Boolean] :all_day (false) Whether it's an all-day event
|
50
|
+
#
|
51
|
+
# @raise [ArgumentError] If required attributes are missing
|
36
52
|
def initialize(attributes = {})
|
37
53
|
@show_attendees = attributes.delete(:show_attendees) || false
|
38
54
|
@timezone = attributes.delete(:timezone) || 'UTC'
|
39
55
|
@multi_day_sessions = attributes.delete(:multi_day_sessions) || []
|
40
56
|
@all_day = attributes.delete(:all_day) || false
|
41
57
|
|
42
|
-
# Convert times to UTC before storing
|
43
|
-
if attributes[:start_time]
|
44
|
-
attributes[:start_time] = ensure_utc(attributes[:start_time])
|
45
|
-
end
|
46
|
-
if attributes[:end_time]
|
47
|
-
attributes[:end_time] = ensure_utc(attributes[:end_time])
|
48
|
-
end
|
49
|
-
|
50
58
|
attributes.each do |key, value|
|
51
59
|
send("#{key}=", value) if respond_to?("#{key}=")
|
52
60
|
end
|
@@ -54,62 +62,72 @@ module CalInvite
|
|
54
62
|
validate!
|
55
63
|
end
|
56
64
|
|
57
|
-
#
|
65
|
+
# Generates a calendar URL for the specified provider.
|
58
66
|
#
|
59
67
|
# @param provider [Symbol] The calendar provider to generate the URL for
|
60
68
|
# @return [String] The generated calendar URL
|
61
|
-
# @raise [ArgumentError]
|
62
|
-
|
69
|
+
# @raise [ArgumentError] If required event attributes are missing
|
70
|
+
#
|
71
|
+
# @example Generate a Google Calendar URL
|
72
|
+
# event.generate_calendar_url(:google)
|
73
|
+
#
|
74
|
+
# @example Generate an Outlook Calendar URL
|
75
|
+
# event.generate_calendar_url(:outlook)
|
76
|
+
def generate_calendar_url(provider)
|
63
77
|
validate!
|
78
|
+
|
79
|
+
if caching_enabled?
|
80
|
+
cache_key = cache_key_for(provider)
|
81
|
+
cached_url = fetch_from_cache(cache_key)
|
82
|
+
return cached_url if cached_url
|
83
|
+
end
|
84
|
+
|
85
|
+
# Generate the URL
|
64
86
|
provider_class = CalInvite::Providers.const_get(capitalize_provider(provider.to_s))
|
65
87
|
generator = provider_class.new(self)
|
66
|
-
generator.generate
|
67
|
-
end
|
88
|
+
url = generator.generate
|
68
89
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
def localize_time(time)
|
74
|
-
return time unless time
|
75
|
-
# When timezone is UTC, we should preserve the UTC time
|
76
|
-
# without any conversion
|
77
|
-
return time if timezone == 'UTC'
|
78
|
-
time.getlocal(timezone_offset)
|
90
|
+
# Cache the result if caching is enabled
|
91
|
+
write_to_cache(cache_key, url) if caching_enabled?
|
92
|
+
|
93
|
+
url
|
79
94
|
end
|
80
95
|
|
81
|
-
#
|
96
|
+
# Updates the event attributes with new values.
|
82
97
|
#
|
83
|
-
# @
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
98
|
+
# @param new_attributes [Hash] The new attributes to update
|
99
|
+
# @return [void]
|
100
|
+
# @raise [ArgumentError] If the updated attributes make the event invalid
|
101
|
+
#
|
102
|
+
# @example Update event title and time
|
103
|
+
# event.update_attributes(
|
104
|
+
# title: "Updated Meeting",
|
105
|
+
# start_time: Time.now + 3600
|
106
|
+
# )
|
107
|
+
def update_attributes(new_attributes)
|
108
|
+
new_attributes.each do |key, value|
|
109
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
89
110
|
end
|
90
|
-
end
|
91
111
|
|
92
|
-
|
93
|
-
|
94
|
-
def ensure_utc(time)
|
95
|
-
return nil unless time
|
96
|
-
time.is_a?(Time) ? time.utc : Time.parse(time.to_s).utc
|
112
|
+
invalidate_cache if caching_enabled?
|
113
|
+
validate!
|
97
114
|
end
|
98
115
|
|
99
|
-
|
100
|
-
return '+00:00' if timezone == 'UTC'
|
101
|
-
timezone # assume timezone is already in offset format
|
102
|
-
end
|
116
|
+
private
|
103
117
|
|
118
|
+
# Capitalizes each part of the provider name.
|
119
|
+
#
|
120
|
+
# @param string [String] The provider name to capitalize
|
121
|
+
# @return [String] The capitalized provider name
|
122
|
+
# @example
|
123
|
+
# capitalize_provider('google_calendar') # => "GoogleCalendar"
|
104
124
|
def capitalize_provider(string)
|
105
|
-
# Handles both simple capitalization (ical -> Ical)
|
106
|
-
# and compound names (office365 -> Office365)
|
107
125
|
string.split('_').map(&:capitalize).join
|
108
126
|
end
|
109
127
|
|
110
|
-
#
|
128
|
+
# Validates the event attributes.
|
111
129
|
#
|
112
|
-
# @raise [ArgumentError]
|
130
|
+
# @raise [ArgumentError] If required attributes are missing or invalid
|
113
131
|
def validate!
|
114
132
|
raise ArgumentError, "Title is required" if title.nil? || title.strip.empty?
|
115
133
|
|
@@ -118,5 +136,78 @@ module CalInvite
|
|
118
136
|
raise ArgumentError, "End time is required for non-all-day events" if end_time.nil?
|
119
137
|
end
|
120
138
|
end
|
139
|
+
|
140
|
+
# Checks if caching is enabled in the configuration.
|
141
|
+
#
|
142
|
+
# @return [Boolean] true if caching is enabled, false otherwise
|
143
|
+
def caching_enabled?
|
144
|
+
CalInvite.configuration &&
|
145
|
+
CalInvite.configuration.respond_to?(:cache_store) &&
|
146
|
+
CalInvite.configuration.cache_store
|
147
|
+
end
|
148
|
+
|
149
|
+
# Generates a cache key for the event and provider combination.
|
150
|
+
#
|
151
|
+
# @param provider [Symbol] The calendar provider
|
152
|
+
# @return [String, nil] The cache key or nil if caching is disabled
|
153
|
+
def cache_key_for(provider)
|
154
|
+
return nil unless caching_enabled?
|
155
|
+
|
156
|
+
attributes_hash = Digest::MD5.hexdigest(
|
157
|
+
[
|
158
|
+
title,
|
159
|
+
start_time&.to_i,
|
160
|
+
end_time&.to_i,
|
161
|
+
description,
|
162
|
+
location,
|
163
|
+
url,
|
164
|
+
attendees,
|
165
|
+
timezone,
|
166
|
+
show_attendees,
|
167
|
+
notes,
|
168
|
+
multi_day_sessions,
|
169
|
+
all_day,
|
170
|
+
provider
|
171
|
+
].map(&:to_s).join('|')
|
172
|
+
)
|
173
|
+
|
174
|
+
"cal_invite:event:#{attributes_hash}"
|
175
|
+
end
|
176
|
+
|
177
|
+
# Retrieves a value from the cache store.
|
178
|
+
#
|
179
|
+
# @param key [String] The cache key
|
180
|
+
# @return [String, nil] The cached value or nil if not found
|
181
|
+
def fetch_from_cache(key)
|
182
|
+
return nil unless key && caching_enabled?
|
183
|
+
CalInvite.configuration.cache_store.read(key)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Writes a value to the cache store.
|
187
|
+
#
|
188
|
+
# @param key [String] The cache key
|
189
|
+
# @param value [String] The value to cache
|
190
|
+
# @return [void]
|
191
|
+
def write_to_cache(key, value)
|
192
|
+
return unless key && caching_enabled?
|
193
|
+
|
194
|
+
expires_in = CalInvite.configuration&.cache_expires_in
|
195
|
+
CalInvite.configuration.cache_store.write(
|
196
|
+
key,
|
197
|
+
value,
|
198
|
+
expires_in: expires_in
|
199
|
+
)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Invalidates all cached URLs for this event.
|
203
|
+
#
|
204
|
+
# @return [void]
|
205
|
+
def invalidate_cache
|
206
|
+
return unless caching_enabled?
|
207
|
+
|
208
|
+
if CalInvite.configuration.cache_store.respond_to?(:delete_matched)
|
209
|
+
CalInvite.configuration.cache_store.delete_matched("cal_invite:event:*")
|
210
|
+
end
|
211
|
+
end
|
121
212
|
end
|
122
213
|
end
|
@@ -1,68 +1,85 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# lib/
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
attr_reader :event
|
3
|
+
# app/lib/base_provider.rb
|
4
|
+
# Base class for calendar providers that implements common functionality
|
5
|
+
# and defines the interface that all providers must implement.
|
6
|
+
#
|
7
|
+
# @abstract Subclass and override {#generate} to implement a calendar provider
|
8
|
+
class BaseProvider
|
9
|
+
attr_reader :event
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
11
|
+
# Initialize a new calendar provider
|
12
|
+
#
|
13
|
+
# @param event [CalInvite::Event] The event to generate a calendar URL for
|
14
|
+
def initialize(event)
|
15
|
+
@event = event
|
16
|
+
end
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
18
|
+
# Generate a calendar URL or content for the event.
|
19
|
+
# This method must be implemented by all provider subclasses.
|
20
|
+
#
|
21
|
+
# @abstract
|
22
|
+
# @return [String] The generated calendar URL or content
|
23
|
+
# @raise [NotImplementedError] if the provider class doesn't implement this method
|
24
|
+
def generate
|
25
|
+
raise NotImplementedError, "#{self.class} must implement #generate"
|
26
|
+
end
|
27
27
|
|
28
|
-
|
28
|
+
protected
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
30
|
+
# URL encode a string for use in calendar URLs
|
31
|
+
#
|
32
|
+
# @param str [#to_s] The string to encode
|
33
|
+
# @return [String] The URL encoded string
|
34
|
+
def url_encode(str)
|
35
|
+
URI.encode_www_form_component(str.to_s)
|
36
|
+
end
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
parts.join("\n\n")
|
47
|
-
end
|
38
|
+
# Format the event description
|
39
|
+
# @return [String, nil] The formatted description or nil if no content
|
40
|
+
def format_description
|
41
|
+
parts = []
|
42
|
+
parts << event.description if event.description
|
43
|
+
parts << "Notes: #{event.notes}" if event.notes
|
44
|
+
parts.join("\n\n")
|
45
|
+
end
|
48
46
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
47
|
+
# Get just the physical location
|
48
|
+
# @return [String, nil] The location or nil if not present
|
49
|
+
def format_location
|
50
|
+
event.location
|
51
|
+
end
|
52
|
+
|
53
|
+
# Get the URL for virtual meetings
|
54
|
+
# @return [String, nil] The URL or nil if not present
|
55
|
+
def format_url
|
56
|
+
event.url
|
57
|
+
end
|
58
58
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
59
|
+
# Format description including URL if present
|
60
|
+
# @return [String, nil] The formatted description with URL
|
61
|
+
def format_description_with_url
|
62
|
+
parts = []
|
63
|
+
parts << format_description if format_description
|
64
|
+
parts << "Virtual Meeting URL: #{format_url}" if format_url
|
65
|
+
parts.join("\n\n")
|
66
|
+
end
|
67
|
+
|
68
|
+
def add_optional_params(params)
|
69
|
+
params[:description] = url_encode(format_description_with_url) if format_description || format_url
|
70
|
+
params[:location] = url_encode(format_location) if format_location
|
71
|
+
|
72
|
+
if event.show_attendees && event.attendees&.any?
|
73
|
+
params[:attendees] = event.attendees.join(',')
|
66
74
|
end
|
75
|
+
|
76
|
+
params
|
77
|
+
end
|
78
|
+
|
79
|
+
# Get the list of attendees if showing attendees is enabled
|
80
|
+
# @return [Array<String>] The list of attendees or empty array if disabled/none present
|
81
|
+
def attendees_list
|
82
|
+
return [] unless event.show_attendees && event.attendees&.any?
|
83
|
+
event.attendees
|
67
84
|
end
|
68
85
|
end
|
@@ -3,7 +3,34 @@
|
|
3
3
|
# lib/cal_invite/providers/google.rb
|
4
4
|
module CalInvite
|
5
5
|
module Providers
|
6
|
+
# Google Calendar provider for generating event URLs.
|
7
|
+
# This provider generates URLs that open the Google Calendar event creation page
|
8
|
+
# with pre-filled event details.
|
9
|
+
#
|
10
|
+
# @example Creating a regular event URL
|
11
|
+
# event = CalInvite::Event.new(
|
12
|
+
# title: "Team Meeting",
|
13
|
+
# start_time: Time.now,
|
14
|
+
# end_time: Time.now + 3600
|
15
|
+
# )
|
16
|
+
# google = CalInvite::Providers::Google.new(event)
|
17
|
+
# url = google.generate
|
18
|
+
#
|
19
|
+
# @example Creating an all-day event URL
|
20
|
+
# event = CalInvite::Event.new(
|
21
|
+
# title: "Company Holiday",
|
22
|
+
# all_day: true,
|
23
|
+
# start_time: Date.today,
|
24
|
+
# end_time: Date.today + 1
|
25
|
+
# )
|
26
|
+
# url = CalInvite::Providers::Google.new(event).generate
|
6
27
|
class Google < BaseProvider
|
28
|
+
# Generates a Google Calendar URL for the event.
|
29
|
+
# Handles both regular and all-day events appropriately.
|
30
|
+
#
|
31
|
+
# @return [String] The generated Google Calendar URL
|
32
|
+
# @see #generate_all_day_event
|
33
|
+
# @see #generate_single_event
|
7
34
|
def generate
|
8
35
|
if event.all_day
|
9
36
|
generate_all_day_event
|
@@ -14,36 +41,53 @@ module CalInvite
|
|
14
41
|
|
15
42
|
private
|
16
43
|
|
44
|
+
# Generates a URL for an all-day event.
|
45
|
+
# Uses a simpler date format without time components.
|
46
|
+
#
|
47
|
+
# @return [String] The Google Calendar URL for an all-day event
|
48
|
+
# @see #format_all_day_dates
|
17
49
|
def generate_all_day_event
|
18
50
|
params = {
|
19
51
|
action: 'TEMPLATE',
|
20
52
|
text: url_encode(event.title),
|
21
53
|
dates: format_all_day_dates
|
22
54
|
}
|
23
|
-
|
24
55
|
add_optional_params(params)
|
25
56
|
build_url(params)
|
26
57
|
end
|
27
58
|
|
59
|
+
# Generates a URL for a regular (time-specific) event.
|
60
|
+
#
|
61
|
+
# @return [String] The Google Calendar URL for a regular event
|
62
|
+
# @raise [ArgumentError] If start_time or end_time is missing
|
63
|
+
# @see #format_dates
|
28
64
|
def generate_single_event
|
29
65
|
params = {
|
30
66
|
action: 'TEMPLATE',
|
31
67
|
text: url_encode(event.title),
|
32
68
|
dates: format_dates
|
33
69
|
}
|
34
|
-
|
35
70
|
add_optional_params(params)
|
36
71
|
build_url(params)
|
37
72
|
end
|
38
73
|
|
74
|
+
# Formats dates for an all-day event according to Google Calendar's requirements.
|
75
|
+
# If start_time is not specified, uses current date.
|
76
|
+
# If end_time is not specified, adds one day to start_time.
|
77
|
+
#
|
78
|
+
# @return [String] Date range in format 'YYYYMMDD/YYYYMMDD'
|
39
79
|
def format_all_day_dates
|
40
|
-
# For all-day events, use current date if no start_time specified
|
41
80
|
start_date = event.start_time || Time.now
|
42
81
|
end_date = event.end_time || (start_date + 86400) # Add one day if no end_time
|
43
82
|
|
44
83
|
"#{start_date.strftime('%Y%m%d')}/#{end_date.strftime('%Y%m%d')}"
|
45
84
|
end
|
46
85
|
|
86
|
+
# Formats dates and times according to Google Calendar's requirements.
|
87
|
+
# Times are converted to UTC and formatted appropriately.
|
88
|
+
#
|
89
|
+
# @return [String] Date/time range in format 'YYYYMMDDTHHmmSSZ/YYYYMMDDTHHmmSSZ'
|
90
|
+
# @raise [ArgumentError] If either start_time or end_time is missing
|
47
91
|
def format_dates
|
48
92
|
raise ArgumentError, "Start time is required" unless event.start_time
|
49
93
|
raise ArgumentError, "End time is required" unless event.end_time
|
@@ -54,12 +98,24 @@ module CalInvite
|
|
54
98
|
"#{start_time.utc.strftime('%Y%m%dT%H%M%SZ')}/#{end_time.utc.strftime('%Y%m%dT%H%M%SZ')}"
|
55
99
|
end
|
56
100
|
|
101
|
+
# Adds optional parameters to the URL parameters hash.
|
102
|
+
# Handles description, virtual meeting URL, and location.
|
103
|
+
#
|
104
|
+
# @param params [Hash] The parameters hash to update
|
105
|
+
# @return [Hash] The updated parameters hash with optional parameters added
|
57
106
|
def add_optional_params(params)
|
58
|
-
|
107
|
+
description_parts = []
|
108
|
+
description_parts << format_description if format_description
|
109
|
+
description_parts << "Virtual Meeting URL: #{format_url}" if format_url
|
110
|
+
params[:details] = url_encode(description_parts.join("\n\n")) if description_parts.any?
|
59
111
|
params[:location] = url_encode(format_location) if format_location
|
60
112
|
params
|
61
113
|
end
|
62
114
|
|
115
|
+
# Builds the final Google Calendar URL from the parameters hash.
|
116
|
+
#
|
117
|
+
# @param params [Hash] The parameters to include in the URL
|
118
|
+
# @return [String] The complete Google Calendar URL
|
63
119
|
def build_url(params)
|
64
120
|
query = params.map { |k, v| "#{k}=#{v}" }.join('&')
|
65
121
|
"https://calendar.google.com/calendar/render?#{query}"
|