almanack 1.0.5 → 1.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/almanack.gemspec +1 -0
  4. data/example.ru +4 -4
  5. data/lib/almanack/base.rb +22 -0
  6. data/lib/almanack/calendar.rb +9 -27
  7. data/lib/almanack/configuration.rb +15 -4
  8. data/lib/almanack/event.rb +56 -5
  9. data/lib/almanack/event_source/ical_feed.rb +6 -2
  10. data/lib/almanack/event_source/meetup_group.rb +26 -5
  11. data/lib/almanack/event_source/static.rb +5 -1
  12. data/lib/almanack/representation/ical_feed.rb +59 -0
  13. data/lib/almanack/representation/json_feed.rb +55 -0
  14. data/lib/almanack/serialized_transformation.rb +53 -0
  15. data/lib/almanack/server/environment.rb +38 -0
  16. data/lib/almanack/server/helpers.rb +32 -28
  17. data/lib/almanack/server.rb +23 -37
  18. data/lib/almanack/themes/legacy/views/events.erb +2 -2
  19. data/lib/almanack/themes/starter/views/events.erb +2 -2
  20. data/lib/almanack/version.rb +1 -1
  21. data/lib/almanack.rb +4 -18
  22. data/spec/calendar_spec.rb +10 -3
  23. data/spec/configuration_spec.rb +10 -0
  24. data/spec/event_source/ical_feed_spec.rb +15 -2
  25. data/spec/event_source/meetup_group_spec.rb +23 -2
  26. data/spec/event_source/static_spec.rb +25 -7
  27. data/spec/event_spec.rb +80 -11
  28. data/spec/features/api_feature_spec.rb +24 -0
  29. data/spec/features/calendar_feature_spec.rb +3 -3
  30. data/spec/features/subscription_feature_spec.rb +5 -5
  31. data/spec/representation/json_feed_spec.rb +44 -0
  32. data/spec/serialized_transformation_spec.rb +45 -0
  33. data/spec/spec_helper.rb +2 -0
  34. data/spec/support/event_matchers.rb +1 -1
  35. data/templates/new/config.ru.tt +1 -1
  36. metadata +29 -4
@@ -1,3 +1,4 @@
1
+ require "rack/contrib"
1
2
  require "sinatra"
2
3
  require "sinatra/reloader"
3
4
  require "sass"
@@ -6,67 +7,52 @@ require "almanack"
6
7
  module Almanack
7
8
  class Server < Sinatra::Base
8
9
  require "almanack/server/helpers"
10
+ require "almanack/server/environment"
9
11
 
10
- configure :development do
11
- register Sinatra::Reloader
12
- end
12
+ include Almanack::ServerContext::Environment
13
13
 
14
14
  set :root, -> { Almanack.config.theme_root }
15
15
  set :protection, except: :frame_options
16
- set :feed_path, "feed.ics"
17
-
18
- not_found do
19
- status 404
20
- erb :error
21
- end
22
-
23
- error do
24
- status 500
25
- erb :error
26
- end
16
+ set :feed_path, "feed"
27
17
 
28
- def basename(file)
29
- Pathname(file).split.last.to_s.split(".", 2).first
30
- end
18
+ use Rack::JSONP
31
19
 
32
- def locate_asset(name, within: path)
33
- name = basename(name)
34
- path = settings.root.join(within)
35
- available = Pathname.glob(path.join("*"))
36
- asset = available.find { |path| basename(path) == name }
37
- raise "Could not find stylesheet #{name} inside #{available}" if asset.nil?
38
- asset
20
+ configure :development do
21
+ register Sinatra::Reloader
39
22
  end
40
23
 
41
- def auto_render_template(asset)
42
- renderer = asset.extname.split(".").last
43
- content = asset.read
44
- respond_to?(renderer) ? send(renderer, content) : content
24
+ helpers do
25
+ include Almanack::ServerContext::Helpers
45
26
  end
46
27
 
47
- def auto_render_asset(*args)
48
- auto_render_template locate_asset(*args)
28
+ before do
29
+ register_sass_loadpaths!
49
30
  end
50
31
 
51
- def theme_stylesheet_path
52
- settings.root.join('stylesheets')
32
+ not_found do
33
+ status 404
34
+ erb :error
53
35
  end
54
36
 
55
- before do
56
- if !Sass.load_paths.include?(theme_stylesheet_path)
57
- Sass.load_paths << theme_stylesheet_path
58
- end
37
+ error do
38
+ status 500
39
+ erb :error
59
40
  end
60
41
 
61
42
  get "/" do
62
43
  erb :events
63
44
  end
64
45
 
65
- get "/#{settings.feed_path}" do
46
+ get "/#{settings.feed_path}.ics" do
66
47
  content_type "text/calendar"
67
48
  Almanack.calendar.ical_feed
68
49
  end
69
50
 
51
+ get "/#{settings.feed_path}.json" do
52
+ content_type :json
53
+ Almanack.calendar.json_feed
54
+ end
55
+
70
56
  get "/stylesheets/:name" do
71
57
  content_type :css
72
58
  auto_render_asset params[:name], within: "stylesheets"
@@ -4,8 +4,8 @@
4
4
  <% calendar.events.each do |event| %>
5
5
  <div class="event">
6
6
  <div class="date">
7
- <div class="month"><%= event.start_date.strftime('%a') %></div>
8
- <div class="day"><%= event.start_date.strftime('%d') %></div>
7
+ <div class="month"><%= event.start_time.strftime('%a') %></div>
8
+ <div class="day"><%= event.start_time.strftime('%d') %></div>
9
9
  </div>
10
10
  <div class="details">
11
11
  <h3 class="title"><%= event.title %></h3>
@@ -4,8 +4,8 @@
4
4
  <% calendar.events.each do |event| %>
5
5
  <div class="event">
6
6
  <div class="date">
7
- <div class="month"><%= event.start_date.strftime('%a') %></div>
8
- <div class="day"><%= event.start_date.strftime('%d') %></div>
7
+ <div class="month"><%= event.start_time.strftime('%a') %></div>
8
+ <div class="day"><%= event.start_time.strftime('%d') %></div>
9
9
  </div>
10
10
  <div class="details">
11
11
  <h3 class="title"><%= event.title %></h3>
@@ -1,6 +1,6 @@
1
1
  module Almanack
2
2
  CODENAME = "Garlick"
3
- VERSION = "1.0.5"
3
+ VERSION = "1.1.0.beta1"
4
4
  HOMEPAGE = "https://github.com/Aupajo/almanack"
5
5
  ISSUES = "https://github.com/Aupajo/almanack/issues"
6
6
  end
data/lib/almanack.rb CHANGED
@@ -4,28 +4,14 @@ require "ri_cal"
4
4
  require "addressable/uri"
5
5
  require "faraday"
6
6
 
7
+ require "almanack/base"
7
8
  require "almanack/version"
8
9
  require "almanack/configuration"
9
10
  require "almanack/calendar"
11
+ require "almanack/serialized_transformation"
12
+ require "almanack/representation/ical_feed"
13
+ require "almanack/representation/json_feed"
10
14
  require "almanack/event"
11
15
  require "almanack/event_source/static"
12
16
  require "almanack/event_source/meetup_group"
13
17
  require "almanack/event_source/ical_feed"
14
-
15
- module Almanack
16
- class << self
17
- def config(&block)
18
- @config ||= Configuration.new
19
- yield @config if block_given?
20
- @config
21
- end
22
-
23
- def calendar
24
- @calendar ||= Calendar.new(config)
25
- end
26
-
27
- def reset!
28
- config.reset!
29
- end
30
- end
31
- end
@@ -42,9 +42,9 @@ module Almanack
42
42
 
43
43
  config = Configuration.new
44
44
  config.add_events [
45
- { title: 'Today', start_date: today },
46
- { title: 'Yesterday', start_date: yesterday },
47
- { title: 'Tomorrow', start_date: tomorrow },
45
+ { title: 'Today', start_time: today },
46
+ { title: 'Yesterday', start_time: yesterday },
47
+ { title: 'Tomorrow', start_time: tomorrow },
48
48
  ]
49
49
 
50
50
  calendar = Calendar.new(config)
@@ -60,5 +60,12 @@ module Almanack
60
60
  end
61
61
  end
62
62
 
63
+ describe "#feed_lookahead" do
64
+ it "delegates to the configuration's feed_lookahead" do
65
+ config = double(feed_lookahead: :delegated)
66
+ expect(Calendar.new(config).feed_lookahead).to eq(:delegated)
67
+ end
68
+ end
69
+
63
70
  end
64
71
  end
@@ -37,6 +37,16 @@ module Almanack
37
37
  end
38
38
  end
39
39
 
40
+ describe "#feed_lookahead" do
41
+ specify { expect(Configuration.new.feed_lookahead).to eq(365) }
42
+
43
+ specify "it can be set" do
44
+ config = Configuration.new
45
+ config.feed_lookahead = 30
46
+ expect(config.feed_lookahead).to eq(30)
47
+ end
48
+ end
49
+
40
50
  describe "#add_events" do
41
51
  it "adds a simple event collection event source" do
42
52
  config = Configuration.new
@@ -16,10 +16,23 @@ module Almanack::EventSource
16
16
  end
17
17
  end
18
18
 
19
- start_dates = events.map(&:start_date)
19
+ start_times = events.map(&:start_time)
20
20
 
21
21
  expect(events.length).to eq(15)
22
- expect(events).to all_have_properties(:title, :start_date, :end_date, :description, :location)
22
+ expect(events).to all_have_properties(:title, :start_time, :end_time, :description, :location)
23
+ end
24
+ end
25
+
26
+ describe "#serialized_between" do
27
+ it "returns a hash containing attributes" do
28
+ source = IcalFeed.new 'url'
29
+
30
+ events = [double(serialized: :serialized_event)]
31
+ expect(source).to receive(:events_between).with(:date_range) { events }
32
+
33
+ expect(source.serialized_between(:date_range)).to eq({
34
+ events: [:serialized_event]
35
+ })
23
36
  end
24
37
  end
25
38
 
@@ -17,10 +17,10 @@ module Almanack::EventSource
17
17
  end
18
18
  end
19
19
 
20
- start_dates = events.map(&:start_date)
20
+ start_times = events.map(&:start_time)
21
21
 
22
22
  expect(events.length).to eq(5)
23
- expect(events).to all_have_properties(:title, :start_date, :end_date, :description, :location)
23
+ expect(events).to all_have_properties(:title, :start_time, :end_time, :description, :location)
24
24
  end
25
25
 
26
26
  it "handles a missing location" do
@@ -38,5 +38,26 @@ module Almanack::EventSource
38
38
  end
39
39
  end
40
40
 
41
+ describe "#serialized_between" do
42
+ it "returns a hash containing attributes" do
43
+ feed = MeetupGroup.new(group_urlname: 'The-Foundation-Christchurch',
44
+ key: 'secrettoken',
45
+ connection: Faraday.new)
46
+ serialized = nil
47
+
48
+ Timecop.freeze(2014, 5, 24) do
49
+ VCR.use_cassette('meetup') do
50
+ from = Time.now
51
+ to = from + 30 * 24 * 60 * 60
52
+ serialized = feed.serialized_between(from..to)
53
+ end
54
+ end
55
+
56
+ expect(serialized[:events].length).to eq 5
57
+ expect(serialized[:name]).to eq("The Foundation")
58
+ expect(serialized[:url]).to eq("http://www.meetup.com/The-Foundation-Christchurch")
59
+ end
60
+ end
61
+
41
62
  end
42
63
  end
@@ -2,16 +2,20 @@ require 'spec_helper'
2
2
 
3
3
  module Almanack::EventSource
4
4
  describe Static do
5
+ let(:yesterday) { now - 1 }
6
+ let(:now) { Time.now }
7
+ let(:tomorrow) { now + 1 }
8
+
9
+ around do |test|
10
+ Timecop.freeze(now, &test)
11
+ end
12
+
5
13
  describe "#events_between" do
6
14
  it "returns events between two dates" do
7
- now = Time.now
8
- yesterday = now - 1
9
- tomorrow = now + 1
10
-
11
15
  source = Static.new [
12
- { title: 'Yesterday', start_date: yesterday },
13
- { title: 'Today', start_date: now },
14
- { title: 'Tomorrow', start_date: tomorrow }
16
+ { title: 'Yesterday', start_time: yesterday },
17
+ { title: 'Today', start_time: now },
18
+ { title: 'Tomorrow', start_time: tomorrow }
15
19
  ]
16
20
 
17
21
  expect(source.events_between(yesterday..tomorrow).map(&:title)).to eq(%w( Yesterday Today Tomorrow ))
@@ -19,5 +23,19 @@ module Almanack::EventSource
19
23
  expect(source.events_between(yesterday..now).map(&:title)).to eq(%w( Yesterday Today ))
20
24
  end
21
25
  end
26
+
27
+ describe "#serialized_between" do
28
+ it "returns a hash containing attributes" do
29
+ source = Static.new []
30
+ date_range = yesterday..tomorrow
31
+
32
+ events = [double(serialized: :serialized_event)]
33
+ expect(source).to receive(:events_between).with(date_range) { events }
34
+
35
+ expect(source.serialized_between(date_range)).to eq({
36
+ events: [:serialized_event]
37
+ })
38
+ end
39
+ end
22
40
  end
23
41
  end
data/spec/event_spec.rb CHANGED
@@ -8,14 +8,14 @@ module Almanack
8
8
  expect(event.title).to eq("Music with rocks in")
9
9
  end
10
10
 
11
- it "has a start date" do
12
- event = Event.new(start_date: Time.new(2014, 01, 01))
13
- expect(event.start_date).to eq_time(Time.new(2014, 01, 01))
11
+ it "has a start time" do
12
+ event = Event.new(start_time: Time.new(2014, 01, 01))
13
+ expect(event.start_time).to eq_time(Time.new(2014, 01, 01))
14
14
  end
15
15
 
16
- it "has a end date" do
17
- event = Event.new(end_date: Time.new(2014, 01, 02))
18
- expect(event.end_date).to eq_time(Time.new(2014, 01, 02))
16
+ it "has a end time" do
17
+ event = Event.new(end_time: Time.new(2014, 01, 02))
18
+ expect(event.end_time).to eq_time(Time.new(2014, 01, 02))
19
19
  end
20
20
 
21
21
  it "has a location" do
@@ -30,23 +30,92 @@ module Almanack
30
30
 
31
31
  describe "#formatted_date" do
32
32
  it "handles events without an end date" do
33
- event = Event.new(start_date: Time.parse("2014-07-06 06:24:00 UTC"))
33
+ event = Event.new(start_time: Time.parse("2014-07-06 06:24:00 UTC"))
34
34
  expect(event.formatted_date).to eq("July 6 2014 at 6:24am")
35
35
  end
36
36
 
37
37
  it "handles events with an end date on the same day" do
38
- event = Event.new(start_date: Time.parse("2014-07-06 06:24:00 UTC"),
39
- end_date: Time.parse("2014-07-06 13:20:00 UTC"))
38
+ event = Event.new(start_time: Time.parse("2014-07-06 06:24:00 UTC"),
39
+ end_time: Time.parse("2014-07-06 13:20:00 UTC"))
40
40
  expect(event.formatted_date).to eq("July 6 2014 at 6:24am to 1:20pm")
41
41
  end
42
42
 
43
43
  it "handles events with an end date on a different day" do
44
- event = Event.new(start_date: Time.parse("2014-07-06 06:00:00 UTC"),
45
- end_date: Time.parse("2014-08-07 10:00:00 UTC"))
44
+ event = Event.new(start_time: Time.parse("2014-07-06 06:00:00 UTC"),
45
+ end_time: Time.parse("2014-08-07 10:00:00 UTC"))
46
46
  expect(event.formatted_date).to eq("July 6 2014 at 6:00am to August 7 2014 at 10:00am")
47
47
  end
48
48
  end
49
49
 
50
+ describe "#start_date" do
51
+ it "returns start_time for legacy reasons" do
52
+ event = Event.new(start_time: Time.new(2014, 01, 01))
53
+ result = nil
54
+ expect { result = event.start_date }.to output(/deprecated/i).to_stderr
55
+ expect(result).to eq_time(Time.new(2014, 01, 01))
56
+ end
57
+
58
+ it "can be set via start_date for legacy reasons" do
59
+ event = Event.new(start_date: Time.new(2014, 01, 01))
60
+ result = nil
61
+ expect { result = event.start_time }.to output(/deprecated/i).to_stderr
62
+ expect(result).to eq_time(Time.new(2014, 01, 01))
63
+ end
64
+
65
+ it "raises an error when both start_date and start_time are given" do
66
+ event = Event.new(start_date: Time.new(2014, 01, 01),
67
+ start_time: Time.new(2014, 01, 02))
68
+ expect { event.start_date }.to raise_error
69
+ expect { event.start_time }.to raise_error
70
+ end
71
+ end
72
+
73
+ describe "#end_date" do
74
+ it "returns end_time for legacy reasons" do
75
+ event = Event.new(end_time: Time.new(2014, 01, 01))
76
+ result = nil
77
+ expect { result = event.end_date }.to output(/deprecated/i).to_stderr
78
+ expect(result).to eq_time(Time.new(2014, 01, 01))
79
+ end
80
+
81
+ it "can be set via end_date for legacy reasons" do
82
+ event = Event.new(end_date: Time.new(2014, 01, 01))
83
+ result = nil
84
+ expect { result = event.end_time }.to output(/deprecated/i).to_stderr
85
+ expect(result).to eq_time(Time.new(2014, 01, 01))
86
+ end
87
+
88
+ it "raises an error when both end_date and end_time are given" do
89
+ event = Event.new(end_date: Time.new(2014, 01, 01),
90
+ end_time: Time.new(2014, 01, 02))
91
+ expect { event.end_date }.to raise_error
92
+ expect { event.end_time }.to raise_error
93
+ end
94
+
95
+ it "does not output a deprecation warning when using end_time if neither end_date or end_time is set" do
96
+ event = Event.new
97
+ expect { event.end_time }.not_to output.to_stderr
98
+ end
99
+ end
100
+
101
+ describe "#serialized" do
102
+ it "returns attributes as a hash" do
103
+ wed = Time.new(2014, 01, 01)
104
+ thu = Time.new(2014, 01, 02)
105
+
106
+ event = Event.new(title: "Hogswatch",
107
+ start_time: wed,
108
+ end_time: thu,
109
+ arbitrary: true)
110
+
111
+ expect(event.serialized).to eq({
112
+ title: "Hogswatch",
113
+ start_time: wed.iso8601,
114
+ end_time: thu.iso8601,
115
+ arbitrary: true
116
+ })
117
+ end
118
+ end
50
119
 
51
120
  end
52
121
  end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe "API", :feature do
4
+ before { Almanack.reset! }
5
+
6
+ describe "/feed.json" do
7
+ it "has a JSON content-type" do
8
+ get '/feed.json'
9
+ expect(last_response['Content-Type']).to eq 'application/json'
10
+ end
11
+
12
+ it "returns the JSON feed" do
13
+ allow(Almanack.calendar).to receive(:json_feed) { "feed" }
14
+ get '/feed.json'
15
+ expect(last_response.body).to eq("feed")
16
+ end
17
+
18
+ it "supports JSONP" do
19
+ allow(Almanack.calendar).to receive(:json_feed) { "feed" }
20
+ get '/feed.json?callback=custom'
21
+ expect(last_response.body).to include("custom(feed)")
22
+ end
23
+ end
24
+ end
@@ -7,9 +7,9 @@ describe "Viewing a calendar", :feature do
7
7
  now = Time.now
8
8
 
9
9
  Almanack.config.add_events [
10
- { title: "Hogswatch", start_date: now },
11
- { title: "Soul Cake Tuesday", start_date: now + 10 * 24 * 60 * 60 },
12
- { title: "Eve of Small Gods", start_date: now + 30 * 24 * 60 * 60 },
10
+ { title: "Hogswatch", start_time: now },
11
+ { title: "Soul Cake Tuesday", start_time: now + 10 * 24 * 60 * 60 },
12
+ { title: "Eve of Small Gods", start_time: now + 30 * 24 * 60 * 60 },
13
13
  ]
14
14
 
15
15
  Timecop.freeze(now) do
@@ -12,13 +12,13 @@ describe "Consolidated iCal feed", :feature do
12
12
  Almanack.reset!
13
13
 
14
14
  Almanack.config.add_events [
15
- { title: "Basic", start_date: now },
16
- { title: "Almost a year away", start_date: now + 364 * 24 * 60 * 60 },
17
- { title: "Over a year away", start_date: now + 366 * 24 * 60 * 60 },
15
+ { title: "Basic", start_time: now },
16
+ { title: "Almost a year away", start_time: now + 364 * 24 * 60 * 60 },
17
+ { title: "Over a year away", start_time: now + 366 * 24 * 60 * 60 },
18
18
  {
19
19
  title: "Complex",
20
- start_date: now,
21
- end_date: now + 30 * 24 * 60 * 60,
20
+ start_time: now,
21
+ end_time: now + 30 * 24 * 60 * 60,
22
22
  description: "Body",
23
23
  location: "CA"
24
24
  }
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ module Almanack::Representation
4
+ describe JSONFeed do
5
+
6
+ let(:calendar) { Almanack.calendar }
7
+ let(:parsed_json) { JSON.parse JSONFeed.from(calendar).to_s }
8
+
9
+ before do
10
+ Almanack.reset!
11
+ end
12
+
13
+ describe ".from" do
14
+ around do |test|
15
+ Timecop.freeze(Time.now, &test)
16
+ end
17
+
18
+ it "returns empty event sources by default" do
19
+ expect(parsed_json).to eq({ "eventSources" => [] })
20
+ end
21
+
22
+ it "returns event sources" do
23
+ Almanack.config.add_events [
24
+ { title: "Basic event", start_time: Time.now, custom_data: 'present' }
25
+ ]
26
+
27
+ expect(parsed_json).to eq({
28
+ "eventSources" => [
29
+ {
30
+ "events" => [
31
+ {
32
+ "title" => "Basic event",
33
+ "startTime" => Time.now.iso8601,
34
+ "customData" => 'present'
35
+ }
36
+ ]
37
+ }
38
+ ]
39
+ })
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ module Almanack
4
+ describe SerializedTransformation do
5
+ let(:fact_book) do
6
+ {
7
+ title: "Facts of the World",
8
+ countries: [
9
+ { name: "New Zealand", population: "4 mil" },
10
+ { name: "America", population: "320 mil" }
11
+ ]
12
+ }
13
+ end
14
+
15
+ let(:transformation) { described_class.new(fact_book) }
16
+
17
+ describe "#apply" do
18
+ it "returns an unmodified structure by default" do
19
+ expect(transformation.apply).to eq fact_book
20
+ end
21
+
22
+ it "can apply changes to keys" do
23
+ transformation.key { |key| key.to_s }
24
+ expect(transformation.apply).to eq({
25
+ "title" => "Facts of the World",
26
+ "countries" => [
27
+ { "name" => "New Zealand", "population" => "4 mil" },
28
+ { "name" => "America", "population" => "320 mil" }
29
+ ]
30
+ })
31
+ end
32
+
33
+ it "can apply changes to values" do
34
+ transformation.value { |value| value.upcase }
35
+ expect(transformation.apply).to eq({
36
+ title: "FACTS OF THE WORLD",
37
+ countries: [
38
+ { name: "NEW ZEALAND", population: "4 MIL" },
39
+ { name: "AMERICA", population: "320 MIL" }
40
+ ]
41
+ })
42
+ end
43
+ end
44
+ end
45
+ end
data/spec/spec_helper.rb CHANGED
@@ -21,3 +21,5 @@ VCR.configure do |config|
21
21
  config.ignore_hosts 'codeclimate.com'
22
22
  config.hook_into :webmock
23
23
  end
24
+
25
+ WebMock.disable_net_connect!(:allow => "codeclimate.com")
@@ -29,7 +29,7 @@ module EventMatchers
29
29
 
30
30
  matcher :be_in_order do
31
31
  match do |events|
32
- events == events.sort_by(&:start_date)
32
+ events == events.sort_by(&:start_time)
33
33
  end
34
34
 
35
35
  failure_message do |events|
@@ -17,7 +17,7 @@ Almanack.config do |c|
17
17
  {
18
18
  title: "Edit my calendar's settings",
19
19
  description: "Edit the configuration at #{__FILE__}",
20
- start_date: Time.now + 30 * 60
20
+ start_time: Time.now + 30 * 60
21
21
  }
22
22
  ]
23
23
  end