Merge feature/calendaring branch:
commit8023d6481cVRFS-3276 : Hook calendar creation into user controller API. Add test to verify. commit3a35002a46VRFS-3276 : Calendar cleanup job * Add cleanup method to calendar manager * Create a daily job. * Add calendar cleanup to that job. * Add CRON entry * Daily job/ calendar cleanup test cases * Fix calendar manager spec for new required attribute commit3ff5910f1fVRFS-3276 : Add a delete-calendar directive when RSVP is canceled. VRFS-3276 : Include path to partial. This fails depending on the method used to start the web server. commitd2441cbf57VRFS-3276 : Test new calendar features. Use icalendar gem in test mode only to more deeply verify calendar in strict mode. commit9ac272a0fbVRFS-3276 : Calendar manager updates to include manual calendars. Some refactoring to keep common stuff in one place. commitb5d0c758f0VRFS-3276 : Schema, model updates and new calendar model. commit20472b6b26VRFS-3276 : Change initial submit behavior of RSVP dialog to display calendar info. The user can then close the dialog after this prompt. commit77c99103d0VRFS-3276 : Calendar feed markup and styling. Included as partial. commite632f48600VRFS-3276 : Routes and controller implementation of user calendar ICS feed, which uses calendar manager. commit21fd80a188VRFS-3276 : Initial tests for calendar manager commit92a2524c65VRFS-3276 : Calendar manager * Streamline logic * Enable recurring sessions through rrule * Implement method to create ics feed for user * Extract a type-safe scheduled duration method on music_session for external and internal use. commitb71ad3a4cdVRFS-3276 : Include calendar manager commitf8eaafd036VRFS-3276 : Calendar Manager - initial checkin * Create ICS events given individual parameters * Create calendar from music session * Also will create ICS “delete” events
This commit is contained in:
parent
5c79bdab5c
commit
614cfcbe85
|
|
@ -16,6 +16,3 @@ PLATFORMS
|
|||
|
||||
DEPENDENCIES
|
||||
pg_migrate (= 0.1.13)
|
||||
|
||||
BUNDLED WITH
|
||||
1.10.3
|
||||
|
|
|
|||
|
|
@ -295,3 +295,4 @@ affiliate_partners2.sql
|
|||
enhance_band_profile.sql
|
||||
broadcast_notifications.sql
|
||||
broadcast_notifications_fk.sql
|
||||
calendar.sql
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
CREATE TABLE calendars (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL,
|
||||
user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
target_uid VARCHAR(64) NOT NULL,
|
||||
name VARCHAR(128),
|
||||
description VARCHAR(8000),
|
||||
trigger_delete BOOLEAN DEFAULT FALSE,
|
||||
start_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
end_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
recurring_mode VARCHAR(50) NOT NULL DEFAULT 'once',
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL
|
||||
);
|
||||
|
|
@ -64,6 +64,7 @@ group :test do
|
|||
gem 'rspec-prof'
|
||||
gem 'time_difference'
|
||||
gem 'byebug'
|
||||
gem 'icalendar'
|
||||
end
|
||||
|
||||
# Specify your gem's dependencies in jam_ruby.gemspec
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"container_file": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20150519-97259-1h1tbhj/jam-track-35.jkz", "version": "0", "coverart": null, "rsa_priv_file": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20150519-97259-1h1tbhj/skey.pem", "tracks": [{"name": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20150519-97259-1h1tbhj/7452fa4a-0c55-4cb2-948e-221475d7299c.ogg", "trackName": "track_00"}], "rsa_pub_file": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20150519-97259-1h1tbhj/pkey.pem", "jamktrack_info": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/tmpGdncJS"}
|
||||
{"container_file": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20150706-18103-9lb217/jam-track-45.jkz", "version": "0", "coverart": null, "rsa_priv_file": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20150706-18103-9lb217/skey.pem", "tracks": [{"name": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20150706-18103-9lb217/4630741c-69a1-4bc6-8a9f-ec70cb5cd401.ogg", "trackName": "track_00"}], "rsa_pub_file": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20150706-18103-9lb217/pkey.pem", "jamktrack_info": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/tmpmwZtC7"}
|
||||
|
|
@ -51,6 +51,7 @@ require "jam_ruby/resque/scheduled/icecast_source_check"
|
|||
require "jam_ruby/resque/scheduled/cleanup_facebook_signup"
|
||||
require "jam_ruby/resque/scheduled/unused_music_notation_cleaner"
|
||||
require "jam_ruby/resque/scheduled/user_progress_emailer"
|
||||
require "jam_ruby/resque/scheduled/daily_job"
|
||||
require "jam_ruby/resque/scheduled/daily_session_emailer"
|
||||
require "jam_ruby/resque/scheduled/new_musician_emailer"
|
||||
require "jam_ruby/resque/scheduled/music_session_scheduler"
|
||||
|
|
@ -94,6 +95,7 @@ require "jam_ruby/amqp/amqp_connection_manager"
|
|||
require "jam_ruby/database"
|
||||
require "jam_ruby/message_factory"
|
||||
require "jam_ruby/models/backing_track"
|
||||
require "jam_ruby/models/calendar"
|
||||
require "jam_ruby/models/feedback"
|
||||
require "jam_ruby/models/feedback_observer"
|
||||
#require "jam_ruby/models/max_mind_geo"
|
||||
|
|
@ -227,6 +229,7 @@ require "jam_ruby/models/sale_line_item"
|
|||
require "jam_ruby/models/recurly_transaction_web_hook"
|
||||
require "jam_ruby/models/broadcast_notification"
|
||||
require "jam_ruby/models/broadcast_notification_view"
|
||||
require "jam_ruby/calendar_manager"
|
||||
require "jam_ruby/jam_tracks_manager"
|
||||
require "jam_ruby/jam_track_importer"
|
||||
require "jam_ruby/jmep_manager"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
module JamRuby
|
||||
class CalendarManager < BaseManager
|
||||
DATE_FORMAT="%Y%m%dT%H%M%SZ"
|
||||
def initialize(options={})
|
||||
super(options)
|
||||
@log = Logging.logger[self]
|
||||
end
|
||||
|
||||
def cancel_ics_event(music_session, user)
|
||||
Calendar.where(
|
||||
user_id: user.id,
|
||||
target_uid: music_session.id,
|
||||
name: music_session.description)
|
||||
.first_or_create(
|
||||
description: music_session.description,
|
||||
start_at: music_session.scheduled_start,
|
||||
end_at: music_session.scheduled_start+music_session.safe_scheduled_duration,
|
||||
trigger_delete: true)
|
||||
|
||||
end
|
||||
|
||||
# Remove all "delete" event calendar records older than 4 weeks:
|
||||
def cleanup()
|
||||
Calendar.where("trigger_delete=TRUE AND created_at < ?", 4.weeks.ago)
|
||||
.destroy_all()
|
||||
end
|
||||
|
||||
# @return event (as ICS string) for a given music session
|
||||
def ics_event_from_music_session(music_session, delete=false)
|
||||
# Determine properties of calendar event and create:
|
||||
uid = "#{music_session.id}@JamKazam"
|
||||
text = "JamKazam Session #{music_session.description}"
|
||||
rrule = nil
|
||||
start_at = music_session.scheduled_start
|
||||
stop_at = music_session.scheduled_start+music_session.safe_scheduled_duration
|
||||
if !delete && music_session.recurring_mode==MusicSession::RECURRING_WEEKLY
|
||||
rrule = "FREQ=WEEKLY;INTERVAL=1"
|
||||
end
|
||||
create_ics_event(uid, text, text, start_at, stop_at, delete, rrule)
|
||||
end
|
||||
|
||||
# @return event (as ICS string) for a given music session
|
||||
def ics_event_from_calendar(calendar)
|
||||
# Determine properties of calendar event and create:
|
||||
rrule = nil
|
||||
if !calendar.trigger_delete && calendar.recurring_mode==MusicSession::RECURRING_WEEKLY
|
||||
rrule = "FREQ=WEEKLY;INTERVAL=1"
|
||||
end
|
||||
|
||||
create_ics_event(
|
||||
calendar.target_uid,
|
||||
"JamKazam Session #{calendar.name}",
|
||||
calendar.description,
|
||||
calendar.start_at,
|
||||
calendar.end_at,
|
||||
calendar.trigger_delete,
|
||||
rrule
|
||||
)
|
||||
end
|
||||
|
||||
# @return calendar (as ICS string) for specified user
|
||||
# Includes all RSVPed sessions, as well as any calendar
|
||||
# entries for the given user:
|
||||
def create_ics_feed(user)
|
||||
ics_events = ""
|
||||
MusicSession.scheduled_rsvp(user, true).each do |music_session|
|
||||
ics_events << "\r\n" if(ics_events.length != 0)
|
||||
ics_events << ics_event_from_music_session(music_session)
|
||||
end
|
||||
|
||||
user.calendars.each do |user|
|
||||
ics_events << "\r\n" if(ics_events.length != 0)
|
||||
ics_events << ics_event_from_calendar(user)
|
||||
end
|
||||
|
||||
create_ics_cal(ics_events)
|
||||
end
|
||||
|
||||
# @return event (as ICS string) for given arguments
|
||||
def create_ics_event(uuid, name, description, start_at, end_at, delete=false, rrule=nil, sequence=nil)
|
||||
uuid ||= UUID.timestamp_create
|
||||
event = "BEGIN:VEVENT\r\n"
|
||||
event << "UID:#{uuid}\r\n"
|
||||
event << "DTSTAMP:#{Time.now.utc().strftime(DATE_FORMAT)}\r\n"
|
||||
event << "DTSTART:#{start_at.utc().strftime(DATE_FORMAT)}\r\n"
|
||||
event << "DTEND:#{end_at.utc().strftime(DATE_FORMAT)}\r\n"
|
||||
event << "SUMMARY:#{name}\r\n"
|
||||
event << "DESCRIPTION:#{description}\r\n"
|
||||
if delete
|
||||
event << "METHOD:CANCEL\r\n"
|
||||
event << "STATUS:CANCELLED\r\n"
|
||||
end
|
||||
if rrule
|
||||
event << "RRULE:#{rrule}\r\n"
|
||||
end
|
||||
event << "SEQUENCE:#{sequence}\r\n" if sequence
|
||||
event << "END:VEVENT"
|
||||
end
|
||||
|
||||
# @return calendar (as ICS string) for specified events
|
||||
def create_ics_cal(ics_events)
|
||||
"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:JamKazam\r\n#{ics_events}\r\nEND:VCALENDAR"
|
||||
end
|
||||
|
||||
end # class
|
||||
end # module
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
module JamRuby
|
||||
class Calendar < ActiveRecord::Base
|
||||
include HtmlSanitize
|
||||
html_sanitize strict: [:name, :description]
|
||||
attr_accessible :name, :description, :target_uid, :trigger_delete, :start_at, :end_at
|
||||
|
||||
@@log = Logging.logger[Calendar]
|
||||
|
||||
self.table_name = "calendars"
|
||||
self.primary_key = 'id'
|
||||
|
||||
belongs_to :user, :class_name => 'JamRuby::User', :foreign_key => :user_id, :inverse_of => :calendars
|
||||
end
|
||||
end
|
||||
|
|
@ -880,6 +880,21 @@ SQL
|
|||
end
|
||||
result
|
||||
end
|
||||
|
||||
def safe_scheduled_duration
|
||||
duration = scheduled_duration
|
||||
# you can put seconds into the scheduled_duration field, but once stored, it comes back out as a string
|
||||
if scheduled_duration.class == String
|
||||
begin
|
||||
bits = scheduled_duration.split(':')
|
||||
duration = bits[0].to_i.hours + bits[1].to_i.minutes + bits[2].to_i.seconds
|
||||
rescue Exception => e
|
||||
duration = 1.hours
|
||||
@@log.error("unable to parse duration #{scheduled_duration}")
|
||||
end
|
||||
end
|
||||
duration
|
||||
end
|
||||
# should create a timestamp like:
|
||||
#
|
||||
# with_timezone = TRUE
|
||||
|
|
@ -910,17 +925,7 @@ SQL
|
|||
end
|
||||
end
|
||||
|
||||
duration = scheduled_duration
|
||||
# you can put seconds into the scheduled_duration field, but once stored, it comes back out as a string
|
||||
if scheduled_duration.class == String
|
||||
begin
|
||||
bits = scheduled_duration.split(':')
|
||||
duration = bits[0].to_i.hours + bits[1].to_i.minutes + bits[2].to_i.seconds
|
||||
rescue Exception => e
|
||||
duration = 1.hours
|
||||
@@log.error("unable to parse duration #{scheduled_duration}")
|
||||
end
|
||||
end
|
||||
duration = safe_scheduled_duration
|
||||
end_time = start_time + duration
|
||||
if with_timezone
|
||||
"#{start_time.strftime("%A, %B %e")}, #{start_time.strftime("%l:%M").strip}-#{end_time.strftime("%l:%M %p").strip} #{timezone_display}"
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ module JamRuby
|
|||
validates :user, presence: true
|
||||
validates :canceled, :inclusion => {:in => [nil, true, false]}
|
||||
validate :creator_rsvp_cancel
|
||||
before_save :cancel_calendar
|
||||
|
||||
# pulls all instruments from the associated rsvp_slots
|
||||
def instrument_list
|
||||
|
|
@ -305,6 +306,15 @@ module JamRuby
|
|||
errors.add(:canceled, "can't be canceled by the session organizer")
|
||||
end
|
||||
end
|
||||
|
||||
def cancel_calendar
|
||||
calendar_manager = CalendarManager.new
|
||||
if self.canceled
|
||||
self.rsvp_slots.each do |slot|
|
||||
calendar_manager.cancel_ics_event(slot.music_session, user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ module JamRuby
|
|||
# authorizations (for facebook, etc -- omniauth)
|
||||
has_many :user_authorizations, :class_name => "JamRuby::UserAuthorization"
|
||||
|
||||
# calendars (for scheduling NOT in music_session)
|
||||
has_many :calendars, :class_name => "JamRuby::Calendar"
|
||||
|
||||
# connections (websocket-gateway)
|
||||
has_many :connections, :class_name => "JamRuby::Connection"
|
||||
|
||||
|
|
@ -698,6 +701,20 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
# Build calendars using given parameter.
|
||||
# @param calendars (array of hash)
|
||||
def update_calendars(calendars)
|
||||
unless self.new_record?
|
||||
Calendar.where("user_id = ?", self.id).delete_all
|
||||
end
|
||||
|
||||
unless calendars.nil?
|
||||
calendars.each do |cal|
|
||||
self.calendars << self.calendars.create(cal)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# given an array of instruments, update a user's instruments
|
||||
def update_instruments(instruments)
|
||||
# delete all instruments for this user first
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
module JamRuby
|
||||
class DailyJob
|
||||
extend Resque::Plugins::JamLonelyJob
|
||||
|
||||
@queue = :scheduled_daily_job
|
||||
@@log = Logging.logger[DailyJob]
|
||||
|
||||
class << self
|
||||
def perform
|
||||
@@log.debug("waking up")
|
||||
calendar_manager = CalendarManager.new
|
||||
calendar_manager.cleanup()
|
||||
@@log.debug("done")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
require 'spec_helper'
|
||||
require 'icalendar'
|
||||
|
||||
describe CalendarManager do
|
||||
CALENDAR_NAME="Test Cal"
|
||||
|
||||
before :all do
|
||||
@genre1 = FactoryGirl.create(:genre)
|
||||
@calendar_manager = JamRuby::CalendarManager.new
|
||||
|
||||
# Time resolution is seconds:
|
||||
@start = Time.at(Time.now.utc.to_i)
|
||||
@stop =(@start+1.hours)
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
|
||||
end
|
||||
|
||||
describe "with music sessions" do
|
||||
before :all do
|
||||
@creator = FactoryGirl.create(:user)
|
||||
@music_session = FactoryGirl.create(:music_session, :creator => @creator, :description => CALENDAR_NAME, :genre => @genre1, :scheduled_start=>@start, :scheduled_duration=>3600)
|
||||
@music_session.reload
|
||||
end
|
||||
|
||||
it "validator detects bad calendar" do
|
||||
lambda{verify_ical("Bad medicine calendar")}
|
||||
.should raise_error(RuntimeError)
|
||||
end
|
||||
|
||||
it "can create calendar feed" do
|
||||
ics = @calendar_manager.create_ics_feed(@creator)
|
||||
# Basic format checking...there are some online tools that
|
||||
# check a lot more, but no ruby libs that I could find:
|
||||
lines = ics.split("\r\n")
|
||||
lines.should have(12).items
|
||||
lines.first.should eq("BEGIN:VCALENDAR")
|
||||
lines.last.should eq("END:VCALENDAR")
|
||||
lines[-2].should eq("END:VEVENT")
|
||||
verify_ical(ics)
|
||||
end
|
||||
end
|
||||
|
||||
describe "with manual calendars" do
|
||||
before :all do
|
||||
@creator = FactoryGirl.create(:user)
|
||||
@creator.calendars<<Calendar.new({:name=>CALENDAR_NAME, :description=>"This is a test", :start_at=>(@start), :end_at=>@stop, :trigger_delete=>false, :target_uid=>"2112"})
|
||||
end
|
||||
|
||||
it "can create calendar feed" do
|
||||
#pending "foobar"
|
||||
ics = @calendar_manager.create_ics_feed(@creator)
|
||||
|
||||
# Basic format checking...there are some online tools that
|
||||
# check a lot more, but no ruby libs that I could find:
|
||||
lines = ics.split("\r\n")
|
||||
lines.should have(12).items
|
||||
lines.first.should eq("BEGIN:VCALENDAR")
|
||||
lines.last.should eq("END:VCALENDAR")
|
||||
lines[-2].should eq("END:VEVENT")
|
||||
verify_ical(ics)
|
||||
end
|
||||
end
|
||||
|
||||
def verify_ical(ics)
|
||||
strict_parser = Icalendar::Parser.new(ics, true)
|
||||
cals = strict_parser.parse
|
||||
cals.should_not be_nil
|
||||
cals.should have(1).items
|
||||
|
||||
cal = cals.first
|
||||
cal.should_not be_nil
|
||||
cal.events.should have(1).items
|
||||
event = cal.events.first
|
||||
event.should_not be_nil
|
||||
|
||||
event.summary.should eq("JamKazam Session #{CALENDAR_NAME}")
|
||||
event.dtstart.to_i.should_not be_nil
|
||||
event.dtend.to_i.should_not be_nil
|
||||
(event.dtstart).to_time.utc.to_i.should eq(@start.to_i)
|
||||
(event.dtend).to_time.utc.to_i.should eq(@stop.to_i)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -30,10 +30,10 @@ describe RsvpRequest do
|
|||
|
||||
@slot1 = FactoryGirl.build(:rsvp_slot, :music_session => @music_session, :instrument => JamRuby::Instrument.find('electric guitar'))
|
||||
@slot1.save
|
||||
|
||||
|
||||
@slot2 = FactoryGirl.build(:rsvp_slot, :music_session => @music_session, :instrument => JamRuby::Instrument.find('drums'))
|
||||
@slot2.save
|
||||
|
||||
|
||||
@invitation = FactoryGirl.build(:invitation, :sender => @session_creator, :receiver => @session_invitee, :music_session => @music_session)
|
||||
@invitation.save
|
||||
end
|
||||
|
|
@ -53,12 +53,12 @@ describe RsvpRequest do
|
|||
@music_session.save
|
||||
|
||||
RsvpRequest.create({:session_id => @music_session.id, :rsvp_slots => [@slot1.id, @slot2.id]}, @non_session_invitee)
|
||||
expect {RsvpRequest.create({:session_id => @music_session.id, :rsvp_slots => [@slot1.id, @slot2.id]}, @non_session_invitee)}.to raise_error(JamRuby::StateError)
|
||||
expect {RsvpRequest.create({:session_id => @music_session.id, :rsvp_slots => [@slot1.id, @slot2.id]}, @non_session_invitee)}.to raise_error(JamRuby::StateError)
|
||||
end
|
||||
|
||||
it "should allow invitee to RSVP to session with closed RSVPs" do
|
||||
rsvp = RsvpRequest.create({:session_id => @music_session.id, :rsvp_slots => [@slot1.id, @slot2.id], :message => "We be jammin!"}, @session_invitee)
|
||||
|
||||
|
||||
# verify comment
|
||||
comment = SessionInfoComment.find_by_creator_id(@session_invitee)
|
||||
comment.comment.should == "We be jammin!"
|
||||
|
|
@ -373,12 +373,14 @@ describe RsvpRequest do
|
|||
comment = SessionInfoComment.find_by_creator_id(@session_invitee)
|
||||
comment.comment.should == "Let's Jam!"
|
||||
|
||||
# cancel
|
||||
expect {RsvpRequest.cancel({:id => rsvp.id, :session_id => @music_session.id, :cancelled => "all", :message => "Sorry, I'm bailing for all sessions"}, @session_invitee)}.to_not raise_error
|
||||
calendar_count = Calendar.find(:all).count
|
||||
|
||||
# cancel & check that calendar has been added:
|
||||
expect {RsvpRequest.cancel({:id => rsvp.id, :session_id => @music_session.id, :cancelled => "all", :message => "Sorry, I'm bailing for all sessions"}, @session_invitee)}.to_not raise_error
|
||||
rsvp = RsvpRequest.find_by_id(rsvp.id)
|
||||
rsvp.canceled.should == true
|
||||
rsvp.cancel_all.should == true
|
||||
(Calendar.find(:all).count - calendar_count).should eq(1)
|
||||
|
||||
# verify comment
|
||||
comment = SessionInfoComment.find_by_creator_id(@session_invitee)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'DailyJob' do
|
||||
describe "calendar cleanup" do
|
||||
shared_examples_for :calendar_cleanup do |trigger_delete, end_count|
|
||||
before :each do
|
||||
Calendar.destroy_all
|
||||
@creator = FactoryGirl.create(:user)
|
||||
@creator.calendars << Calendar.new(
|
||||
:name=>"Test Cal",
|
||||
:description=>"This is a test",
|
||||
:start_at=>(Time.now),
|
||||
:end_at=>Time.now,
|
||||
:trigger_delete=>trigger_delete,
|
||||
:target_uid=>"2112"
|
||||
)
|
||||
end
|
||||
|
||||
it "properly purges old 'delete' calendars" do
|
||||
@creator.reload
|
||||
@creator.calendars.should have(1).items
|
||||
|
||||
JamRuby::DailyJob.perform
|
||||
@creator.reload
|
||||
@creator.calendars.should have(1).items
|
||||
|
||||
Timecop.travel(Time.now + 5.weeks)
|
||||
JamRuby::DailyJob.perform
|
||||
@creator.reload
|
||||
@creator.calendars.should have(end_count).items
|
||||
Timecop.return
|
||||
end
|
||||
end
|
||||
|
||||
describe "whacks old 'delete' calendars" do
|
||||
it_behaves_like :calendar_cleanup, true, 0
|
||||
end
|
||||
|
||||
describe "doesn't whacks non 'delete' calendars" do
|
||||
it_behaves_like :calendar_cleanup, false, 1
|
||||
end
|
||||
end # calendar cleanpu
|
||||
end #spec
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
var dialogId = 'rsvp-submit-dialog';
|
||||
var $btnSubmit = $("#btnSubmitRsvp");
|
||||
|
||||
|
||||
function beforeShow(data) {
|
||||
$('.error', $dialog).hide();
|
||||
}
|
||||
|
|
@ -56,7 +57,6 @@
|
|||
$btnSubmit.unbind('click');
|
||||
$btnSubmit.click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var error = false;
|
||||
var slotIds = [];
|
||||
var selectedSlots = [];
|
||||
|
|
@ -96,7 +96,11 @@
|
|||
|
||||
if (!error) {
|
||||
$dialog.triggerHandler(EVENTS.RSVP_SUBMITTED);
|
||||
app.layout.closeDialog(dialogId);
|
||||
|
||||
// Show confirmation & calendar; hide regular buttons.
|
||||
$(".rsvp-options").addClass("hidden")
|
||||
$(".rsvp-confirm").removeClass("hidden")
|
||||
$(".buttons").addClass("hidden")
|
||||
}
|
||||
})
|
||||
.fail(function(xhr, textStatus, errorMessage) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,16 @@
|
|||
.session-detail-scroller,
|
||||
#account-identity-content-scroller {
|
||||
|
||||
.ics-feed-caption {
|
||||
font-size: 1.2em;
|
||||
margin: 0em 0em 1em 0em;
|
||||
}
|
||||
|
||||
.ics-feed-link {
|
||||
font-size: 1.1em;
|
||||
margin: 0.5em 0em 1em 0em;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding:10px 30px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,33 @@
|
|||
.rsvp-dialog {
|
||||
|
||||
min-height:initial;
|
||||
height:auto;
|
||||
|
||||
.rsvp-confirm {
|
||||
color: white;
|
||||
margin-top: 1em;
|
||||
.ics-feed-caption {
|
||||
font-size: 1.2em;
|
||||
margin: 0em 0em 1em 0em;
|
||||
}
|
||||
|
||||
.ics-feed-link {
|
||||
font-size: 1.1em;
|
||||
margin: 0.5em 0em 1em 0em;
|
||||
}
|
||||
|
||||
.ics-help-link {
|
||||
display: inline;
|
||||
font-size: 0.8em;
|
||||
padding-right: 2em;
|
||||
}
|
||||
|
||||
.confirm-buttons {
|
||||
text-align: center;
|
||||
margin: 1em 0em 0em 0em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.session-name {
|
||||
margin:3px 0 0;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
require 'sanitize'
|
||||
class ApiUsersController < ApiController
|
||||
|
||||
before_filter :api_signed_in_user, :except => [:create, :show, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring, :add_play, :crash_dump, :validate_data]
|
||||
before_filter :api_signed_in_user, :except => [:create, :calendar, :show, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring, :add_play, :crash_dump, :validate_data]
|
||||
before_filter :auth_user, :only => [:session_settings_show, :session_history_index, :session_user_history_index, :update, :delete,
|
||||
:liking_create, :liking_destroy, # likes
|
||||
:following_create, :following_show, :following_destroy, # followings
|
||||
|
|
@ -15,19 +15,22 @@ class ApiUsersController < ApiController
|
|||
:share_session, :share_recording,
|
||||
:affiliate_report, :audio_latency, :broadcast_notification]
|
||||
|
||||
respond_to :json
|
||||
respond_to :json, :except => :calendar
|
||||
respond_to :ics, :only => :calendar
|
||||
|
||||
def index
|
||||
@users = User.paginate(page: params[:page])
|
||||
respond_with @users, responder: ApiResponder, :status => 200
|
||||
end
|
||||
|
||||
def calendar
|
||||
@user=lookup_user
|
||||
ics = CalendarManager.new.create_ics_feed(@user)
|
||||
send_data ics, :filename => 'JamKazam', :disposition => 'inline', :type => "text/calendar"
|
||||
end
|
||||
|
||||
def show
|
||||
@user = User.includes([{musician_instruments: :instrument},
|
||||
{band_musicians: :user},
|
||||
{genre_players: :genre},
|
||||
:bands, :instruments, :genres, :jam_track_rights, :affiliate_partner])
|
||||
.find(params[:id])
|
||||
@user=lookup_user
|
||||
|
||||
respond_with @user, responder: ApiResponder, :status => 200
|
||||
end
|
||||
|
|
@ -80,10 +83,10 @@ class ApiUsersController < ApiController
|
|||
respond_with_model(@user, new: true, location: lambda { return api_user_detail_url(@user.id) })
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def profile_save
|
||||
end
|
||||
|
||||
|
||||
def update
|
||||
|
||||
@user = User.find(params[:id])
|
||||
|
|
@ -96,7 +99,7 @@ class ApiUsersController < ApiController
|
|||
@user.country = params[:country] if params.has_key?(:country)
|
||||
@user.musician = params[:musician] if params.has_key?(:musician)
|
||||
@user.update_instruments(params[:instruments].nil? ? [] : params[:instruments]) if params.has_key?(:instruments)
|
||||
|
||||
|
||||
# genres
|
||||
@user.update_genres(params[:genres].nil? ? [] : params[:genres], GenrePlayer::PROFILE) if params.has_key?(:genres)
|
||||
@user.update_genres(params[:virtual_band_genres].nil? ? [] : params[:virtual_band_genres], GenrePlayer::VIRTUAL_BAND) if params.has_key?(:virtual_band_genres)
|
||||
|
|
@ -104,7 +107,7 @@ class ApiUsersController < ApiController
|
|||
@user.update_genres(params[:paid_session_genres].nil? ? [] : params[:paid_session_genres], GenrePlayer::PAID_SESSION) if params.has_key?(:paid_session_genres)
|
||||
@user.update_genres(params[:free_session_genres].nil? ? [] : params[:free_session_genres], GenrePlayer::FREE_SESSION) if params.has_key?(:free_session_genres)
|
||||
@user.update_genres(params[:cowriting_genres].nil? ? [] : params[:cowriting_genres], GenrePlayer::COWRITING) if params.has_key?(:cowriting_genres)
|
||||
|
||||
|
||||
@user.show_whats_next = params[:show_whats_next] if params.has_key?(:show_whats_next)
|
||||
@user.show_whats_next_count = params[:show_whats_next_count] if params.has_key?(:show_whats_next_count)
|
||||
@user.subscribe_email = params[:subscribe_email] if params.has_key?(:subscribe_email)
|
||||
|
|
@ -146,7 +149,7 @@ class ApiUsersController < ApiController
|
|||
|
||||
@user.update_online_presences(params[:online_presences]) if params.has_key?(:online_presences)
|
||||
@user.update_performance_samples(params[:performance_samples]) if params.has_key?(:performance_samples)
|
||||
|
||||
@user.update_calendars(params[:calendars]) if params.has_key?(:calendars)
|
||||
@user.save
|
||||
|
||||
if @user.errors.any?
|
||||
|
|
@ -196,9 +199,9 @@ class ApiUsersController < ApiController
|
|||
end
|
||||
|
||||
def delete
|
||||
@user.destroy
|
||||
@user.destroy
|
||||
respond_with responder: ApiResponder, :status => 204
|
||||
end
|
||||
end
|
||||
|
||||
def signup_confirm
|
||||
@user = UserManager.new.signup_confirm(params[:signup_token])
|
||||
|
|
@ -260,7 +263,7 @@ class ApiUsersController < ApiController
|
|||
def auth_session_delete
|
||||
sign_out
|
||||
render :json => { :success => true }, :status => 200
|
||||
end
|
||||
end
|
||||
|
||||
###################### SESSION SETTINGS ###################
|
||||
def session_settings_show
|
||||
|
|
@ -276,7 +279,7 @@ class ApiUsersController < ApiController
|
|||
@session_user_history = @user.session_user_history(params[:id], params[:session_id])
|
||||
end
|
||||
|
||||
###################### BANDS ########################
|
||||
###################### BANDS ########################
|
||||
def band_index
|
||||
@bands = User.band_index(params[:id])
|
||||
end
|
||||
|
|
@ -296,7 +299,7 @@ class ApiUsersController < ApiController
|
|||
@user = User.find(params[:id])
|
||||
if !params[:user_id].nil?
|
||||
@user.create_user_liking(params[:user_id])
|
||||
|
||||
|
||||
elsif !params[:band_id].nil?
|
||||
@user.create_band_liking(params[:band_id])
|
||||
end
|
||||
|
|
@ -454,7 +457,7 @@ class ApiUsersController < ApiController
|
|||
respond_with @invitation, responder: ApiResponder, :status => 200
|
||||
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render :json => { :message => ValidationMessages::BAND_INVITATION_NOT_FOUND }, :status => 404
|
||||
render :json => { :message => ValidationMessages::BAND_INVITATION_NOT_FOUND }, :status => 404
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -467,9 +470,9 @@ class ApiUsersController < ApiController
|
|||
params[:accepted])
|
||||
|
||||
respond_with @invitation, responder: ApiResponder, :status => 200
|
||||
|
||||
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render :json => { :message => ValidationMessages::BAND_INVITATION_NOT_FOUND }, :status => 404
|
||||
render :json => { :message => ValidationMessages::BAND_INVITATION_NOT_FOUND }, :status => 404
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -576,11 +579,11 @@ class ApiUsersController < ApiController
|
|||
# user_id is deduced if possible from the user's cookie.
|
||||
@dump = CrashDump.new
|
||||
|
||||
@dump.client_type = params[:client_type]
|
||||
@dump.client_type = params[:client_type]
|
||||
@dump.client_version = params[:client_version]
|
||||
@dump.client_id = params[:client_id]
|
||||
@dump.user_id = current_user.try(:id)
|
||||
@dump.session_id = params[:session_id]
|
||||
@dump.session_id = params[:session_id]
|
||||
@dump.timestamp = params[:timestamp]
|
||||
|
||||
unless @dump.save
|
||||
|
|
@ -589,7 +592,7 @@ class ApiUsersController < ApiController
|
|||
respond_with @dump
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
# This part is the piece that really needs to be decomposed into a library...
|
||||
if Rails.application.config.storage_type == :fog
|
||||
s3 = AWS::S3.new(:access_key_id => Rails.application.config.aws_access_key_id,
|
||||
|
|
@ -597,15 +600,15 @@ class ApiUsersController < ApiController
|
|||
bucket = s3.buckets[Rails.application.config.aws_bucket]
|
||||
uri = @dump.uri
|
||||
expire = Time.now + 20.years
|
||||
read_url = bucket.objects[uri].url_for(:read,
|
||||
:expires => expire,
|
||||
read_url = bucket.objects[uri].url_for(:read,
|
||||
:expires => expire,
|
||||
:'response_content_type' => 'application/octet-stream').to_s
|
||||
@dump.update_attribute(:uri, read_url)
|
||||
|
||||
write_url = bucket.objects[uri].url_for(:write,
|
||||
:expires => Rails.application.config.crash_dump_data_signed_url_timeout,
|
||||
write_url = bucket.objects[uri].url_for(:write,
|
||||
:expires => Rails.application.config.crash_dump_data_signed_url_timeout,
|
||||
:'response_content_type' => 'application/octet-stream').to_s
|
||||
|
||||
|
||||
logger.debug("crash_dump can read from url #{read_url}")
|
||||
|
||||
redirect_to write_url
|
||||
|
|
@ -744,9 +747,9 @@ class ApiUsersController < ApiController
|
|||
if txt = oo.affiliate_legalese.try(:legalese)
|
||||
txt = ControllerHelp.instance.simple_format(txt)
|
||||
end
|
||||
result['agreement'] = {
|
||||
'legalese' => txt,
|
||||
'signed_at' => oo.signed_at
|
||||
result['agreement'] = {
|
||||
'legalese' => txt,
|
||||
'signed_at' => oo.signed_at
|
||||
}
|
||||
#result['signups'] = oo.referrals_by_date
|
||||
#result['earnings'] = [['April 2015', '1000 units', '$100']]
|
||||
|
|
@ -851,7 +854,7 @@ class ApiUsersController < ApiController
|
|||
else
|
||||
render json: { message: 'Valid Site', data: data }, status: 200
|
||||
end
|
||||
else
|
||||
else
|
||||
render json: { message: "unknown validation for data '#{params[:data]}', site '#{params[:site]}'" }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
|
@ -880,6 +883,14 @@ class ApiUsersController < ApiController
|
|||
render json: { }, status: 200
|
||||
end
|
||||
|
||||
def lookup_user
|
||||
User.includes([{musician_instruments: :instrument},
|
||||
{band_musicians: :user},
|
||||
{genre_players: :genre},
|
||||
:bands, :instruments, :genres, :jam_track_rights, :affiliate_partner])
|
||||
.find(params[:id])
|
||||
end
|
||||
|
||||
###################### RECORDINGS #######################
|
||||
# def recording_index
|
||||
# @recordings = User.recording_index(current_user, params[:id])
|
||||
|
|
@ -932,5 +943,5 @@ class ApiUsersController < ApiController
|
|||
# @recording = Recording.find(params[:recording_id])
|
||||
# @recording.delete
|
||||
# respond_with responder: ApiResponder, :status => 204
|
||||
# end
|
||||
# end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@
|
|||
%thead
|
||||
%tbody
|
||||
.clearall
|
||||
.content-wrapper
|
||||
.ics-feed-caption Following is a URL for your personal JamKazam .ics calendar, which tracks all sessions and events to which you have RSVP'd:
|
||||
=render "calendar"
|
||||
|
||||
/ end content scrolling area
|
||||
|
||||
%script{type: 'text/template', id: 'template-account-session'}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
-if current_user
|
||||
.account-calendar
|
||||
.ics-feed-link
|
||||
=api_users_calendar_feed_url(current_user)
|
||||
.ics-help-links
|
||||
.ics-help-link
|
||||
a href="" How to subscribe to your calendar in Google Calendar
|
||||
.ics-help-link
|
||||
a href="" How to subscribe to your calendar in Microsoft Outlook
|
||||
|
|
@ -7,16 +7,27 @@
|
|||
.session-name
|
||||
.scheduled-start
|
||||
.schedule-recurrence
|
||||
.part
|
||||
.slot-instructions Check the box(es) next to the track(s) you want to play in the session:
|
||||
.error{:style => 'display:none'}
|
||||
.rsvp-instruments
|
||||
.rsvp-options
|
||||
.part
|
||||
.slot-instructions Check the box(es) next to the track(s) you want to play in the session:
|
||||
.error{:style => 'display:none'}
|
||||
.rsvp-instruments
|
||||
|
||||
.comment-instructions Enter a message to the other musicians in the session (optional):
|
||||
%textarea.txtComment{rows: '2', placeholder: 'Enter a comment...'}
|
||||
.rsvp-confirm.hidden
|
||||
%p SUCCESS!
|
||||
%br
|
||||
%p We recommend that you subscribe to your own personal JamKazam calendar in your favorite calendar app to help you remember this session, as well as other sessions and events to which you RSVP.
|
||||
%br
|
||||
%p Here is the URL for your calendar:
|
||||
=render "clients/calendar"
|
||||
.confirm-buttons
|
||||
%a#btnClose.button-grey{'layout-action' => 'close'} CLOSE
|
||||
|
||||
.comment-instructions Enter a message to the other musicians in the session (optional):
|
||||
%textarea.txtComment{rows: '2', placeholder: 'Enter a comment...'}
|
||||
.buttons
|
||||
.left
|
||||
%a.button-grey{:href => 'http://jamkazam.desk.com', :rel => 'external', :target => '_blank'} HELP
|
||||
%a#btnHelp.button-grey{:href => 'http://jamkazam.desk.com', :rel => 'external', :target => '_blank'} HELP
|
||||
.right
|
||||
%a.button-grey{:id => 'btnCancel', 'layout-action' => 'close'} CANCEL
|
||||
%a.button-orange{:id => 'btnSubmitRsvp'} SUBMIT RSVP
|
||||
%a#btnCancel.button-grey{'layout-action' => 'close'} CANCEL
|
||||
%a#btnSubmitRsvp.button-orange SUBMIT RSVP
|
||||
|
|
@ -271,9 +271,9 @@ SampleApp::Application.routes.draw do
|
|||
#match '/users' => 'api_users#create', :via => :post
|
||||
match '/users/:id' => 'api_users#update', :via => :post
|
||||
match '/users/:id' => 'api_users#delete', :via => :delete
|
||||
match '/users/:id/calendar.ics' => 'api_users#calendar', :via => :get, :as => 'api_users_calendar_feed'
|
||||
match '/users/confirm/:signup_token' => 'api_users#signup_confirm', :via => :post, :as => 'api_signup_confirmation'
|
||||
match '/users/complete/:signup_token' => 'api_users#complete', as: 'complete', via: 'post'
|
||||
|
||||
match '/users/:id/set_password' => 'api_users#set_password', :via => :post
|
||||
|
||||
# recurly
|
||||
|
|
|
|||
|
|
@ -40,6 +40,11 @@ DailySessionEmailer:
|
|||
class: "JamRuby::DailySessionEmailer"
|
||||
description: "Sends daily scheduled session emails"
|
||||
|
||||
DailyJob:
|
||||
cron: "0 4 * * *"
|
||||
class: "JamRuby::DailyJob"
|
||||
description: "Aggregate task to perform general daily things"
|
||||
|
||||
ScheduledMusicSessionCleaner:
|
||||
cron: "0 3 * * *"
|
||||
class: "JamRuby::ScheduledMusicSessionCleaner"
|
||||
|
|
|
|||
|
|
@ -59,6 +59,27 @@ describe ApiUsersController do
|
|||
end
|
||||
end
|
||||
|
||||
describe "calendars" do
|
||||
before :each do
|
||||
Calendar.destroy_all
|
||||
end
|
||||
|
||||
it "adds calendar via update" do
|
||||
cals = [{
|
||||
:name=>"Test Cal",
|
||||
:description=>"This is a test",
|
||||
:start_at=>(Time.now),
|
||||
:end_at=>Time.now,
|
||||
:trigger_delete=>true,
|
||||
:target_uid=>"2112"
|
||||
}]
|
||||
post :update, id:user.id, calendars: cals, :format=>'json'
|
||||
response.should be_success
|
||||
user.reload
|
||||
user.calendars.should have(1).items
|
||||
end
|
||||
end
|
||||
|
||||
describe "update mod" do
|
||||
it "empty mod" do
|
||||
post :update, id:user.id, mods: {}, :format=>'json'
|
||||
|
|
@ -83,13 +104,13 @@ describe ApiUsersController do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'site validation' do
|
||||
describe 'site validation' do
|
||||
|
||||
it 'checks valid and invalid site types' do
|
||||
it 'checks valid and invalid site types' do
|
||||
site_types = Utils::SITE_TYPES.clone << 'bandcamp-fan'
|
||||
site_types.each do |sitetype|
|
||||
rec_id = nil
|
||||
case sitetype
|
||||
case sitetype
|
||||
when 'url'
|
||||
valid, invalid = 'http://jamkazam.com', 'http://jamkazamxxx.com'
|
||||
when 'youtube'
|
||||
|
|
|
|||
Loading…
Reference in New Issue