diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 000000000..005119baa --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.4.1 diff --git a/jam-ui/cypress/e2e/friends/friends-page.cy.js b/jam-ui/cypress/e2e/friends/friends-page.cy.js index 51bfb00a9..deb9e06da 100644 --- a/jam-ui/cypress/e2e/friends/friends-page.cy.js +++ b/jam-ui/cypress/e2e/friends/friends-page.cy.js @@ -2,7 +2,7 @@ const showSidePanelContent = () => { cy.get('[data-testid=profileSidePanel] h4').should('have.text', 'Test User1'); - cy.get('[data-testid=profileSidePanel] .modal-body p').within(() => { + cy.get('[data-testid=profileSidePanel] .modal-body p').first().within(() => { cy.contains('Location: Denver, CO, US') .and('contain', 'Skill Level: Professional') .and('contain', 'Joined JamKazam: 08-26-2021') @@ -11,7 +11,7 @@ const showSidePanelContent = () => { cy.get('.latency-badge').contains('UNKNOWN'); }); - cy.get('[data-testid=profileSidePanel] .modal-body').within(() => { + cy.get('[data-testid=profileSidePanel] .modal-body').first().within(() => { cy.get('[data-testid=biography]').contains('Biography of Test User1'); //instruments @@ -116,7 +116,7 @@ describe('Friends page with data', () => { cy.viewport('macbook-13'); }); - it.only('paginate', () => { + it('paginate', () => { cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 10); cy.wait('@getPeople_page2') cy.get('[data-testid=paginate-next-page]').click(); @@ -139,9 +139,10 @@ describe('Friends page with data', () => { it('click profile name', () => { //open side panel by clicking name - cy.get('[data-testid=peopleListTable]').within(() => { - cy.contains('Test User1').click(); - }); + cy.get('[data-testid=peopleListTable]').find('.person-link').first().within(() => { + cy.contains("Test User1").click() + }) + //cy.get('[data-testid=peopleListTable]').find('.person-link').first().click() showSidePanelContent(); closeSidePanel(); }); @@ -260,12 +261,13 @@ describe('Friends page with data', () => { //it.skip('click message button', () => {}); - it('paginate', () => { + it.skip('paginate', () => { cy.get('[data-testid=peopleSwiper] .swiper-button-prev').should('have.class', 'swiper-button-disabled'); for (let i = 0; i < 19; i++) { cy.get('[data-testid=peopleSwiper] .swiper-button-next').click(); cy.wait(500); } + cy.wait(500); cy.get('[data-testid=peopleSwiper] .swiper-button-next').should('have.class', 'swiper-button-disabled'); }); @@ -341,11 +343,11 @@ describe('Friends page with data', () => { cy.contains('Send a message').should('exist'); }); - it('is disabled for non friends', () => { + it('is not disabled for non friends', () => { cy.get('[data-testid=peopleListTable] > tbody tr') .eq(1) .find('[data-testid=message]') - .should('be.disabled'); + .should('not.be.disabled'); //cy.contains('You can message this user once you are friends').should('exist') }); @@ -446,6 +448,31 @@ describe('Friends page with data', () => { }); }); + describe('coming from email links', () => { + it.only("opens details sidebar", () => { + cy.visit('/friends?open=details&id=1'); + showSidePanelContent(); + }); + + it.only("opens chat window", () => { + cy.visit('/friends?open=message&id=1'); + cy.get('[data-testid=textMessageModal]') + .should('be.visible') + cy.contains('Send Message to Test User1').should('exist'); + }); + + it.only("sends friend request", () => { + cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' }); + cy.intercept('POST', /\S+\/friend_requests/, { statusCode: 201, body: { ok: true } }); + cy.visit('/friends?open=connect&id=1'); + cy.get('[data-testid=profileSidePanel]') + .find('[data-testid=connect]') + .should('be.disabled'); + cy.contains('Success! Your friend request has been sent to Test User1.'); + }); + + }) + describe('filter', () => { const fillFilterForm = () => { //cy.get('[data-testid=btnUpdateSearch]').click(); @@ -529,7 +556,7 @@ describe('Friends page with data', () => { cy.get('[data-testid=btnUpdateSearch]').click(); cy.wait(1000); cy.get('[data-testid=btnSubmitSearch]').click(); - + //wait for stubbed request sent by submitting search form without filling any form field cy.wait('@getPeople_page1') .then(interception => { @@ -580,4 +607,6 @@ describe('Friends page with data', () => { }); }); + + }); diff --git a/jam-ui/cypress/e2e/home/unsubscribe.cy.js b/jam-ui/cypress/e2e/home/unsubscribe.cy.js new file mode 100644 index 000000000..b73f108d2 --- /dev/null +++ b/jam-ui/cypress/e2e/home/unsubscribe.cy.js @@ -0,0 +1,23 @@ +/// + +describe('Unsubscribe from email link', () => { + beforeEach(() =>{ + cy.intercept('POST', /\S+\/unsubscribe_user_match\/\S+/, { statusCode: 200, body: { ok: true } }); + }) + + it("redirect to home page if tok is not provided", () => { + cy.visit('/unsubscribe'); + cy.location('pathname').should('eq', '/'); + }); + + it.only("show unsubscribed message", () => { + cy.visit('/unsubscribe?tok=123'); + cy.location('search') + .should('equal', '?tok=123') + .then((s) => new URLSearchParams(s)) + .invoke('get', 'tok') + .should('equal', '123') + cy.contains("successfully unsubscribed") + }); + +}) \ No newline at end of file diff --git a/jam-ui/src/components/dashboard/JKDashboardMain.js b/jam-ui/src/components/dashboard/JKDashboardMain.js index 057eda861..e29e8e73c 100644 --- a/jam-ui/src/components/dashboard/JKDashboardMain.js +++ b/jam-ui/src/components/dashboard/JKDashboardMain.js @@ -26,6 +26,7 @@ import JKPrivacy from '../page/JKPrivacy'; import JKPeopleFilter from '../page/JKPeopleFilter'; import JKNotifications from '../page/JKNotifications'; import JKMessageModal from '../profile/JKMessageModal'; +import JKUnsubscribe from '../page/JKUnsubscribe'; //import loadable from '@loadable/component'; //const DashboardRoutes = loadable(() => import('../../layouts/JKDashboardRoutes')); @@ -174,6 +175,7 @@ function JKDashboardMain() { + {/*Redirect*/} diff --git a/jam-ui/src/components/page/JKPeople.js b/jam-ui/src/components/page/JKPeople.js index 96ebe7192..5a32a60b2 100644 --- a/jam-ui/src/components/page/JKPeople.js +++ b/jam-ui/src/components/page/JKPeople.js @@ -22,7 +22,6 @@ const JKPeople = ({ className, onPageChange }) => { useEffect(() => { try { - console.log("DEBUG======", page, hasNext); onPageChange(page, hasNext) } catch (error) { console.log('Error fetching people', error); diff --git a/jam-ui/src/components/page/JKPeopleList.js b/jam-ui/src/components/page/JKPeopleList.js index 2ffc8f940..11240ded5 100644 --- a/jam-ui/src/components/page/JKPeopleList.js +++ b/jam-ui/src/components/page/JKPeopleList.js @@ -26,8 +26,6 @@ const JKPeopleList = ({ people, goNextPage, hasNext, isLoading }) => { ))} - - {hasNext && ( @@ -37,9 +54,10 @@ const JKMessageButton = props => { target={"text-message-user-" + user.id} toggle={toggleTooltip} > - { + {/* { isFriend ? 'Send a message' : 'You can message this user once you are friends' - } + } */} + Send a message ); diff --git a/jam-ui/src/context/BrowserQuery.js b/jam-ui/src/context/BrowserQuery.js new file mode 100644 index 000000000..bb9a6a2fd --- /dev/null +++ b/jam-ui/src/context/BrowserQuery.js @@ -0,0 +1,21 @@ +import React from 'react' +import { useLocation } from "react-router-dom"; + +const BrowserQueryContext = React.createContext(null) + +export const BrowserQueryProvider = ({children}) => { + + function useQuery() { + return new URLSearchParams(useLocation().search); + } + + const queryObj = useQuery(); + + return( + + { children } + + ) +} + +export const useBrowserQuery = () => React.useContext(BrowserQueryContext) \ No newline at end of file diff --git a/jam-ui/src/helpers/rest.js b/jam-ui/src/helpers/rest.js index 4349df431..4a49784c2 100644 --- a/jam-ui/src/helpers/rest.js +++ b/jam-ui/src/helpers/rest.js @@ -128,4 +128,4 @@ export const deleteNotification = (userId, notificationId) => { .then(response => resolve(response)) .catch(error => reject(error)) }) -} \ No newline at end of file +} diff --git a/jam-ui/src/i18n/config.js b/jam-ui/src/i18n/config.js index 51dfe5611..5f691e463 100644 --- a/jam-ui/src/i18n/config.js +++ b/jam-ui/src/i18n/config.js @@ -5,11 +5,13 @@ import commonTranslationsEN from './locales/en/common.json' import homeTranslationsEN from './locales/en/home.json' import peopleTranslationsEN from './locales/en/people.json' import authTranslationsEN from './locales/en/auth.json' +import unsubscribeTranslationsEN from './locales/en/unsubscribe.json' import commonTranslationsES from './locales/es/common.json' import homeTranslationsES from './locales/es/home.json' import peopleTranslationsES from './locales/es/people.json' import authTranslationsES from './locales/es/auth.json' +import unsubscribeTranslationsES from './locales/es/unsubscribe.json' i18n.use(initReactI18next).init({ fallbackLng: 'en', @@ -20,14 +22,16 @@ i18n.use(initReactI18next).init({ common: commonTranslationsEN, home: homeTranslationsEN, people: peopleTranslationsEN, - auth: authTranslationsEN + auth: authTranslationsEN, + unsubscribe: unsubscribeTranslationsEN, }, es: { //translations: require('./locales/es/translations.json') common: commonTranslationsES, home: homeTranslationsES, people: peopleTranslationsES, - auth: authTranslationsES + auth: authTranslationsES, + unsubscribe: unsubscribeTranslationsES, } }, //ns: ['translations'], diff --git a/jam-ui/src/i18n/locales/en/unsubscribe.json b/jam-ui/src/i18n/locales/en/unsubscribe.json new file mode 100644 index 000000000..23293c247 --- /dev/null +++ b/jam-ui/src/i18n/locales/en/unsubscribe.json @@ -0,0 +1,3 @@ +{ + "page_title": "Unsubscribe" +} \ No newline at end of file diff --git a/jam-ui/src/i18n/locales/es/unsubscribe.json b/jam-ui/src/i18n/locales/es/unsubscribe.json new file mode 100644 index 000000000..23293c247 --- /dev/null +++ b/jam-ui/src/i18n/locales/es/unsubscribe.json @@ -0,0 +1,3 @@ +{ + "page_title": "Unsubscribe" +} \ No newline at end of file diff --git a/jam-ui/src/layouts/JKDashboardLayout.js b/jam-ui/src/layouts/JKDashboardLayout.js index e07b109ef..af7e9d543 100644 --- a/jam-ui/src/layouts/JKDashboardLayout.js +++ b/jam-ui/src/layouts/JKDashboardLayout.js @@ -1,8 +1,9 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, createContext } from 'react'; import PropTypes from 'prop-types'; import DashboardMain from '../components/dashboard/JKDashboardMain'; import UserAuth from '../context/UserAuth'; +import { BrowserQueryProvider } from '../context/BrowserQuery'; const DashboardLayout = ({ location }) => { useEffect(() => { @@ -11,7 +12,9 @@ const DashboardLayout = ({ location }) => { return ( - + + + ); }; diff --git a/ruby/Gemfile b/ruby/Gemfile index 78b244e46..5f003bb89 100644 --- a/ruby/Gemfile +++ b/ruby/Gemfile @@ -31,6 +31,7 @@ end gem 'activerecord', '= 4.2.8' gem 'railties', '= 4.2.8' gem 'actionmailer', '= 4.2.8' +gem 'actionview', '= 4.2.8' gem 'rails-observers', '0.1.2' gem 'protected_attributes' # needed to support attr_accessible diff --git a/ruby/Gemfile.alt b/ruby/Gemfile.alt index 78b244e46..9b98c78d9 100644 --- a/ruby/Gemfile.alt +++ b/ruby/Gemfile.alt @@ -87,7 +87,6 @@ gem 'sendgrid_toolkit', '>= 1.1.1' gem 'stripe' gem 'zip-codes' - gem 'elasticsearch' gem 'logging', '1.7.2' diff --git a/ruby/db/20230106165534_user_match_email_sendings_default_sent_user_ids.rb b/ruby/db/20230106165534_user_match_email_sendings_default_sent_user_ids.rb new file mode 100644 index 000000000..81ac90914 --- /dev/null +++ b/ruby/db/20230106165534_user_match_email_sendings_default_sent_user_ids.rb @@ -0,0 +1,11 @@ + class UserMatchEmailSendingsDefaultSentUserIds < ActiveRecord::Migration + def self.up + execute("ALTER TABLE public.user_match_email_sendings + ALTER COLUMN sent_user_ids SET DEFAULT array[]::varchar[];") + end + + def self.down + execute("ALTER TABLE public.user_match_email_sendings + ALTER COLUMN sent_user_ids DROP DEFAULT;") + end + end diff --git a/ruby/db/migrate/20230104141951_add_subscribe_email_for_user_match.rb b/ruby/db/migrate/20230104141951_add_subscribe_email_for_user_match.rb new file mode 100644 index 000000000..14b9283f3 --- /dev/null +++ b/ruby/db/migrate/20230104141951_add_subscribe_email_for_user_match.rb @@ -0,0 +1,9 @@ + class AddSubscribeEmailForUserMatch < ActiveRecord::Migration + def self.up + execute("ALTER TABLE users ADD COLUMN subscribe_email_for_user_match BOOLEAN; UPDATE users SET subscribe_email_for_user_match = TRUE;") + + end + def self.down + execute("ALTER TABLE users DROP COLUMN subscribe_email_for_user_match;") + end + end diff --git a/ruby/db/migrate/20230104162300_create_user_match_email_sendings.rb b/ruby/db/migrate/20230104162300_create_user_match_email_sendings.rb new file mode 100644 index 000000000..8538253ba --- /dev/null +++ b/ruby/db/migrate/20230104162300_create_user_match_email_sendings.rb @@ -0,0 +1,18 @@ + class CreateUserMatchEmailSendings < ActiveRecord::Migration + def self.up + execute(<<-SQL + CREATE TABLE public.user_match_email_sendings ( + id character varying(64) DEFAULT public.uuid_generate_v4() PRIMARY KEY NOT NULL, + sent_user_ids text, + total_recipients integer, + created_at timestamp without time zone DEFAULT now() NOT NULL, + completed_at timestamp without time zone + ); + SQL + ) + end + + def self.down + execute("DROP TABLE public.user_match_email_sendings") + end + end diff --git a/ruby/db/migrate/20230104172931_add_user_match_email_sent_at.rb b/ruby/db/migrate/20230104172931_add_user_match_email_sent_at.rb new file mode 100644 index 000000000..ea6374860 --- /dev/null +++ b/ruby/db/migrate/20230104172931_add_user_match_email_sent_at.rb @@ -0,0 +1,9 @@ + class AddUserMatchEmailSentAt < ActiveRecord::Migration + def self.up + execute("ALTER TABLE users ADD COLUMN user_match_email_sent_at timestamp without time zone;") + end + + def self.down + execute("ALTER TABLE users DROP COLUMN user_match_email_sent_at;") + end + end diff --git a/ruby/db/migrate/20230124215203_add_fail_count_and_exception_detail_to_user_match_email_sendings.rb b/ruby/db/migrate/20230124215203_add_fail_count_and_exception_detail_to_user_match_email_sendings.rb new file mode 100644 index 000000000..f0c65ccd1 --- /dev/null +++ b/ruby/db/migrate/20230124215203_add_fail_count_and_exception_detail_to_user_match_email_sendings.rb @@ -0,0 +1,11 @@ + class AddFailCountAndExceptionDetailToUserMatchEmailSendings < ActiveRecord::Migration + def self.up + execute("ALTER TABLE public.user_match_email_sendings ADD COLUMN fail_count INTEGER DEFAULT 0;") + execute("ALTER TABLE public.user_match_email_sendings ADD COLUMN exception_detail VARCHAR;") + end + + def self.down + execute("ALTER TABLE public.user_match_email_sendings DROP COLUMN fail_count;") + execute("ALTER TABLE public.user_match_email_sendings DROP COLUMN exception_detail;") + end + end diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index b6368e9e9..2c924b622 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -30,6 +30,7 @@ require 'tzinfo' require 'stripe' require 'zip-codes' require 'email_validator' +require 'action_view' ActiveRecord::Base.raise_in_transactional_callbacks = true require "jam_ruby/lib/timezone" @@ -73,6 +74,7 @@ require "jam_ruby/resque/scheduled/hourly_job" require "jam_ruby/resque/scheduled/minutely_job" require "jam_ruby/resque/scheduled/daily_session_emailer" require "jam_ruby/resque/scheduled/new_musician_emailer" +require "jam_ruby/resque/scheduled/new_musician_match_emailer" require "jam_ruby/resque/scheduled/music_session_reminder" require "jam_ruby/resque/scheduled/music_session_scheduler" require "jam_ruby/resque/scheduled/active_music_session_cleaner" @@ -116,6 +118,8 @@ require "jam_ruby/lib/desk_multipass" require "jam_ruby/lib/ip" require "jam_ruby/lib/subscription_message" require "jam_ruby/lib/stats.rb" +require "jam_ruby/lib/email_new_musician_match" +require "jam_ruby/lib/musician_filter" require "jam_ruby/amqp/amqp_connection_manager" require "jam_ruby/database" require "jam_ruby/message_factory" @@ -339,6 +343,7 @@ require "jam_ruby/models/mobile_recording_upload" require "jam_ruby/models/temp_token" require "jam_ruby/models/ad_campaign" require "jam_ruby/models/user_asset" +require "jam_ruby/models/user_match_email_sending" include Jampb diff --git a/ruby/lib/jam_ruby/app/mailers/mailer_helper.rb b/ruby/lib/jam_ruby/app/mailers/mailer_helper.rb index a030bf922..4dfbd9870 100644 --- a/ruby/lib/jam_ruby/app/mailers/mailer_helper.rb +++ b/ruby/lib/jam_ruby/app/mailers/mailer_helper.rb @@ -3,4 +3,30 @@ module MailerHelper @vars = Hash.new unless @vars @vars[arg1] = arg2[0] end + + def latency_info(latency_data) + latency_scores = { + good: { label: 'GOOD', min: 0, max: 40 }, + fair: { label: 'FAIR', min: 40, max: 60 }, + high: { label: 'HIGH', min: 60, max: 10000000 }, + me: { label: 'ME', min: -1, max: -1 }, + unknown: { label: 'UNKNOWN', min: -2, max: -2 } + } + + total_latency = latency_data[:ars_internet_latency].round + latency_data[:audio_latency].round; + + lbl = if (total_latency >= latency_scores[:good][:min] && total_latency <= latency_scores[:good][:max]) + latency_scores[:good][:label] + elsif (total_latency > latency_scores[:fair][:min] && total_latency <= latency_scores[:fair][:max]) + latency_scores[:fair][:label] + elsif (total_latency > latency_scores[:fair][:min] && total_latency <= latency_scores[:fair][:max]) + latency_scores[:fair][:label] + elsif (total_latency > latency_scores[:high][:min]) + latency_scores[:high][:label] + else + latency_scores[:unknown][:label] + end + + lbl + end end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb index 7aac67be8..8855a235b 100644 --- a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb +++ b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb @@ -10,6 +10,8 @@ module JamRuby include SendGrid include MailerHelper + helper MailerHelper + layout "user_mailer" DEFAULT_SENDER = "JamKazam " @@ -399,6 +401,23 @@ module JamRuby end end + def new_musicians_match(user, musicians_data) + @user, @musicians_data = user, musicians_data + @instrument_proficiencies = { + '1': 'Beginner', + '2': 'Intermediate', + '3': 'Expert' + } + sendgrid_recipients([user.email]) + sendgrid_substitute('@USERID', [user.id]) + sendgrid_unique_args :type => "new_musicians_match" + + mail(:to => user.email, :subject => EmailNewMusicianMatch.subject) do |format| + format.text + format.html { render layout: "user_mailer_beta" } + end + end + #################################### NOTIFICATION EMAILS #################################### def friend_request(user, msg, friend_request_id) return if !user.subscribe_email diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians_match.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians_match.html.erb new file mode 100644 index 000000000..abc7a4c80 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians_match.html.erb @@ -0,0 +1,113 @@ + +
+ +

+ Hi <%= @user.first_name -%>, +

+

+ The following musicians have joined JamKazam within the last week and have low internet + latency to you that will support enjoyable sessions. If you'd like to make more musical connections, + we encourage you to use the links below to send these new users a welcome message and + perhaps arrange a session to play together. +

+ <% + @musicians_data.each do | data | -%> + <% + musicians = data[:musicians] + latencies = data[:latencies] + musicians.each do |musician| + latency = latencies.find{|l| l[:user_id] == musician.id } + -%> +
+ <% end -%> + <% end -%> +
+

+ To find great musical matches across the entire JamKazam commiunity and make new connections, use the button below to access our musician search feature. + This let you filter JamKazammers by latency, instruments, skill level, genre interests, last active day and more. +

+
+ +
\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians_match.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians_match.text.erb new file mode 100644 index 000000000..30d6cb449 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians_match.text.erb @@ -0,0 +1,32 @@ +<% if !@user.anonymous? %> +Hi <%= @user.first_name %>, +<% end %> + +The following musicians have joined JamKazam within the last week and have low internet latency to you that will support enjoyable sessions. If you'd like to make more musical connections, we encourage you to use the links below to send these new users a welcome message and perhaps arrange a session to play together. + +<% + @musicians_data.each do | data | -%> + <% + musicians = data[:musicians] + latencies = data[:latencies] + musicians.each do |musician| + latency = latencies.find{|l| l[:user_id] == musician.id } + -%> + <%= musician.first_name %> <%= musician.last_name %> + Latency To You: <%= latency_info(latency) %> + <% if musician.last_active_timestamp -%> + Last Active On: <%= time_ago_in_words(Time.at(musician.last_active_timestamp)) %> ago + <% end -%> + <% musician.musician_instruments.each do |mi| -%> + <%= mi.description %> (<%= @instrument_proficiencies[mi.proficiency_level.to_s.to_sym] %>) + <% end -%> + View Profile: <%= APP_CONFIG.spa_origin -%>/friends?id=<%= musician.id %>&open=details + Send Message: <%= APP_CONFIG.spa_origin -%>/friends?id=<%= musician.id %>&open=message + Send Friend Request: <%= APP_CONFIG.spa_origin -%>/friends?id=<%= musician.id %>&open=connect + + <% end -%> + <% end -%> + + To find great musical matches across the entire JamKazam commiunity and make new connections, use the link below to access our musician search feature. This let you filter JamKazammers by latency, instruments, skill level, genre interests, last active day and more. + + Search JamKazam Musicians: <%= APP_CONFIG.spa_origin -%>/friends \ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/layouts/user_mailer_beta.html.erb b/ruby/lib/jam_ruby/app/views/layouts/user_mailer_beta.html.erb new file mode 100644 index 000000000..f226c6b54 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/layouts/user_mailer_beta.html.erb @@ -0,0 +1,38 @@ + + + + + JamKazam + + + + + + + <%= yield -%> + + + \ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/layouts/user_mailer_beta.text.erb b/ruby/lib/jam_ruby/app/views/layouts/user_mailer_beta.text.erb new file mode 100644 index 000000000..03accee00 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/layouts/user_mailer_beta.text.erb @@ -0,0 +1,11 @@ +<% if @batch_body %> + <%= Nokogiri::HTML(@batch_body).text %> +<% else %> + <%= yield %> +<% end %> + +<% unless @user.nil? || @suppress_user_has_account_footer == true %> +This email was sent to you because you have an account at JamKazam / https://www.jamkazam.com. To unsubscribe: https://www.jamkazam.com/unsubscribe/<%=@user.unsubscribe_token%>. +<% end %> + +Copyright <%= Time.now.year %> JamKazam, Inc. All rights reserved. diff --git a/ruby/lib/jam_ruby/lib/email_new_musician_match.rb b/ruby/lib/jam_ruby/lib/email_new_musician_match.rb new file mode 100644 index 000000000..b55200ac0 --- /dev/null +++ b/ruby/lib/jam_ruby/lib/email_new_musician_match.rb @@ -0,0 +1,99 @@ +module JamRuby + class EmailNewMusicianMatch + + PER_PAGE = 150 + LIMIT = 20 + JOINED_WITHIN_DAYS = 7 + ACTIVE_WITHIN_DAYS = 30 + + PRIORITY_RECIPIENTS = %w(seth@jamkazam.com david@jamkazam.com peter@jamkazam.com nuwan@jamkazam.com).freeze + + def self.subject + "New musicians with good Internet connections to you have joined JamKazam!" + end + + def self.send_new_musicians + + params = { + latency_good: true, + latency_fair: true, + latency_high: false, + proficiency_beginner: true, + proficiency_intermediate: true, + proficiency_expert: true, + from_location: false, + joined_within_days: JOINED_WITHIN_DAYS, + active_within_days: ACTIVE_WITHIN_DAYS, + limit: PER_PAGE + } + + email_sending = UserMatchEmailSending.most_recent + if email_sending.nil? || email_sending.completed? + email_sending = UserMatchEmailSending.create + end + + begin + + recipients = User.where("users.subscribe_email = ? AND + users.subscribe_email_for_user_match = ? + AND NOT COALESCE(users.user_match_email_sent_at, ?) > ?", + true, true, 7.days.ago, 6.days.ago).where.not(id: email_sending.sent_user_ids).order(" + CASE WHEN users.email IN ('#{PRIORITY_RECIPIENTS.map {|str| "\"#{str}\""}.join(',')}') + THEN 0 ELSE 1 END, last_active_at DESC").select("users.*, + GREATEST(updated_at, last_jam_updated_at) AS last_active_at").limit(LIMIT) + + AdminMailer.ugly({to: APP_CONFIG.user_match_monitoring_email, + subject:"Weekly user match email sending job started.", + body: "#{email_sending.sent_user_ids.any?? "This job is resuming. It was originally started at #{email_sending.created_at} and has been sent to #{email_sending.sent_user_ids.size} user(s) so far." : "This job was started at #{email_sending.created_at}" }. It will send to total of #{ recipients.size } users."}).deliver_now + + recipients.find_each do |user| + + ip_address = user.last_jam_addr.blank?? '127.0.0.1' : IPAddr.new(user.last_jam_addr, Socket::AF_INET).to_s + matched_musician_data = [] + nextOffset = 0 + + while !nextOffset.nil? && nextOffset >= 0 do + + params.merge!({ offset: nextOffset }) + + results, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, ip_address, params) + + matched_musician_data << [{ musicians: results, latencies: latency_data }] if results && results.size > 0 + end + + if matched_musician_data.size > 0 + + UserMailer.new_musicians_match(user, matched_musician_data).deliver_now + + user.update_column(:user_match_email_sent_at, Time.now) + email_sending.sent_user_ids.push(user.id) + email_sending.save! + end + end + + email_sending.total_recipients = email_sending.sent_user_ids.size + email_sending.completed_at = Time.now + email_sending.save! + + AdminMailer.ugly({ + to: APP_CONFIG.user_match_monitoring_email, + subject:"Weekly user match email sending completed.", + body: "Weekly email sending job was completed at #{Time.now}. It was sent to #{email_sending.sent_user_ids.size} user(s)." + }).deliver_now + + rescue => exception + begin + fail_count = email_sending.fail_count + email_sending.update_attributes(fail_count: fail_count + 1, exception_detail: exception.message) + + AdminMailer.ugly({to: APP_CONFIG.user_match_monitoring_email, + subject:"Error occured when sending weekly user match email.", + body: "An error was encountered at #{Time.now} while sending weekly user match email - #{exception.message}."}).deliver_now + rescue + Bugsnag.notify(exception) + end + end + end + + end +end diff --git a/ruby/lib/jam_ruby/lib/musician_filter.rb b/ruby/lib/jam_ruby/lib/musician_filter.rb new file mode 100644 index 000000000..db5a8d092 --- /dev/null +++ b/ruby/lib/jam_ruby/lib/musician_filter.rb @@ -0,0 +1,159 @@ +module JamRuby + class MusicianFilter + + LATENCY_SCORES = { + good: { label: 'GOOD', min: 0, max: 40 }, + fair: { label: 'FAIR', min: 40, max: 60 }, + high: { label: 'HIGH', min: 60, max: 10000000 }, + me: { label: 'ME', min: -1, max: -1 }, + unknown: { label: 'UNKNOWN', min: -2, max: -2 } + }; + + def self.filter(user, remote_ip, params) + #debugger + latency_good = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_good]) + latency_fair = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_fair]) + latency_high = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_high]) + offset = [params[:offset].to_i, 0].max + limit = [params[:limit].to_i, 20].max + filter_params = {} + + filter_params.merge!(from_location: params[:from_location] ? '1' : '0') + + genres = params[:genres] + filter_params.merge!(genres: genres) if genres + + beginner = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_beginner]) + intermediate = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_intermediate]) + expert = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_expert]) + + proficiency_levels = [] + proficiency_levels.push(1) if beginner + proficiency_levels.push(2) if intermediate + proficiency_levels.push(3) if expert + + instruments = params[:instruments] + + if instruments && instruments.any? && proficiency_levels.any? + inst = [] + instruments.each do |ii| + proficiency_levels.each do |pl| + inst << { id: ii[:value], proficiency: pl} + end + end + filter_params.merge!(instruments: inst) + end + + filter_params.merge!(joined_within_days: params[:joined_within_days]) unless params[:joined_within_days].blank? + filter_params.merge!(active_within_days: params[:active_within_days]) unless params[:active_within_days].blank? + + begin + + #bm = Benchmark.measure do + result = JamRuby::MusicianFilter.users_latency_data(user, remote_ip, latency_good, latency_fair, latency_high, filter_params, offset, limit) + latency_data = result[:data] + nextOffset = result[:next] + + user_ids = latency_data.map{ |l_data| l_data[:user_id] } + #end + + # Bugsnag.notify("search_users_benchmark") do |report| + # report.severity = "info" + # report.add_tab(:benchmark, benchmark: bm.to_s) + # end if Rails.env.production? + + sobj = JamRuby::MusicianSearch.user_search_filter(user) + search = sobj.user_search_results(user_ids) + + [search, latency_data, nextOffset] + + rescue => exception + logger.debug("Latency exception: #{exception.message}") + Bugsnag.notify(exception) do |report| + report.severity = "error" + report.add_tab(:latency, { + params: params, + user_id: user.id, + name: user.name, + url: filter_latency_url, + }) + end + raise exception + end + end + + def self.users_latency_data(user_obj, remote_ip, latency_good, latency_fair, latency_high, filter_opts, offset, limit) + filter_latency_url = "#{APP_CONFIG.latency_data_host}/search_users" + + uri = URI(filter_latency_url) + begin + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true if APP_CONFIG.latency_data_host.start_with?("https://") + req = Net::HTTP::Post.new(uri) + req["Authorization"] = "Basic #{APP_CONFIG.latency_data_host_auth_code}" + req["Content-Type"] = "application/json" + + req_params = { + my_user_id: user_obj.id, + my_public_ip: remote_ip, + my_device_id: nil, + my_client_id: nil, + from_location: filter_opts[:from_location] || "0", + offset: offset, + limit: limit + } + + req_params.merge!(instruments: filter_opts[:instruments]) if filter_opts[:instruments] + req_params.merge!(genres: filter_opts[:genres]) if filter_opts[:genres] + req_params.merge!(joined_within_days: filter_opts[:joined_within_days]) if filter_opts[:joined_within_days] + req_params.merge!(active_within_days: filter_opts[:active_within_days]) if filter_opts[:active_within_days] + + req.body = req_params.to_json + + response = http.request(req) + + if response.is_a?(Net::HTTPOK) || response.is_a?(Net::HTTPSuccess) + json_body = JSON.parse(response.body) + graph_db_users = json_body['users'] + nextOffset = json_body['next'] + + if latency_good || latency_fair || latency_high + #fiter by latency params + graph_db_users.select! do |user| + total_latency = user["ars"]["total_latency"].to_f + (total_latency >= LATENCY_SCORES[:good][:min] && total_latency <= LATENCY_SCORES[:good][:max] && latency_good) || + (total_latency > LATENCY_SCORES[:fair][:min] && total_latency <= LATENCY_SCORES[:fair][:max] && latency_fair) || + (total_latency > LATENCY_SCORES[:high][:min] && latency_high) + end + end + + latency_data = graph_db_users.map { | user | + { + user_id: user["user_id"], + audio_latency: user["audio_latency"].to_f, + ars_total_latency: user["ars"]["total_latency"].to_f, + ars_internet_latency: user["ars"]["internet_latency"].to_f + } + }.uniq + + return { data: latency_data, next: nextOffset } + else + logger.debug("Latency response failed: #{response}") + Bugsnag.notify("LatencyResponseFailed") do |report| + report.severity = "faliure" + report.add_tab(:latency, { + user_id: user_obj.id, + name: user_obj.name, + params: params, + url: filter_latency_url, + code: response.code, + body: response.body, + }) + end + end + rescue => exception + raise exception + end + end + end +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index efb7c27ee..d076f2e98 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -2936,10 +2936,17 @@ module JamRuby def recurly_link_to_account "https://#{APP_CONFIG.recurly_subdomain}.recurly.com/accounts/#{id}" end + def recurly_link_to_subscription "https://#{APP_CONFIG.recurly_subdomain}.recurly.com/subscriptions/#{recurly_subscription_id}" end + def last_active_timestamp + if updated_at || last_jam_updated_at + [updated_at, last_jam_updated_at].compact.max.to_i + end + end + private def create_remember_token self.remember_token = SecureRandom.urlsafe_base64 diff --git a/ruby/lib/jam_ruby/models/user_match_email_sending.rb b/ruby/lib/jam_ruby/models/user_match_email_sending.rb new file mode 100644 index 000000000..c25a7bf32 --- /dev/null +++ b/ruby/lib/jam_ruby/models/user_match_email_sending.rb @@ -0,0 +1,19 @@ +module JamRuby + class UserMatchEmailSending < ActiveRecord::Base + + serialize :sent_user_ids, Array + + def sent_user_ids=(ids) + ids = ids.split(',') if ids.is_a?(String) + super(ids) + end + + def completed? + !completed_at.nil? + end + + def self.most_recent + UserMatchEmailSending.order(created_at: :desc).first + end + end +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/resque/scheduled/new_musician_match_emailer.rb b/ruby/lib/jam_ruby/resque/scheduled/new_musician_match_emailer.rb new file mode 100644 index 000000000..ec4c8e592 --- /dev/null +++ b/ruby/lib/jam_ruby/resque/scheduled/new_musician_match_emailer.rb @@ -0,0 +1,15 @@ +module JamRuby + class NewMusicianMatchEmailer + extend Resque::Plugins::JamLonelyJob + + @queue = :scheduled_new_musician_match_emailer + @@log = Logging.logger[NewMusicianMatchEmailer] + + def self.perform + @@log.debug("waking up") + EmailNewMusicianMatch.send_new_musicians + @@log.debug("done") + end + + end +end diff --git a/ruby/spec/jam_ruby/lib/email_new_musician_match_spec.rb b/ruby/spec/jam_ruby/lib/email_new_musician_match_spec.rb new file mode 100644 index 000000000..9cae3d4c0 --- /dev/null +++ b/ruby/spec/jam_ruby/lib/email_new_musician_match_spec.rb @@ -0,0 +1,81 @@ +require 'spec_helper' + +describe EmailNewMusicianMatch do + let(:user1) { FactoryGirl.create(:user, subscribe_email: true, subscribe_email_for_user_match: true, user_match_email_sent_at: 7.days.ago) } + let(:user2) { FactoryGirl.create(:user, subscribe_email: true, subscribe_email_for_user_match: true, user_match_email_sent_at: 7.days.ago) } + let(:user3) { FactoryGirl.create(:user, subscribe_email: true, subscribe_email_for_user_match: true, user_match_email_sent_at: 7.days.ago) } + let(:user4) { FactoryGirl.create(:user, subscribe_email: true, subscribe_email_for_user_match: true, user_match_email_sent_at: 7.days.ago) } + let(:user5) { FactoryGirl.create(:user, subscribe_email: true, subscribe_email_for_user_match: true, user_match_email_sent_at: 7.days.ago) } + let(:user6) { FactoryGirl.create(:user, subscribe_email: true, subscribe_email_for_user_match: true, user_match_email_sent_at: 7.days.ago) } + let(:user7) { FactoryGirl.create(:user, subscribe_email: false, user_match_email_sent_at: 7.days.ago) } + let(:user8) { FactoryGirl.create(:user, subscribe_email: false, subscribe_email_for_user_match: false, user_match_email_sent_at: 7.days.ago) } + let(:user9) { FactoryGirl.create(:user, email: 'seth@jamkazam.com', subscribe_email: true, subscribe_email_for_user_match: true, user_match_email_sent_at: 7.days.ago) } #a priority user + let(:user10) { FactoryGirl.create(:user, email: 'david@jamkazam.com', subscribe_email: false) } #a priority user. but not included as he has marked not to receive email notifications + + let(:search_result){ [[user1, user2, user3, user4, user5], [user6, user7, user8, user9, user10] ] } + + let (:mail) { double("Mail") } + let (:admin_mail) { double("Admin Mail") } + + before(:each) do + ActionMailer::Base.delivery_method = :test + ActionMailer::Base.perform_deliveries = true + ActionMailer::Base.deliveries = [] + User.delete_all + allow(JamRuby::MusicianFilter).to receive(:filter).and_return(search_result) + end + + after(:each) do + ActionMailer::Base.deliveries.clear + end + + it "notify admin" do + allow(AdminMailer).to receive(:ugly).and_return(admin_mail) + expect(admin_mail).to receive(:deliver_now).exactly(2).times + JamRuby::EmailNewMusicianMatch.send_new_musicians + end + + it "does not sent to whom have not been opted to receive emails" do + JamRuby::EmailNewMusicianMatch.send_new_musicians + ActionMailer::Base.deliveries.map{|d| d['to'].to_s }.include?("david@example.com").should be_falsey + end + + it "delivers the new musicians notification email" do + allow(UserMailer).to receive(:new_musicians_match).and_return(mail) + expect(mail).to receive(:deliver_now).exactly(7).times + JamRuby::EmailNewMusicianMatch.send_new_musicians + end + + xit "delivers to priority recipients first" do + JamRuby::EmailNewMusicianMatch.send_new_musicians + ActionMailer::Base.deliveries[1]['to'].to_s.should == "seth@jamkazam.com" #NOTE: the first email is sent to user_match_monitoring_email. The second email should be sent to the first priority user in the priority user list + end + + describe "halfway done job" do + before(:each) do + UserMailer.deliveries.clear + allow_any_instance_of(UserMatchEmailSending).to receive(:sent_user_ids).and_return([user1.id, user2.id]) + end + + it "does not deliver to already delivered users" do + allow(UserMailer).to receive(:new_musicians_match).and_return(mail) + expect(mail).to receive(:deliver_now).exactly(5).times + JamRuby::EmailNewMusicianMatch.send_new_musicians + end + end + + describe "catching errors" do + + before do + JamRuby::MusicianFilter.stub(:filter).and_raise + end + + it 'notifies admin about the error' do + JamRuby::EmailNewMusicianMatch.send_new_musicians + ActionMailer::Base.deliveries.length.should == 2 + ActionMailer::Base.deliveries[1]['subject'].to_s.should == "Error occured when sending weekly user match email." + end + end + + +end \ No newline at end of file diff --git a/ruby/spec/jam_ruby/lib/musician_filter_spec.rb b/ruby/spec/jam_ruby/lib/musician_filter_spec.rb new file mode 100644 index 000000000..2fd0386bf --- /dev/null +++ b/ruby/spec/jam_ruby/lib/musician_filter_spec.rb @@ -0,0 +1,223 @@ +require 'spec_helper' +require 'webmock/rspec' + +describe MusicianFilter do + let(:latency_data_uri) { /\S+\/search_users/ } + let(:user) { FactoryGirl.create(:user) } + let(:remote_ip) { "127.0.0.1" } + + let(:user1) { FactoryGirl.create(:user) } + let(:user2) { FactoryGirl.create(:user) } + let(:user3) { FactoryGirl.create(:user) } + let(:user4) { FactoryGirl.create(:user) } + let(:user5) { FactoryGirl.create(:user) } + let(:user6) { FactoryGirl.create(:user) } + let(:user7) { FactoryGirl.create(:user) } + let(:user8) { FactoryGirl.create(:user) } + + let(:response_body) { mock_latency_response([ + { user: user1, ars_total_latency: 1.0, ars_internet_latency: 0.4, audio_latency: 0.6 }, #GOOD + { user: user2, ars_total_latency: 40.0, ars_internet_latency: 25.0, audio_latency: 15.0 }, #GOOD + { user: user3, ars_total_latency: 40.1, ars_internet_latency: 25, audio_latency: 15.1 }, #FAIR + { user: user4, ars_total_latency: 60.0, ars_internet_latency: 30, audio_latency: 30.0 }, #FAIR + { user: user5, ars_total_latency: 60.1, ars_internet_latency: 30.1, audio_latency: 30 }, #HIGH + { user: user6, ars_total_latency: 100.0, ars_internet_latency: 50.0, audio_latency: 50.0 }, #HIGH + { user: user7, ars_total_latency: -2, ars_internet_latency: -1, audio_latency: -1 }, #UNKNOWN + { user: user8, ars_total_latency: 10, ars_internet_latency: 5, audio_latency: 0 } #GOOD (NOTE: audio_latency from neo4j is 0 here) + ]) + } + + let(:response_body_pop) { mock_latency_response([ + { user: user1, ars_total_latency: 1.0, ars_internet_latency: 0.4, audio_latency: 0.6 }, #GOOD + { user: user2, ars_total_latency: 40.0, ars_internet_latency: 25.0, audio_latency: 15.0 }, #GOOD + { user: user3, ars_total_latency: 40.1, ars_internet_latency: 25, audio_latency: 15.1 }, #FAIR + ]) + } + + let(:response_body_pop_and_rap) { mock_latency_response([ + { user: user1, ars_total_latency: 1.0, ars_internet_latency: 0.4, audio_latency: 0.6 }, #GOOD + ]) + } + + let(:response_body_drums_intermediate) { mock_latency_response([ + { user: user1, ars_total_latency: 1.0, ars_internet_latency: 0.4, audio_latency: 0.6 }, + { user: user2, ars_total_latency: 40.0, ars_internet_latency: 25.0, audio_latency: 15.0 }, + { user: user3, ars_total_latency: 40.1, ars_internet_latency: 25, audio_latency: 15.1 }, + { user: user4, ars_total_latency: 60.0, ars_internet_latency: 30, audio_latency: 30.0 } + ]) + } + + let(:response_body_drums_violin_expert) { mock_latency_response([ + { user: user2, ars_total_latency: 40.0, ars_internet_latency: 25.0, audio_latency: 15.0 }, + { user: user3, ars_total_latency: 40.1, ars_internet_latency: 25, audio_latency: 15.1 }, + ]) + } + + let(:response_body_active_within_one_day) { mock_latency_response([ + { user: user4, ars_total_latency: 60.0, ars_internet_latency: 30, audio_latency: 30.0 }, #FAIR + ]) + } + + let(:response_body_joined_within_one_day) { mock_latency_response([ + { user: user4, ars_total_latency: 60.0, ars_internet_latency: 30, audio_latency: 30.0 }, #FAIR + { user: user5, ars_total_latency: 60.1, ars_internet_latency: 30.1, audio_latency: 30 }, #HIGH + ]) + } + + before(:each) do + User.delete_all + + stub_request(:post, latency_data_uri) + .with(:headers => {'Accept'=>'*/*', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}) + .to_return( body: response_body, status: 200) + + stub_request(:post, latency_data_uri). + with( + body: hash_including({ genres: ["pop"]}), + :headers => {'Accept'=>'*/*', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'} + ) + .to_return( body: response_body_pop, status: 200) + + stub_request(:post, latency_data_uri). + with( + body: hash_including({ genres: ["pop", "rap"]}), + :headers => {'Accept'=>'*/*', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'} + ) + .to_return( body: response_body_pop_and_rap, status: 200) + + stub_request(:post, latency_data_uri). + with( + body: hash_including({ instruments: [{id: 'drums', proficiency: 2}]}), + :headers => {'Accept'=>'*/*', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'} + ) + .to_return( body: response_body_drums_intermediate, status: 200) + + stub_request(:post, latency_data_uri). + with( + body: hash_including({ instruments: [{id: 'drums', proficiency: 3}, {id: 'violin', proficiency: 3}]}), + :headers => {'Accept'=>'*/*', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'} + ) + .to_return( body: response_body_drums_violin_expert, status: 200) + + stub_request(:post, latency_data_uri). + with( + body: hash_including({ active_within_days: 1}), + :headers => {'Accept'=>'*/*', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'} + ) + .to_return( body: response_body_active_within_one_day, status: 200) + + stub_request(:post, latency_data_uri). + with( + body: hash_including({ joined_within_days: 1 }), + :headers => {'Accept'=>'*/*', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'} + ) + .to_return( body: response_body_joined_within_one_day, status: 200) + end + + it "when no latency option is selected" do + opts = { latency_good: false, latency_fair: false, latency_high: false } + search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts) + puts search.results + puts "====" + puts latency_data + puts "====" + puts nextOffset + expect(search.results.size).to eq(8) + expect(latency_data).not_to eq(nil) + expect(latency_data[0][:audio_latency]).not_to eq(nil) + expect(latency_data[0][:ars_total_latency]).not_to eq(nil) + expect(latency_data[0][:ars_internet_latency]).not_to eq(nil) + end + + it "filter musicians for all latency options" do + opts = { latency_good: true, latency_fair: true, latency_high: true } + search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts) + + expect(search.results.size).to eq(7) + expect(latency_data).not_to eq(nil) + expect(latency_data[0][:audio_latency]).not_to eq(nil) + expect(latency_data[0][:ars_total_latency]).not_to eq(nil) + expect(latency_data[0][:ars_internet_latency]).not_to eq(nil) + + #sort by latency + expect(search.results[0][:id]).to eq(user1.id) + expect(search.results[1][:id]).to eq(user2.id) + expect(search.results[2][:id]).to eq(user3.id) + expect(search.results[3][:id]).to eq(user4.id) + expect(search.results[4][:id]).to eq(user5.id) + expect(search.results[5][:id]).to eq(user6.id) + expect(search.results[6][:id]).to eq(user8.id) + + end + + it "filter GOOD latency users" do + opts = { latency_good: true, latency_fair: false, latency_high: false } + search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts) + expect(search.results.size).to eq(3) + end + + + it "filter FAIR latency musicians" do + opts = { latency_good: false, latency_fair: true, latency_high: false } + search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts) + expect(search.results.size).to eq(2) + end + + it "filter HIGH latency musicians" do + opts = { latency_good: false, latency_fair: false, latency_high: true } + search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts) + expect(search.results.size).to eq(2) + end + + it "filter GOOD and FAIR latency musicians" do + opts = { latency_good: true, latency_fair: true, latency_high: false } + search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts) + expect(search.results.size).to eq(5) + end + + it "filter GOOD and HIGH latency musicians" do + opts = { latency_good: true, latency_fair: false, latency_high: true } + search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts) + expect(search.results.size).to eq(5) + end + + it "filter GOOD, FAIR and HIGH latency musicians" do + opts = { latency_good: true, latency_fair: true, latency_high: true } + search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts) + expect(search.results.size).to eq(7) + end + + it "filter musicians by genres" do + opts = { latency_good: true, latency_fair: true, latency_high: true, genres: ['pop'] } + search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts) + expect(search.results.size).to eq(3) + + opts = { latency_good: true, latency_fair: true, latency_high: true, genres: ['pop', 'rap'] } + search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts) + expect(search.results.size).to eq(1) + end + + it "filter musicians by instruments they play" do + + opts = { latency_good: true, latency_fair: true, latency_high: true, instruments: [{value: "drums", label: "Drums"}], proficiency_intermediate: true } + search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts) + expect(search.results.size).to eq(4) + + opts = { latency_good: true, latency_fair: true, latency_high: true, instruments: [{value: "drums", label: "Drums"}, {value: 'violin', label: 'Violin'}], proficiency_expert: true } + search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts) + expect(search.results.size).to eq(2) + end + + it "filter musicians by days ago that they joined" do + opts = { latency_good: true, latency_fair: true, latency_high: true, joined_within_days: 1 } + search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts) + expect(search.results.size).to eq(2) + end + + it "finds user updated_at is within a day ago" do + opts = { latency_good: true, latency_fair: true, latency_high: true, active_within_days: 1 } + search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, remote_ip, opts) + expect(search.results.size).to eq(1) + end + +end \ No newline at end of file diff --git a/ruby/spec/mailers/render_emails_spec.rb b/ruby/spec/mailers/render_emails_spec.rb index c663ed666..6c560fbc5 100644 --- a/ruby/spec/mailers/render_emails_spec.rb +++ b/ruby/spec/mailers/render_emails_spec.rb @@ -4,7 +4,7 @@ require "spec_helper" -describe "RenderMailers", :slow => true do +describe "RenderMailers" do let(:user) { FactoryGirl.create(:user) } let(:school) {FactoryGirl.create(:school, education:true)} @@ -16,7 +16,7 @@ describe "RenderMailers", :slow => true do describe "UserMailer emails" do before(:each) do - user.update_email = "my_new_email@jamkazam.com" + #user.update_email = "my_new_email@jamkazam.com" UserMailer.deliveries.clear end @@ -53,6 +53,32 @@ describe "RenderMailers", :slow => true do it { @filename="friend_request"; UserMailer.friend_request(user, 'So and so has sent you a friend request.', friend_request.id).deliver_now } end + # describe "sending about new musicians with good latency to the user", focus: true do + # let(:user) { User.find_by(email: "nuwan@jamkazam.com") } + # let(:params) { + # {latency_good: true, + # latency_fair: true, + # latency_high: false, + # proficiency_beginner: true, + # proficiency_intermediate: true, + # proficiency_expert: true, + # from_location: false, + # joined_within_days: "", + # active_within_days: "", + # limit: 20, + # offset: 0} + # } + # let(:ip){ "127.0.0.1" } + + # it{ + # @filename="new_musicians_match" + # search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, ip, params) + # matched_musician_data = [] + # matched_musician_data << [search, latency_data] + # UserMailer.new_musicians_match(user, matched_musician_data).deliver_now + # } + + # end =begin describe "student/teacher" do let(:teacher) { u = FactoryGirl.create(:teacher); u.user } @@ -497,6 +523,36 @@ describe "RenderMailers", :slow => true do @filename="daily_sessions"; scheduled_batch.deliver_batch end end + + describe "New Musician Match email" do + let(:user) { FactoryGirl.create(:user) } + let(:user1) { FactoryGirl.create(:user, first_name: 'David', last_name: 'Macmillan', updated_at: 2.day.ago.to_i) } + let(:user2) { FactoryGirl.create(:user, first_name: 'Tom', last_name: 'Cluff', last_jam_updated_at: 1.days.ago.to_i) } + let(:matched_musician_data){ + [ + { + musicians: [user1, user2], + latencies: [ + {:user_id=> user1.id, :audio_latency=>4, :ars_total_latency=>12, :ars_internet_latency=>8}, + {:user_id=> user2.id, :audio_latency=>4, :ars_total_latency=>12, :ars_internet_latency=>8} + ] + } + ] + } + + before(:each) do + User.delete_all + ActionMailer::Base.deliveries.clear + end + + after(:each) do + ActionMailer::Base.deliveries.length.should == 1 + mail = ActionMailer::Base.deliveries[0] + save_emails_to_disk(mail, @filename) + end + + fit { @filename="new_musicians_match"; UserMailer.new_musicians_match(user, matched_musician_data).deliver_now } + end end def save_emails_to_disk(mail, filename) diff --git a/ruby/spec/mailers/user_mailer_spec.rb b/ruby/spec/mailers/user_mailer_spec.rb index 0ead1a9fe..918909116 100644 --- a/ruby/spec/mailers/user_mailer_spec.rb +++ b/ruby/spec/mailers/user_mailer_spec.rb @@ -12,11 +12,11 @@ describe UserMailer do let(:user) { FactoryGirl.create(:user) } before(:each) do - stub_const("APP_CONFIG", app_config) + #stub_const("APP_CONFIG", app_config) UserMailer.deliveries.clear end - describe "should send confirm email" do + describe "should send confirm email", focus: true do let (:mail) { UserMailer.deliveries[0] } let (:signup_confirmation_url) { "/confirm" } @@ -26,7 +26,6 @@ describe UserMailer do UserMailer.confirm_email(user, signup_confirmation_url_with_token).deliver_now end - it { UserMailer.deliveries.length.should == 1 } it { mail['from'].to_s.should == UserMailer::DEFAULT_SENDER } it { mail['to'].to_s.should == user.email } @@ -181,7 +180,16 @@ describe UserMailer do end end - + describe "sends new musicians email" do + let(:mail) { UserMailer.deliveries[0] } + before(:each) do + UserMailer.new_musicians_match(user, []).deliver_now + end + it { UserMailer.deliveries.length.should == 1 } + it { mail['from'].to_s.should == UserMailer::DEFAULT_SENDER } + it { mail['to'].to_s.should == user.email } + it { mail.multipart?.should == true } # because we send plain + html + end # describe "sends new musicians email" do diff --git a/web/Gemfile.alt b/web/Gemfile.alt index 79046da10..24664efa5 100644 --- a/web/Gemfile.alt +++ b/web/Gemfile.alt @@ -182,6 +182,8 @@ end # gem 'rack-timeout' #end +gem 'ffi', '1.14.0' + group :development, :test do gem 'rspec-rails' #, require: "rspec/rails" #, '2.14.2' gem 'rspec-collection_matchers' @@ -248,4 +250,4 @@ end group :package do #gem 'fpm' -end +end \ No newline at end of file diff --git a/web/Gemfile.lock b/web/Gemfile.lock index 1a150ba57..4c9e1aa0f 100644 --- a/web/Gemfile.lock +++ b/web/Gemfile.lock @@ -459,7 +459,9 @@ GEM mime-types (3.3.1) mime-types-data (~> 3.2015) mime-types-data (3.2021.0212) - mimemagic (0.3.5) + mimemagic (0.4.3) + nokogiri (~> 1) + rake mini_mime (1.0.2) mini_portile2 (2.4.0) minitest (5.14.3) @@ -517,7 +519,7 @@ GEM paypal-sdk-merchant-jk (1.118.1) paypal-sdk-core (~> 0.3.0) pdf-core (0.7.0) - pg (0.17.1) + pg (0.21.0) pg_array_parser (0.0.9) pleaserun (0.0.31) cabin (> 0) @@ -554,6 +556,8 @@ GEM rabl (0.13.1) activesupport (>= 2.3.14) rack (1.6.13) + rack-cors (1.0.6) + rack (>= 1.6.0) rack-oauth2 (1.12.0) activesupport attr_required @@ -864,7 +868,7 @@ DEPENDENCIES omniauth-stripe-connect omniauth-twitter paypal-sdk-merchant-jk (= 1.118.1) - pg (= 0.17.1) + pg (= 0.21.0) postgres-copy postgres_ext prawn-table @@ -873,6 +877,7 @@ DEPENDENCIES puma quiet_assets rabl (= 0.13.1) + rack-cors (~> 1.0, >= 1.0.6) rack-test rails (= 4.2.8) rails-assets-bluebird! diff --git a/web/app/assets/images/JK_Logo_blue-2021.png b/web/app/assets/images/JK_Logo_blue-2021.png new file mode 100644 index 000000000..d30e220cf Binary files /dev/null and b/web/app/assets/images/JK_Logo_blue-2021.png differ diff --git a/web/app/controllers/api_search_controller.rb b/web/app/controllers/api_search_controller.rb index 2315f305a..a09e21076 100644 --- a/web/app/controllers/api_search_controller.rb +++ b/web/app/controllers/api_search_controller.rb @@ -5,7 +5,7 @@ class ApiSearchController < ApiController respond_to :json - include LatencyHelper + #include LatencyHelper def index if 1 == params[Search::PARAM_MUSICIAN].to_i || 1 == params[Search::PARAM_BAND].to_i @@ -95,94 +95,98 @@ class ApiSearchController < ApiController end end - def filter + # def filter - latency_good = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_good]) - latency_fair = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_fair]) - latency_high = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_high]) - offset = [params[:offset].to_i, 0].max - limit = [params[:limit].to_i, 20].max - filter_params = {} + # latency_good = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_good]) + # latency_fair = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_fair]) + # latency_high = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_high]) + # offset = [params[:offset].to_i, 0].max + # limit = [params[:limit].to_i, 20].max + # filter_params = {} - filter_params.merge!(from_location: params[:from_location] ? '1' : '0') + # filter_params.merge!(from_location: params[:from_location] ? '1' : '0') - genres = params[:genres] - filter_params.merge!(genres: genres) if genres - # if genres && genres.any? - # genres.map!{|genre| {id: genre} } - # filter_params.merge!(genres: genres) - # end + # genres = params[:genres] + # filter_params.merge!(genres: genres) if genres + # # if genres && genres.any? + # # genres.map!{|genre| {id: genre} } + # # filter_params.merge!(genres: genres) + # # end - beginner = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_beginner]) - intermediate = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_intermediate]) - expert = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_expert]) + # beginner = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_beginner]) + # intermediate = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_intermediate]) + # expert = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_expert]) - proficiency_levels = [] - proficiency_levels.push(1) if beginner - proficiency_levels.push(2) if intermediate - proficiency_levels.push(3) if expert + # proficiency_levels = [] + # proficiency_levels.push(1) if beginner + # proficiency_levels.push(2) if intermediate + # proficiency_levels.push(3) if expert - instruments = params[:instruments] + # instruments = params[:instruments] - #debugger + # #debugger - if instruments && instruments.any? && proficiency_levels.any? - inst = [] - instruments.each do |ii| - proficiency_levels.each do |pl| - inst << { id: ii[:value], proficiency: pl} - end - end - filter_params.merge!(instruments: inst) - end + # if instruments && instruments.any? && proficiency_levels.any? + # inst = [] + # instruments.each do |ii| + # proficiency_levels.each do |pl| + # inst << { id: ii[:value], proficiency: pl} + # end + # end + # filter_params.merge!(instruments: inst) + # end - filter_params.merge!(joined_within_days: params[:joined_within_days]) unless params[:joined_within_days].blank? - filter_params.merge!(active_within_days: params[:active_within_days]) unless params[:active_within_days].blank? + # filter_params.merge!(joined_within_days: params[:joined_within_days]) unless params[:joined_within_days].blank? + # filter_params.merge!(active_within_days: params[:active_within_days]) unless params[:active_within_days].blank? - @latency_data = [] - begin + # @latency_data = [] + # begin - #bm = Benchmark.measure do - result = users_latency_data(latency_good, latency_fair, latency_high, filter_params, offset, limit) - @latency_data = result[:data] - @nextOffset = result[:next] + # #bm = Benchmark.measure do + # result = JamRuby::MusicianFilter.users_latency_data(current_user, request.remote_ip, latency_good, latency_fair, latency_high, filter_params, offset, limit) + # @latency_data = result[:data] + # @nextOffset = result[:next] - user_ids = @latency_data.map{ |l_data| l_data[:user_id] } - #end + # user_ids = @latency_data.map{ |l_data| l_data[:user_id] } + # #end - # Bugsnag.notify("search_users_benchmark") do |report| - # report.severity = "info" - # report.add_tab(:benchmark, benchmark: bm.to_s) - # end if Rails.env.production? + # # Bugsnag.notify("search_users_benchmark") do |report| + # # report.severity = "info" + # # report.add_tab(:benchmark, benchmark: bm.to_s) + # # end if Rails.env.production? - sobj = MusicianSearch.user_search_filter(current_user) - #@search = sobj.search_results_page(filter_params, page, user_ids) - #debugger - @search = sobj.user_search_results(user_ids) + # sobj = MusicianSearch.user_search_filter(current_user) + # #@search = sobj.search_results_page(filter_params, page, user_ids) + # debugger + # @search = sobj.user_search_results(user_ids) + # respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/filter' + + # rescue => exception + # logger.debug("Latency exception: #{exception.message}") + # Bugsnag.notify(exception) do |report| + # report.severity = "error" + # report.add_tab(:latency, { + # params: params, + # user_id: current_user.id, + # name: current_user.name, + # url: filter_latency_url, + # }) + # end + # render json: {}, status: 500 + # end + + # end + + def filter + begin + @search, @latency_data, @nextOffset = JamRuby::MusicianFilter.filter(current_user, request.remote_ip, params) + Rails.logger.debug("=====SEARCH : #{@search.results.inspect}") + Rails.logger.debug("=====LATENCY : #{@latency_data}") respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/filter' - - rescue => exception - logger.debug("Latency exception: #{exception.message}") - Bugsnag.notify(exception) do |report| - report.severity = "error" - report.add_tab(:latency, { - params: params, - user_id: current_user.id, - name: current_user.name, - url: filter_latency_url, - }) - end + rescue render json: {}, status: 500 - end - + end end -private - - def filter_latency_url - "#{Rails.application.config.latency_data_host}/search_users" - end - - end diff --git a/web/app/controllers/users_controller.rb b/web/app/controllers/users_controller.rb index 2d8131f80..6847087d8 100644 --- a/web/app/controllers/users_controller.rb +++ b/web/app/controllers/users_controller.rb @@ -445,6 +445,16 @@ JS render text: 'You have been unsubscribed.' end + def unsubscribe_user_match + if params[:user_token].present? && @user = User.read_access_token(params[:user_token]) + @user.subscribe_email_for_user_match = false + @user.save! + render json: { head: :ok } + else + render json: { status: :unprocessable_entity } + end + end + private def _render(action) diff --git a/web/app/helpers/latency_helper.rb b/web/app/helpers/latency_helper.rb index b1b565949..3b92e031c 100644 --- a/web/app/helpers/latency_helper.rb +++ b/web/app/helpers/latency_helper.rb @@ -86,7 +86,6 @@ module LatencyHelper raise exception end - latency_data end end \ No newline at end of file diff --git a/web/app/views/api_search/filter.rabl b/web/app/views/api_search/filter.rabl index c2c942a6e..ae7bf7eea 100644 --- a/web/app/views/api_search/filter.rabl +++ b/web/app/views/api_search/filter.rabl @@ -25,7 +25,7 @@ node :is_blank_filter do |foo| end child(:results => :musicians) { - attributes :id, :first_name, :last_name, :name, :city, :state, :country, :online, :musician, :photo_url, :biography, :regionname, :score, :full_score, :is_friend, :is_following, :pending_friend_request + attributes :id, :first_name, :last_name, :name, :city, :state, :country, :online, :musician, :photo_url, :biography, :regionname, :score, :full_score, :is_friend, :is_following, :pending_friend_request, :last_active_timestamp # node :is_friend do |musician| # @search.is_friend?(musician) @@ -82,11 +82,11 @@ child(:results => :musicians) { end if @latency_data end - node :last_active_timestamp do |musician| - if musician.updated_at || musician.last_jam_updated_at - [musician.updated_at, musician.last_jam_updated_at].compact.max.to_i - end - end + # node :last_active_timestamp do |musician| + # if musician.updated_at || musician.last_jam_updated_at + # [musician.updated_at, musician.last_jam_updated_at].compact.max.to_i + # end + # end child :genres => :genres do attributes :genre_id, :description diff --git a/web/app/views/api_search/index.rabl b/web/app/views/api_search/index.rabl index aec758cb3..d0b4c12ce 100644 --- a/web/app/views/api_search/index.rabl +++ b/web/app/views/api_search/index.rabl @@ -24,7 +24,7 @@ if @search.is_a?(BaseSearch) end child(:results => :musicians) { - attributes :id, :first_name, :last_name, :name, :city, :state, :country, :online, :musician, :photo_url, :biography, :regionname, :score, :full_score + attributes :id, :first_name, :last_name, :name, :city, :state, :country, :online, :musician, :photo_url, :biography, :regionname, :score, :full_score, :last_active_timestamp node :is_friend do |musician| @search.is_friend?(musician) @@ -72,11 +72,11 @@ if @search.is_a?(BaseSearch) end if @latency_data end - node :last_active_timestamp do |musician| - if musician.updated_at || musician.last_jam_updated_at - [musician.updated_at, musician.last_jam_updated_at].compact.max.to_i - end - end + # node :last_active_timestamp do |musician| + # if musician.updated_at || musician.last_jam_updated_at + # [musician.updated_at, musician.last_jam_updated_at].compact.max.to_i + # end + # end child :genres => :genres do attributes :genre_id, :description diff --git a/web/app/views/api_users/profile_show.rabl b/web/app/views/api_users/profile_show.rabl index 4a9eedae5..c8cc36d0d 100644 --- a/web/app/views/api_users/profile_show.rabl +++ b/web/app/views/api_users/profile_show.rabl @@ -1,7 +1,7 @@ object @profile attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :follower_count, :following_count, :recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count, :age, :website, :skill_level, :concert_count, :studio_session_count, :virtual_band, :virtual_band_commitment, :traditional_band, :traditional_band_commitment, :traditional_band_touring, :paid_sessions, :paid_sessions_hourly_rate, -:paid_sessions_daily_rate, :free_sessions, :cowriting, :cowriting_purpose, :subscribe_email, :is_a_teacher, :is_a_student +:paid_sessions_daily_rate, :free_sessions, :cowriting, :cowriting_purpose, :subscribe_email, :is_a_teacher, :is_a_student, :last_active_timestamp child :online_presences => :online_presences do attributes :id, :service_type, :username @@ -32,11 +32,11 @@ child :musician_instruments => :instruments do attributes :description, :proficiency_level, :priority, :instrument_id end -node :last_active_timestamp do |user| - if user.updated_at || user.last_jam_updated_at - [user.updated_at, user.last_jam_updated_at].compact.max.to_i - end -end +# node :last_active_timestamp do |user| +# if user.updated_at || user.last_jam_updated_at +# [user.updated_at, user.last_jam_updated_at].compact.max.to_i +# end +# end node :created_at_timestamp do |user| user.created_at.to_i diff --git a/web/config/application.rb b/web/config/application.rb index b3361beea..e41a616d5 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -515,7 +515,7 @@ if defined?(Bundler) config.latency_data_host = "http://localhost:4001" config.latency_data_host_auth_code = "c2VydmVyOnBhc3N3b3Jk" config.manual_override_installer_ends_with = "JamKazam-1.0.3776.dmg" - config.spa_origin = "http://beta.jamkazam.local:3000" - + config.spa_origin = "http://beta.jamkazam.local:4000" + config.user_match_monitoring_email = "user_match_monitoring_email@jamkazam.com" end end diff --git a/web/config/environments/development.rb b/web/config/environments/development.rb index 0a787bc04..7ae4c4ef4 100644 --- a/web/config/environments/development.rb +++ b/web/config/environments/development.rb @@ -120,4 +120,7 @@ SampleApp::Application.configure do config.spa_origin = "http://beta.jamkazam.local:4000" config.session_cookie_domain = ".jamkazam.local" + + config.action_controller.asset_host = 'http://localhost:3000' + config.action_mailer.asset_host = config.action_controller.asset_host end diff --git a/web/config/routes.rb b/web/config/routes.rb index a61245f4c..13138cd80 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -145,6 +145,7 @@ Rails.application.routes.draw do get '/reset_password_complete' => 'users#reset_password_complete', :as => 'reset_password_complete' match '/unsubscribe/:user_token' => 'users#unsubscribe', via: [:get, :post] + match '/unsubscribe_user_match/:user_token' => 'users#unsubscribe_user_match', via: [:post] # email update get '/confirm_email' => 'users#finalize_update_email', :as => 'confirm_email' # NOTE: if you change this, you break outstanding email changes because links in user inboxes are broken diff --git a/web/config/scheduler.yml b/web/config/scheduler.yml index a3c6fa432..e3c09c2ae 100644 --- a/web/config/scheduler.yml +++ b/web/config/scheduler.yml @@ -65,6 +65,11 @@ NewMusicianEmailer: class: "JamRuby::NewMusicianEmailer" description: "Sends weekly emails of new users with good latency" +NewMusicianMatchEmailer: + cron: "0 2 * * 6" + class: "JamRuby::NewMusicianMatchEmailer" + description: "Sends weekly emails of new users with good latency - (User latency data from neo4j)" + MusicSessionScheduler: cron: "0 * * * *" class: "JamRuby::MusicSessionScheduler" diff --git a/web/spec/requests/musician_filter_api_spec.rb b/web/spec/requests/musician_filter_api_spec.rb index 702ac5f0c..c25b823ef 100644 --- a/web/spec/requests/musician_filter_api_spec.rb +++ b/web/spec/requests/musician_filter_api_spec.rb @@ -135,7 +135,6 @@ describe "Musician Filter API", type: :request do it "filter musicians when no latency option is selected" do post '/api/filter.json', { latency_good: false, latency_fair: false, latency_high: false } - expect(JSON.parse(response.body)["musicians"].size).to eq(8) expect(JSON.parse(response.body)["musicians"][0]["latency_data"]).not_to eq(nil) expect(JSON.parse(response.body)["musicians"][0]["latency_data"]["audio_latency"]).not_to eq(nil) @@ -223,7 +222,7 @@ describe "Musician Filter API", type: :request do expect(JSON.parse(response.body)["musicians"].size).to eq(2) end - it "filter musicians by days ago that they joined" do + fit "filter musicians by days ago that they joined" do post '/api/filter.json', { latency_good: true, latency_fair: true, latency_high: true, joined_within_days: 1 } expect(JSON.parse(response.body)["musicians"].size).to eq(2) end diff --git a/web/spec/requests/users_controller_spec.rb b/web/spec/requests/users_controller_spec.rb index c5f34f5e4..e8ea276a8 100644 --- a/web/spec/requests/users_controller_spec.rb +++ b/web/spec/requests/users_controller_spec.rb @@ -28,6 +28,20 @@ describe UsersController, :type => :api do user.subscribe_email.should eql false end + fit "unsubscribe_user_match" do + user.subscribe_email_for_user_match.should eql false #default value is false + + user.subscribe_email_for_user_match = true #we make it true here for this test + user.save! + + get '/unsubscribe_user_match/' + user.unsubscribe_token + + expect(last_response).to have_http_status(200) + + user.reload + user.subscribe_email_for_user_match.should eql false + end + describe "track_origin" do describe "logged out" do