Merge feature/calendaring branch:

commit 8023d6481c
    VRFS-3276 : Hook calendar creation into user controller API.  Add test to verify.

commit 3a35002a46
    VRFS-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

commit 3ff5910f1f
    VRFS-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.

commit d2441cbf57
    VRFS-3276 : Test new calendar features. Use icalendar gem in test mode only to more deeply verify calendar in strict mode.

commit 9ac272a0fb
    VRFS-3276 : Calendar manager updates to include manual calendars.  Some refactoring to keep common stuff in one place.

commit b5d0c758f0
    VRFS-3276 : Schema, model updates and new calendar model.

commit 20472b6b26
    VRFS-3276 : Change initial submit behavior of RSVP dialog to display calendar info.  The user can then close the dialog after this prompt.

commit 77c99103d0
    VRFS-3276 : Calendar feed markup and styling.  Included as partial.

commit e632f48600
    VRFS-3276 : Routes and controller implementation of user calendar ICS feed, which uses calendar manager.

commit 21fd80a188
    VRFS-3276 : Initial tests for calendar manager

commit 92a2524c65
    VRFS-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.

commit b71ad3a4cd
    VRFS-3276 : Include calendar manager

commit f8eaafd036
    VRFS-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:
Steven Miers 2015-07-06 15:34:27 -05:00
parent 5c79bdab5c
commit 614cfcbe85
25 changed files with 485 additions and 69 deletions

View File

@ -16,6 +16,3 @@ PLATFORMS
DEPENDENCIES
pg_migrate (= 0.1.13)
BUNDLED WITH
1.10.3

View File

@ -295,3 +295,4 @@ affiliate_partners2.sql
enhance_band_profile.sql
broadcast_notifications.sql
broadcast_notifications_fk.sql
calendar.sql

13
db/up/calendar.sql Normal file
View File

@ -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
);

View File

@ -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

View File

@ -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"}

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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}"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;

View File

@ -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

View File

@ -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'}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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'