merging jam-ruby into ruby
This commit is contained in:
commit
6ea4bae05c
|
|
@ -0,0 +1,22 @@
|
|||
*.gem
|
||||
*.rbc
|
||||
.bundle
|
||||
.config
|
||||
.yardoc
|
||||
Gemfile.lock
|
||||
InstalledFiles
|
||||
_yardoc
|
||||
coverage
|
||||
doc/
|
||||
lib/bundler/man
|
||||
pkg
|
||||
rdoc
|
||||
spec/reports
|
||||
test/tmp
|
||||
test/version_tmp
|
||||
tmp
|
||||
|
||||
.idea
|
||||
*~
|
||||
*.swp
|
||||
*.iml
|
||||
|
|
@ -0,0 +1 @@
|
|||
up.connopts=dbname:jam_ruby
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
--color
|
||||
--format progress
|
||||
|
|
@ -0,0 +1 @@
|
|||
jamruby
|
||||
|
|
@ -0,0 +1 @@
|
|||
ruby-2.0.0-p247
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
#ruby=1.9.3
|
||||
source 'http://rubygems.org'
|
||||
unless ENV["LOCAL_DEV"] == "1"
|
||||
source 'https://jamjam:blueberryjam@int.jamkazam.com/gems/'
|
||||
end
|
||||
|
||||
# Look for $WORKSPACE, otherwise use "workspace" as dev path.
|
||||
workspace = ENV["WORKSPACE"] || "~/workspace"
|
||||
devenv = ENV["BUILD_NUMBER"].nil? # Jenkins sets a build number environment variable
|
||||
|
||||
gem 'pg', '0.15.1', :platform => [:mri, :mswin, :mingw]
|
||||
gem 'jdbc_postgres', :platform => [:jruby]
|
||||
|
||||
gem 'activerecord', '3.2.13'
|
||||
gem 'uuidtools', '2.1.2'
|
||||
gem 'bcrypt-ruby', '3.0.1'
|
||||
gem 'ruby-protocol-buffers', '1.2.2'
|
||||
gem 'eventmachine', '1.0.3'
|
||||
gem 'amqp', '1.0.2'
|
||||
gem 'will_paginate'
|
||||
gem 'actionmailer', '3.2.13'
|
||||
gem 'sendgrid'
|
||||
gem 'aws-sdk', '1.8.0'
|
||||
gem 'carrierwave'
|
||||
gem 'aasm', '3.0.16'
|
||||
gem 'devise', '>= 1.1.2'
|
||||
gem 'postgres-copy'
|
||||
|
||||
if devenv
|
||||
gem 'jam_db', :path=> "#{workspace}/jam-db/target/ruby_package"
|
||||
gem 'jampb', :path => "#{workspace}/jam-pb/target/ruby/jampb"
|
||||
else
|
||||
gem 'jam_db'
|
||||
gem 'jampb'
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem "factory_girl", '4.1.0'
|
||||
gem "rspec", "2.10.0"
|
||||
gem 'spork', '0.9.0'
|
||||
gem 'database_cleaner', '0.7.0'
|
||||
end
|
||||
|
||||
# Specify your gem's dependencies in jam_ruby.gemspec
|
||||
gemspec
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2012 Seth Call
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# JamRuby
|
||||
|
||||
## Environment
|
||||
Create development database 'jam_ruby'
|
||||
`createdb jam_ruby`
|
||||
|
||||
Once you've created your database, migrate it:
|
||||
`bundle exec jam_ruby up`
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
#!/usr/bin/env rake
|
||||
require "bundler/gem_tasks"
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
# Ensure the cron is using the proper version of ruby, and simply run this with:
|
||||
# ruby mix_cron.rb
|
||||
#
|
||||
|
||||
require 'faraday'
|
||||
require 'json'
|
||||
require 'tempfile'
|
||||
|
||||
MIXER_EXECUTABLE = "/usr/local/bin/audiomixerapp"
|
||||
S3CMD = "s3cmd"
|
||||
|
||||
# FIXME: This should probably come from an environments file or something
|
||||
BASE_URL = "http://www.jamkazam.com"
|
||||
|
||||
# This must be present on requests from the cron to prevent hackers from
|
||||
# hitting these routes.
|
||||
CRON_TOKEN = "2kkl39sjjf3ijdsflje2923j"
|
||||
|
||||
AUDIOMIXER_LOG_FILE = "/var/log/audiomixer"
|
||||
|
||||
MIX_CRON_WATCH_FILE = "/var/run/mix_cron"
|
||||
|
||||
|
||||
# Don't do anything if the cron is arleady running. There's a theoretical race
|
||||
# condition here, but it should never actually be a problem because the cron should
|
||||
# only run every minute or two.
|
||||
if File.exist?(MIX_CRON_WATCH_FILE)
|
||||
psret = `ps axuw | grep mix_cron.rb | grep ruby`
|
||||
unless psret.empty?
|
||||
puts "Cron still running"
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
`touch #{MIX_CRON_WATCH_FILE}`
|
||||
|
||||
|
||||
# Get the next manifest to mix
|
||||
response = Faraday.get "#{BASE_URL}/mixes/next", :token => CRON_TOKEN
|
||||
if response.status > 299
|
||||
puts "Error response getting next mix: #{response.status}, #{response.body}"
|
||||
do_exit
|
||||
end
|
||||
|
||||
# This just means no mixes available.
|
||||
if response.status == 204
|
||||
do_exit
|
||||
end
|
||||
|
||||
|
||||
if response.status != 200
|
||||
puts "Unexpected response received: #{response.status}, #{response.body}"
|
||||
do_exit
|
||||
end
|
||||
|
||||
json = JSON.parse(response.body)
|
||||
# This needs to download all the vorbis files, mix and then upload
|
||||
# the finished one, and tell the server about that.
|
||||
json['manifest']['files'].map! do |file|
|
||||
file['filename'] = Dir::Tmpname.make_tmpname ['/tmp/', '.ogg'], nil
|
||||
file_response = Faraday.get file.url
|
||||
if file_response.status != 200
|
||||
puts "Error downloading url: #{file.url}"
|
||||
do_exit
|
||||
end
|
||||
File.open(file['filename'], 'wb') { |fp| fp.write(file_response.body) }
|
||||
end
|
||||
|
||||
output_filename = "/tmp/mixout-#{json['id']}.ogg"
|
||||
IO.popen("#{MIXER_EXECUTABLE} #{output_filename} vorbis >>& #{AUDIOMIXER_LOG_FILE}", "w") do |f|
|
||||
f.puts JSON.generate(json)
|
||||
f.close
|
||||
end
|
||||
|
||||
# First maybe make sure the length is reasonable or something? I bet sox can check that (duration i mean).
|
||||
|
||||
# FIXME?: Need to check that the put succeeded before carrying on. Probably can use the exit code or some such.
|
||||
# Or maybe just do an ls to sanity check it.
|
||||
`#{S3CMD} -P put #{output_filename} #{json['destination']}`
|
||||
|
||||
finish_response = Faraday.put "#{BASE_URL}/mixes/finish", :token => CRON_TOKEN, :id => json['id']
|
||||
if finish_response.status != 204
|
||||
puts "Error calling finish on server for mix_id #{json['id']}: #{finish_response.status}, #{finish_response.body}"
|
||||
do_exit
|
||||
end
|
||||
|
||||
puts "Mix complete and uploaded to: #{json['destination']}"
|
||||
do_exit
|
||||
|
||||
|
||||
|
||||
|
||||
def do_exit
|
||||
`rm -f #{MIX_CRON_WATCH_FILE}`
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "updating dependencies"
|
||||
bundle install --path vendor/bundle --local
|
||||
bundle update
|
||||
|
||||
echo "running rspec tests"
|
||||
bundle exec rspec
|
||||
|
||||
if [ "$?" = "0" ]; then
|
||||
echo "tests completed"
|
||||
else
|
||||
echo "tests failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "build complete"
|
||||
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
development:
|
||||
access_key_id: AKIAIFFBNBRQG5YQ5WHA
|
||||
secret_access_key: XLq2mpJHNyA0bN7GBSdYyF/pWjfzGkDx92b1C+Wv
|
||||
|
||||
test:
|
||||
access_key_id: AKIAIFFBNBRQG5YQ5WHA
|
||||
secret_access_key: XLq2mpJHNyA0bN7GBSdYyF/pWjfzGkDx92b1C+Wv
|
||||
|
||||
production:
|
||||
access_key_id: AKIAIFFBNBRQG5YQ5WHA
|
||||
secret_access_key: XLq2mpJHNyA0bN7GBSdYyF/pWjfzGkDx92b1C+Wv
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
test:
|
||||
adapter: postgresql
|
||||
database: jam_ruby_test
|
||||
host: localhost
|
||||
pool: 3
|
||||
username: postgres
|
||||
password: postgres
|
||||
timeout: 2000
|
||||
encoding: unicode
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
# Note: I got this from here: https://github.com/intridea/profanity_filter/blob/master/config/dictionary.yml
|
||||
# I doubt that this list can be copyrighted
|
||||
# the filter currently checks for words that are 3 or more characters.
|
||||
---
|
||||
ass: "*ss"
|
||||
asses: "*ss*s"
|
||||
asshole: "*ssh*l*"
|
||||
assholes: "*ssh*l*s"
|
||||
bastard: b*st*rd
|
||||
beastial: b**st**l
|
||||
beastiality: b**st**l*ty
|
||||
beastility: b**st*l*ty
|
||||
bestial: b*st**l
|
||||
bestiality: b*st**l*ty
|
||||
bitch: b*tch
|
||||
bitcher: b*tch*r
|
||||
bitchers: b*tch*rs
|
||||
bitches: b*tch*s
|
||||
bitchin: b*tch*n
|
||||
bitching: b*tch*ng
|
||||
blowjob: bl*wj*b
|
||||
blowjobs: bl*wj*bs
|
||||
bullshit: b*llsh*t
|
||||
clit: cl*t
|
||||
cock: c*ck
|
||||
cocks: c*cks
|
||||
cocksuck: c*cks*ck
|
||||
cocksucked: c*cks*ck*d
|
||||
cocksucker: c*cks*ck*r
|
||||
cocksucking: c*cks*ck*ng
|
||||
cocksucks: c*cks*cks
|
||||
cum: c*m
|
||||
cummer: c*mm*r
|
||||
cumming: c*mm*ng
|
||||
cums: c*ms
|
||||
cumshot: c*msh*t
|
||||
cunillingus: c*n*ll*ng*s
|
||||
cunnilingus: c*nn*l*ng*s
|
||||
cunt: c*nt
|
||||
cuntlick: c*ntl*ck
|
||||
cuntlicker: c*ntl*ck*r
|
||||
cuntlicking: c*ntl*ck*ng
|
||||
cunts: c*nts
|
||||
cyberfuc: cyb*rf*c
|
||||
cyberfuck: cyb*rf*ck
|
||||
cyberfucked: cyb*rf*ck*d
|
||||
cyberfucker: cyb*rf*ck*r
|
||||
cyberfuckers: cyb*rf*ck*rs
|
||||
cyberfucking: cyb*rf*ck*ng
|
||||
damn: d*mn
|
||||
dildo: d*ld*
|
||||
dildos: d*ld*s
|
||||
dick: d*ck
|
||||
dink: d*nk
|
||||
dinks: d*nks
|
||||
ejaculate: "*j*c*l*t*"
|
||||
ejaculated: "*j*c*l*t*d"
|
||||
ejaculates: "*j*c*l*t*s"
|
||||
ejaculating: "*j*c*l*t*ng"
|
||||
ejaculatings: "*j*c*l*t*ngs"
|
||||
ejaculation: "*j*c*l*t**n"
|
||||
fag: f*g
|
||||
fagging: f*gg*ng
|
||||
faggot: f*gg*t
|
||||
faggs: f*ggs
|
||||
fagot: f*g*t
|
||||
fagots: f*g*ts
|
||||
fags: f*gs
|
||||
fart: f*rt
|
||||
farted: f*rt*d
|
||||
farting: f*rt*ng
|
||||
fartings: f*rt*ngs
|
||||
farts: f*rts
|
||||
farty: f*rty
|
||||
felatio: f*l*t**
|
||||
fellatio: f*ll*t**
|
||||
fingerfuck: f*ng*rf*ck
|
||||
fingerfucked: f*ng*rf*ck*d
|
||||
fingerfucker: f*ng*rf*ck*r
|
||||
fingerfuckers: f*ng*rf*ck*rs
|
||||
fingerfucking: f*ng*rf*ck*ng
|
||||
fingerfucks: f*ng*rf*cks
|
||||
fistfuck: f*stf*ck
|
||||
fistfucked: f*stf*ck*d
|
||||
fistfucker: f*stf*ck*r
|
||||
fistfuckers: f*stf*ck*rs
|
||||
fistfucking: f*stf*ck*ng
|
||||
fistfuckings: f*stf*ck*ngs
|
||||
fistfucks: f*stf*cks
|
||||
fuck: f*ck
|
||||
fucked: f*ck*d
|
||||
fucker: f*ck*r
|
||||
fuckers: f*ck*rs
|
||||
fuckin: f*ck*n
|
||||
fucking: f*ck*ng
|
||||
fuckings: f*ck*ngs
|
||||
fuckme: f*ckm*
|
||||
fucks: f*cks
|
||||
fuk: f*k
|
||||
fuks: f*ks
|
||||
gangbang: g*ngb*ng
|
||||
gangbanged: g*ngb*ng*d
|
||||
gangbangs: g*ngb*ngs
|
||||
gaysex: g*ys*x
|
||||
goddamn: g*dd*mn
|
||||
hardcoresex: h*rdc*r*s*x
|
||||
hell: h*ll
|
||||
horniest: h*rn**st
|
||||
horny: h*rny
|
||||
hotsex: h*ts*x
|
||||
jism: j*sm
|
||||
jiz: j*z
|
||||
jizm: j*zm
|
||||
kock: k*ck
|
||||
kondum: k*nd*m
|
||||
kondums: k*nd*ms
|
||||
kum: k*m
|
||||
kumer: k*mm*r
|
||||
kummer: k*mm*r
|
||||
kumming: k*mm*ng
|
||||
kums: k*ms
|
||||
kunilingus: k*n*l*ng*s
|
||||
lust: l*st
|
||||
lusting: l*st*ng
|
||||
mothafuck: m*th*f*ck
|
||||
mothafucka: m*th*f*ck*
|
||||
mothafuckas: m*th*f*ck*s
|
||||
mothafuckaz: m*th*f*ck*z
|
||||
mothafucked: m*th*f*ck*d
|
||||
mothafucker: m*th*f*ck*r
|
||||
mothafuckers: m*th*f*ck*rs
|
||||
mothafuckin: m*th*f*ck*n
|
||||
mothafucking: m*th*f*ck*ng
|
||||
mothafuckings: m*th*f*ck*ngs
|
||||
mothafucks: m*th*f*cks
|
||||
motherfuck: m*th*rf*ck
|
||||
motherfucked: m*th*rf*ck*d
|
||||
motherfucker: m*th*rf*ck*r
|
||||
motherfuckers: m*th*rf*ck*rs
|
||||
motherfuckin: m*th*rf*ck*n
|
||||
motherfucking: m*th*rf*ck*ng
|
||||
motherfuckings: m*th*rf*ck*ngs
|
||||
motherfucks: m*th*rf*cks
|
||||
niger: n*gg*r
|
||||
nigger: n*gg*r
|
||||
niggers: n*gg*rs
|
||||
orgasim: "*rg*s*m"
|
||||
orgasims: "*rg*s*ms"
|
||||
orgasm: "*rg*sm"
|
||||
orgasms: "*rg*sms"
|
||||
phonesex: ph*n*s*x
|
||||
phuk: ph*k
|
||||
phuked: ph*k*d
|
||||
phuking: ph*k*ng
|
||||
phukked: ph*kk*d
|
||||
phukking: ph*kk*ng
|
||||
phuks: ph*ks
|
||||
phuq: ph*q
|
||||
pis: p*ss
|
||||
piss: p*ss
|
||||
pisser: p*ss*r
|
||||
pissed: p*ss*d
|
||||
pisser: p*ss*r
|
||||
pissers: p*ss*rs
|
||||
pises: p*ss*s
|
||||
pisses: p*ss*s
|
||||
pisin: p*ss*n
|
||||
pissin: p*ss*n
|
||||
pising: p*ss*ng
|
||||
pissing: p*ss*ng
|
||||
pisof: p*ss*ff
|
||||
pissoff: p*ss*ff
|
||||
porn: p*rn
|
||||
porno: p*rn*
|
||||
pornography: p*rn*gr*phy
|
||||
pornos: p*rn*s
|
||||
prick: pr*ck
|
||||
pricks: pr*cks
|
||||
pussies: p*ss**s
|
||||
pusies: p*ss**s
|
||||
pussy: p*ssy
|
||||
pusy: p*ssy
|
||||
pussys: p*ssys
|
||||
pusys: p*ssys
|
||||
shit: sh*t
|
||||
shited: sh*t*d
|
||||
shitfull: sh*tf*ll
|
||||
shiting: sh*t*ng
|
||||
shitings: sh*t*ngs
|
||||
shits: sh*ts
|
||||
shitted: sh*tt*d
|
||||
shitter: sh*tt*r
|
||||
shitters: sh*tt*rs
|
||||
shitting: sh*tt*ng
|
||||
shittings: sh*tt*ngs
|
||||
shitty: sh*tty
|
||||
shity: sh*tty
|
||||
slut: sl*t
|
||||
sluts: sl*ts
|
||||
smut: sm*t
|
||||
spunk: sp*nk
|
||||
twat: tw*t
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
require File.expand_path('../lib/jam_ruby/version', __FILE__)
|
||||
|
||||
Gem::Specification.new do |gem|
|
||||
gem.authors = ["Seth Call"]
|
||||
gem.email = ["seth@jamkazam.com"]
|
||||
gem.description = %q{Common library for JamKazam Ruby code}
|
||||
gem.summary = %q{Common library for JamKazam Ruby code}
|
||||
gem.homepage = ""
|
||||
|
||||
gem.files = `git ls-files`.split($\)
|
||||
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
||||
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
||||
gem.name = "jam_ruby"
|
||||
gem.require_paths = ["lib"]
|
||||
gem.version = JamRuby::VERSION
|
||||
end
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#!/bin/bash
|
||||
|
||||
GEM_SERVER=http://localhost:9000/gems
|
||||
|
||||
echo "starting build..."
|
||||
./build
|
||||
|
||||
if [ "$?" = "0" ]; then
|
||||
echo "build succeeded"
|
||||
|
||||
# generate gem version based on jenkins build number
|
||||
if [ -z $BUILD_NUMBER ]; then
|
||||
BUILD_NUMBER="1"
|
||||
fi
|
||||
VERSION="0.0.${BUILD_NUMBER}"
|
||||
echo "packaging gem jam_ruby-$VERSION"
|
||||
cat > lib/jam_ruby/version.rb << EOF
|
||||
module JamRuby
|
||||
VERSION = "$VERSION"
|
||||
end
|
||||
EOF
|
||||
|
||||
gem build jam_ruby.gemspec
|
||||
|
||||
GEMNAME="jam_ruby-${VERSION}.gem"
|
||||
|
||||
echo "publishing gem"
|
||||
curl -f -T $GEMNAME $GEM_SERVER/$GEMNAME
|
||||
|
||||
if [ "$?" != "0" ]; then
|
||||
echo "publish failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "done publishing gems"
|
||||
else
|
||||
echo "build failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
require "pg"
|
||||
require "active_record"
|
||||
require "carrierwave"
|
||||
require "carrierwave/orm/activerecord"
|
||||
require "jampb"
|
||||
require "uuidtools"
|
||||
require "logging"
|
||||
require "will_paginate"
|
||||
require "will_paginate/active_record"
|
||||
require "action_mailer"
|
||||
require "devise"
|
||||
require "sendgrid"
|
||||
require 'postgres-copy'
|
||||
require "jam_ruby/constants/limits"
|
||||
require "jam_ruby/constants/notification_types"
|
||||
require "jam_ruby/constants/validation_messages"
|
||||
require "jam_ruby/errors/permission_error"
|
||||
require "jam_ruby/errors/state_error"
|
||||
require "jam_ruby/errors/jam_argument_error"
|
||||
require "jam_ruby/mq_router"
|
||||
require "jam_ruby/base_manager"
|
||||
require "jam_ruby/connection_manager"
|
||||
require "jam_ruby/version"
|
||||
require "jam_ruby/environment"
|
||||
require "jam_ruby/init"
|
||||
require "jam_ruby/app/mailers/user_mailer"
|
||||
require "jam_ruby/app/mailers/invited_user_mailer"
|
||||
require "jam_ruby/app/mailers/corp_mailer"
|
||||
require "jam_ruby/app/uploaders/artifact_uploader"
|
||||
require "jam_ruby/app/uploaders/perf_data_uploader"
|
||||
require "jam_ruby/lib/desk_multipass"
|
||||
require "jam_ruby/lib/s3_util"
|
||||
require "jam_ruby/lib/s3_manager"
|
||||
require "jam_ruby/lib/profanity"
|
||||
require "jam_ruby/amqp/amqp_connection_manager"
|
||||
require "jam_ruby/message_factory"
|
||||
require "jam_ruby/models/feedback"
|
||||
require "jam_ruby/models/feedback_observer"
|
||||
require "jam_ruby/models/max_mind_geo"
|
||||
require "jam_ruby/models/max_mind_isp"
|
||||
require "jam_ruby/models/genre"
|
||||
require "jam_ruby/models/user"
|
||||
require "jam_ruby/models/user_observer"
|
||||
require "jam_ruby/models/user_authorization"
|
||||
require "jam_ruby/models/join_request"
|
||||
require "jam_ruby/models/band"
|
||||
require "jam_ruby/models/invited_user"
|
||||
require "jam_ruby/models/invited_user_observer"
|
||||
require "jam_ruby/models/artifact_update"
|
||||
require "jam_ruby/models/band_invitation"
|
||||
require "jam_ruby/models/band_liker"
|
||||
require "jam_ruby/models/band_follower"
|
||||
require "jam_ruby/models/band_following"
|
||||
require "jam_ruby/models/band_musician"
|
||||
require "jam_ruby/models/connection"
|
||||
require "jam_ruby/models/friendship"
|
||||
require "jam_ruby/models/music_session"
|
||||
require "jam_ruby/models/music_session_history"
|
||||
require "jam_ruby/models/music_session_user_history"
|
||||
require "jam_ruby/models/music_session_perf_data"
|
||||
require "jam_ruby/models/invitation"
|
||||
require "jam_ruby/models/fan_invitation"
|
||||
require "jam_ruby/models/friend_request"
|
||||
require "jam_ruby/models/instrument"
|
||||
require "jam_ruby/models/musician_instrument"
|
||||
require "jam_ruby/models/notification"
|
||||
require "jam_ruby/models/track"
|
||||
require "jam_ruby/models/user_liker"
|
||||
require "jam_ruby/models/user_like"
|
||||
require "jam_ruby/models/user_follower"
|
||||
require "jam_ruby/models/user_following"
|
||||
require "jam_ruby/models/search"
|
||||
require "jam_ruby/models/recording"
|
||||
require "jam_ruby/models/recorded_track"
|
||||
require "jam_ruby/models/mix"
|
||||
require "jam_ruby/models/claimed_recording"
|
||||
require "jam_ruby/models/crash_dump"
|
||||
|
||||
include Jampb
|
||||
|
||||
module JamRuby
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
module JamRuby
|
||||
# The purpose of this class is to handle reconnect logic and 'recover' logic (which means automatically resubscribe to topics/queues).
|
||||
# It's 'leaky' in that it will give you a AMQP::Channel to do these subscriptions yourself in the block you pass to connect.
|
||||
# Use the *connected* property to check if the connection is currently up.
|
||||
class AmqpConnectionManager
|
||||
|
||||
attr_accessor :should_reconnect, :reconnect_interval, :connection, :connect, :connect_options, :connect_block, :channel,
|
||||
:connected
|
||||
|
||||
def initialize(should_reconnect, reconnect_interval, connect_options = {})
|
||||
@should_reconnect = should_reconnect
|
||||
@reconnect_interval = reconnect_interval
|
||||
@connect_options = connect_options
|
||||
@connected = false
|
||||
@log = Logging.logger[self]
|
||||
end
|
||||
|
||||
# the block you pass in will be passed a channel upon successful connect. You need
|
||||
#
|
||||
def connect(&block)
|
||||
@connect = true # indicate that we should be connected
|
||||
@connect_block = block
|
||||
|
||||
try_connect
|
||||
end
|
||||
|
||||
def try_connect
|
||||
@connection = AMQP.connect(@connect_options, &method(:successful_connect))
|
||||
@connection.on_tcp_connection_failure(&method(:on_tcp_connection_failure))
|
||||
@connection.on_tcp_connection_loss(&method(:on_tcp_connection_loss))
|
||||
@connection.on_recovery(&method(:on_recovery))
|
||||
@connection.on_error(&method(:on_error))
|
||||
end
|
||||
|
||||
def successful_connect(connection)
|
||||
@log.debug "connected to #{@connect_options}"
|
||||
@connected = true
|
||||
|
||||
@channel = AMQP::Channel.new(connection)
|
||||
@channel.auto_recovery = true
|
||||
|
||||
unless @connect_block.nil?
|
||||
@connect_block.call(@channel)
|
||||
end
|
||||
end
|
||||
|
||||
def on_tcp_connection_failure(settings)
|
||||
@connected = false
|
||||
|
||||
if @connect && @should_reconnect
|
||||
@log.warn "[network failure] Trying to connect in 4 seconds to #{@connect_options}"
|
||||
EventMachine.add_timer(@reconnect_interval, &method(:try_connect))
|
||||
end
|
||||
end
|
||||
|
||||
def on_tcp_connection_loss(conn, settings)
|
||||
@connected = false
|
||||
if @connect && @should_reconnect
|
||||
@log.warn "[network failure] Trying to reconnect..."
|
||||
conn.reconnect(false, @reconnect_interval)
|
||||
end
|
||||
end
|
||||
|
||||
def on_recovery(conn, settings)
|
||||
@connected = true
|
||||
|
||||
@log.debug "reconnected #{conn} #{settings}"
|
||||
|
||||
#puts "#channel before #{@channel}"
|
||||
#puts "recovered channel: #{@channel.reuse}"
|
||||
end
|
||||
|
||||
def disconnect
|
||||
@connect = false # indicate that we should no longer be connected
|
||||
|
||||
unless @connection.nil?
|
||||
if @connection.connected?
|
||||
@connection.disconnect do
|
||||
@connected = false
|
||||
@log.debug "disconnected"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def on_error(connection, connection_close)
|
||||
@log.error "Handling a connection-level exception."
|
||||
|
||||
@log.error "AMQP class id : #{connection_close.class_id}"
|
||||
@log.error "AMQP method id: #{connection_close.method_id}"
|
||||
@log.error "Status code : #{connection_close.reply_code}"
|
||||
@log.error "Error message : #{connection_close.reply_text}"
|
||||
end
|
||||
|
||||
def connected?
|
||||
return @connected
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
module JamRuby
|
||||
# CorpMail must be configured to work
|
||||
# Some common configs occur in jam_ruby/init.rb
|
||||
# Environment specific configs occur in spec_helper.rb in jam-ruby and jam-web (to put it into test mode),
|
||||
# and in config/initializers/email.rb in rails to configure sendmail account settings
|
||||
# If UserMailer were to be used in another project, it would need to be configured there, as well.
|
||||
|
||||
# Templates for UserMailer can be found in jam_ruby/app/views/jam_ruby/user_mailer
|
||||
class CorpMailer < ActionMailer::Base
|
||||
include SendGrid
|
||||
|
||||
layout "user_mailer"
|
||||
|
||||
DEFAULT_SENDER = "noreply@jamkazam.com"
|
||||
|
||||
default :from => DEFAULT_SENDER
|
||||
|
||||
sendgrid_category :use_subject_lines
|
||||
#sendgrid_enable :opentrack, :clicktrack # this makes our emails creepy, imo (seth)
|
||||
sendgrid_unique_args :env => Environment.mode
|
||||
|
||||
def feedback(feedback)
|
||||
@email = feedback.email
|
||||
@body = feedback.body
|
||||
|
||||
sendgrid_category "Corporate"
|
||||
sendgrid_unique_args :type => "feedback"
|
||||
|
||||
mail(:to => "info@jamkazam.com", :subject => "Feedback received from #{@email} ") do |format|
|
||||
format.text
|
||||
format.html
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
module JamRuby
|
||||
# InvitedUserMailer must be configured to work
|
||||
# Some common configs occur in jam_ruby/init.rb
|
||||
# Environment specific configs occur in spec_helper.rb in jam-ruby and jam-web (to put it into test mode),
|
||||
# and in config/initializers/email.rb in rails to configure sendmail account settings
|
||||
# If InvitedUserMailer were to be used in another project, it would need to be configured there, as well.
|
||||
|
||||
# Templates for InvitedUserMailer can be found in jam_ruby/app/views/jam_ruby/user_mailer
|
||||
class InvitedUserMailer < ActionMailer::Base
|
||||
include SendGrid
|
||||
|
||||
DEFAULT_SENDER = "support@jamkazam.com"
|
||||
|
||||
default :from => DEFAULT_SENDER
|
||||
|
||||
sendgrid_category :use_subject_lines
|
||||
#sendgrid_enable :opentrack, :clicktrack # this makes our emails creepy, imo (seth)
|
||||
sendgrid_unique_args :env => Environment.mode
|
||||
|
||||
# sent in the case of a general 'service invitation', from no one person in particular
|
||||
def welcome_betauser(invited_user)
|
||||
|
||||
@signup_url = generate_signup_url(invited_user)
|
||||
@suppress_user_has_account_footer = true
|
||||
|
||||
sendgrid_category "Welcome"
|
||||
sendgrid_unique_args :type => "welcome_betauser"
|
||||
|
||||
mail(:to => invited_user.email, :subject => "Welcome to the JamKazam Beta release") do |format|
|
||||
format.text
|
||||
format.html { render :layout => "user_mailer" }
|
||||
end
|
||||
end
|
||||
|
||||
# used when sending an invitation from one user to another
|
||||
def friend_invitation(invited_user)
|
||||
@signup_url = generate_signup_url(invited_user)
|
||||
@sender = invited_user.sender
|
||||
@note = invited_user.note
|
||||
@suppress_user_has_account_footer = true
|
||||
sendgrid_category "Invitation"
|
||||
sendgrid_unique_args :type => "friend_invitation"
|
||||
|
||||
mail(:to => invited_user.email, :subject => "You've been invited to JamKazam by #{invited_user.sender.name}") do |format|
|
||||
format.text
|
||||
format.html { render :layout => "from_user_mailer" }
|
||||
end
|
||||
end
|
||||
|
||||
def generate_signup_url(invited_user)
|
||||
"http://www.jamkazam.com/signup?invitation_code=#{invited_user.invitation_code}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
module JamRuby
|
||||
# UserMailer must be configured to work
|
||||
# Some common configs occur in jam_ruby/init.rb
|
||||
# Environment specific configs occur in spec_helper.rb in jam-ruby and jam-web (to put it into test mode),
|
||||
# and in config/initializers/email.rb in rails to configure sendmail account settings
|
||||
# If UserMailer were to be used in another project, it would need to be configured there, as well.
|
||||
|
||||
# Templates for UserMailer can be found in jam_ruby/app/views/jam_ruby/user_mailer
|
||||
class UserMailer < ActionMailer::Base
|
||||
include SendGrid
|
||||
|
||||
layout "user_mailer"
|
||||
|
||||
DEFAULT_SENDER = "support@jamkazam.com"
|
||||
|
||||
default :from => DEFAULT_SENDER
|
||||
|
||||
sendgrid_category :use_subject_lines
|
||||
#sendgrid_enable :opentrack, :clicktrack # this makes our emails creepy, imo (seth)
|
||||
sendgrid_unique_args :env => Environment.mode
|
||||
|
||||
def welcome_message(user, signup_confirm_url)
|
||||
@user = user
|
||||
@signup_confirm_url = signup_confirm_url
|
||||
sendgrid_category "Welcome"
|
||||
sendgrid_unique_args :type => "welcome_message"
|
||||
|
||||
mail(:to => user.email, :subject => "Welcome to JamKazam, #{user.first_name} ") do |format|
|
||||
format.text
|
||||
format.html
|
||||
end
|
||||
end
|
||||
|
||||
def password_changed(user)
|
||||
@user = user
|
||||
sendgrid_unique_args :type => "password_changed"
|
||||
mail(:to => user.email, :subject => "JamKazam Password Changed") do |format|
|
||||
format.text
|
||||
format.html
|
||||
end
|
||||
end
|
||||
|
||||
def password_reset(user, password_reset_url)
|
||||
@user = user
|
||||
@password_reset_url = password_reset_url
|
||||
sendgrid_unique_args :type => "password_reset"
|
||||
mail(:to => user.email, :subject => "JamKazam Password Reset") do |format|
|
||||
format.text
|
||||
format.html
|
||||
end
|
||||
end
|
||||
|
||||
def updating_email(user)
|
||||
@user = user
|
||||
sendgrid_unique_args :type => "updating_email"
|
||||
mail(:to => user.update_email, :subject => "JamKazam Email Change Confirmation") do |format|
|
||||
format.text
|
||||
format.html
|
||||
end
|
||||
end
|
||||
|
||||
def updated_email(user)
|
||||
@user = user
|
||||
sendgrid_unique_args :type => "updated_email"
|
||||
mail(:to => user.email, :subject => "JamKazam Email Changed") do |format|
|
||||
format.text
|
||||
format.html
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
# encoding: utf-8
|
||||
|
||||
|
||||
class ArtifactUploader < CarrierWave::Uploader::Base
|
||||
# Include RMagick or MiniMagick support:
|
||||
# include CarrierWave::RMagick
|
||||
# include CarrierWave::MiniMagick
|
||||
|
||||
# Include the Sprockets helpers for Rails 3.1+ asset pipeline compatibility:
|
||||
# include Sprockets::Helpers::RailsHelper
|
||||
# include Sprockets::Helpers::IsolatedHelper
|
||||
|
||||
# Choose what kind of storage to use for this uploader:
|
||||
# storage :file
|
||||
# storage :fog
|
||||
|
||||
# Override the directory where uploaded files will be stored.
|
||||
# This is a sensible default for uploaders that are meant to be mounted:
|
||||
def store_dir
|
||||
"artifacts/#{model.product}/#{model.version}"
|
||||
end
|
||||
|
||||
def md5
|
||||
@md5 ||= ::Digest::MD5.file(current_path).hexdigest
|
||||
end
|
||||
|
||||
# Provide a default URL as a default if there hasn't been a file uploaded:
|
||||
# def default_url
|
||||
# # For Rails 3.1+ asset pipeline compatibility:
|
||||
# # asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
|
||||
#
|
||||
# "/images/fallback/" + [version_name, "default.png"].compact.join('_')
|
||||
# end
|
||||
|
||||
# Process files as they are uploaded:
|
||||
# process :scale => [200, 300]
|
||||
#
|
||||
# def scale(width, height)
|
||||
# # do something
|
||||
# end
|
||||
|
||||
# Create different versions of your uploaded files:
|
||||
# version :thumb do
|
||||
# process :scale => [50, 50]
|
||||
# end
|
||||
|
||||
# Add a white list of extensions which are allowed to be uploaded.
|
||||
# For images you might use something like this:
|
||||
def extension_white_list
|
||||
%w(exe msi dmg)
|
||||
end
|
||||
|
||||
# Override the filename of the uploaded files:
|
||||
# Avoid using model.id or version_name here, see uploader/store.rb for details.
|
||||
# def filename
|
||||
# "something.jpg" if original_filename
|
||||
# end
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
# encoding: utf-8
|
||||
|
||||
class PerfDataUploader < CarrierWave::Uploader::Base
|
||||
|
||||
# Include RMagick or MiniMagick support:
|
||||
# include CarrierWave::RMagick
|
||||
# include CarrierWave::MiniMagick
|
||||
|
||||
# Include the Sprockets helpers for Rails 3.1+ asset pipeline compatibility:
|
||||
# include Sprockets::Helpers::RailsHelper
|
||||
# include Sprockets::Helpers::IsolatedHelper
|
||||
|
||||
# Choose what kind of storage to use for this uploader:
|
||||
# storage :file
|
||||
# storage :fog
|
||||
|
||||
# Override the directory where uploaded files will be stored.
|
||||
# This is a sensible default for uploaders that are meant to be mounted:
|
||||
def store_dir
|
||||
"perf_data/#{model.id}/#{model.client_id}-#{model.created_at}"
|
||||
end
|
||||
|
||||
def md5
|
||||
@md5 ||= ::Digest::MD5.file(current_path).hexdigest
|
||||
end
|
||||
|
||||
# Provide a default URL as a default if there hasn't been a file uploaded:
|
||||
# def default_url
|
||||
# # For Rails 3.1+ asset pipeline compatibility:
|
||||
# # asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
|
||||
#
|
||||
# "/images/fallback/" + [version_name, "default.png"].compact.join('_')
|
||||
# end
|
||||
|
||||
# Process files as they are uploaded:
|
||||
# process :scale => [200, 300]
|
||||
#
|
||||
# def scale(width, height)
|
||||
# # do something
|
||||
# end
|
||||
|
||||
# Create different versions of your uploaded files:
|
||||
# version :thumb do
|
||||
# process :scale => [50, 50]
|
||||
# end
|
||||
|
||||
# Add a white list of extensions which are allowed to be uploaded.
|
||||
# For images you might use something like this:
|
||||
def extension_white_list
|
||||
%w(exe msi dmg)
|
||||
end
|
||||
|
||||
# Override the filename of the uploaded files:
|
||||
# Avoid using model.id or version_name here, see uploader/store.rb for details.
|
||||
# def filename
|
||||
# "something.jpg" if original_filename
|
||||
# end
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<html>
|
||||
<body>
|
||||
<h3>Feedback Received</h3>
|
||||
<h4>From <%= @email %>:</h4>
|
||||
<p><%= @body %></p>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<p>
|
||||
This email was received because someone left feedback at <a href="http://www.jamkazam.com/corp/contact">http://www.jamkazam.com/corp/contact</a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
Feedback Received
|
||||
|
||||
From <%= @email %>:
|
||||
|
||||
<%= @body %>
|
||||
|
||||
|
||||
This email was received because someone left feedback at http://www.jamkazam.com/corp/contact
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<% provide(:title, "You've been invited to JamKazam by #{@sender.name}!") %>
|
||||
<% provide(:photo_url, @sender.resolved_photo_url) %>
|
||||
|
||||
To signup, please go to the <a href="<%= @signup_url %>">create account</a> page.
|
||||
|
||||
<% content_for :note do %>
|
||||
<% if @note %>
|
||||
<%= @sender.name %> says: <%= @note %>
|
||||
<% else %>
|
||||
<%= @sender.name %> would like you to join JamKazam.
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
You've been invited to JamKazam by <%= @sender.name %>!
|
||||
<% unless @note.nil? %>
|
||||
<%= @sender.name %> says:
|
||||
<%= @note %>
|
||||
<% end %>
|
||||
|
||||
To signup, please go to the 'create account' page: <%= @signup_url %>.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<% provide(:title, 'Welcome to the JamKazam Beta!') %>
|
||||
|
||||
To signup, please go to the <a href="<%= @signup_url %>">create account</a> page.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Welcome to the JamKazam Beta!
|
||||
To signup, please go to the 'create account' page: <%= @signup_url %>.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<% provide(:title, 'Jamkazam Password Changed') %>
|
||||
|
||||
You just changed your password at Jamkazam.
|
||||
|
|
@ -0,0 +1 @@
|
|||
You just changed your password!
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<% provide(:title, 'Jamkazam Password Reset') %>
|
||||
|
||||
Visit this link so that you can change your Jamkazam password: <a href="<%= @password_reset_url %>">reset password</a>.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Visit this link so that you can change your Jamkazam password: <a href="<%= @password_reset_url %>">Reset Password</a>.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<% provide(:title, 'Jamkazam Email Confirmed') %>
|
||||
|
||||
<b><%= @user.email %></b> has been confirmed as your new email address.
|
||||
|
|
@ -0,0 +1 @@
|
|||
<%= @user.email %> has been confirmed as your new email address.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<% provide(:title, 'Please Confirm New Jamkazam Email') %>
|
||||
|
||||
Please click the following link to confirm your change in email: <a href="<%= @user.update_email_confirmation_url %>">confirm email</a>.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Please click the following link to confirm your change in email: <%= @user.update_email_confirmation_url %>.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<% provide(:title, 'Welcome to Jamkazam') %>
|
||||
|
||||
<p>Welcome to Jamkazam, <%= @user.first_name %>!</p>
|
||||
<p>To confirm this email address, please go to the <a href="<%= @signup_confirm_url %>">signup confirmation page.</a>.</p>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
Welcome to Jamkazam, <%= @user.first_name %>!
|
||||
|
||||
To confirm this email address, please go to the signup confirmation page at: <%= @signup_confirm_url %>.
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
Understanding Jamkazam Email Layouts
|
||||
====================================
|
||||
|
||||
We have two types of layouts;
|
||||
* a 'standard' email layout which is an email to a user (user_mailer.html.erb/text)
|
||||
* a 'from a user to another user' email layout (from_user_mailer.html.erb/text)
|
||||
|
||||
## user_mailer.html.erb
|
||||
To use the user_mailer.html.erb layout, you must provide a title section, as well as the body.
|
||||
Look at 'password_changed.html.erb' for an example.
|
||||
|
||||
## from_user_mailer.html.erb
|
||||
To use the from_user_mailer.html.erb layout, you must provide a title section, photo_url section (photo of the person who sent the email), and a note section (any personalized note that the sender may have said, or boilerplate)
|
||||
Look at 'friend_invitation.html.erb' for an example.
|
||||
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>JamKazam</title>
|
||||
|
||||
<style>
|
||||
p {
|
||||
margin-bottom:0px;
|
||||
line-height:140%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body bgcolor="#000000" style="margin-top:10px;font-family:Arial, Helvetica, sans-serif;">
|
||||
<table bgcolor="#262626" width="650" align="center" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td><img src="http://www.jamkazam.com/assets/email/header.png" width="650" height="183" alt="JamKazam"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table bgcolor="#262626" width="650" align="center" cellpadding="30" cellspacing="0">
|
||||
|
||||
<tr>
|
||||
<td align="left"><h1 style="font-size:22px;font-weight:normal;margin-top:0px"><font color="#F34E1C" face="Arial, Helvetica, sans-serif"><%= yield(:title) %></font></h1>
|
||||
<p><font size="3" color="#AAAAAA" face="Arial, Helvetica, sans-serif"><%= yield %></font></p>
|
||||
<br>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="58" align="left" valign="top"><p style="padding:2px;width:54px;height:54px;background-color:#ed3618;-webkit-border-radius:28px;-moz-border-radius:28px;border-radius:28px;margin-right:10px"><img src="<%= yield(:photo_url) %>" width="54" height="54" style="-webkit-border-radius:26px;-moz-border-radius:26px;border-radius:26px;"></p></td>
|
||||
<td><p><font size="3" color="#AAAAAA" face="Arial, Helvetica, sans-serif"><%= yield(:note) %></font></p>
|
||||
</td></tr></table>
|
||||
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
|
||||
<% unless @suppress_user_has_account_footer == true %>
|
||||
<tr>
|
||||
<td>
|
||||
<table bgcolor="#21474C" cellpadding="10" cellspacing="0">
|
||||
<tr>
|
||||
<td align="left">
|
||||
|
||||
<!-- CALL OUT BOX -->
|
||||
</font></p>
|
||||
<p style="margin-top:0px"><font size="2" color="#7FACBA" face="Arial, Helvetica, sans-serif">This email was sent to you because you have an account at <a href="http://www.jamkazam.com">Jamkazam</a>.
|
||||
</td></tr></table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
<table align="center" width="650" cellpadding="10" bgcolor="#156572" cellspacing="0">
|
||||
<tr>
|
||||
<td align="center"><font size="1" color="#ffffff" face="Arial, Helvetica, sans-serif">Copyright © <%= Time.now.year %> JamKazam, Inc. All rights reserved.</font>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<%= yield %>
|
||||
|
||||
|
||||
<% unless @suppress_user_has_account_footer == true %>
|
||||
This email was sent to you because you have an account at Jamkazam / http://www.jamkazam.com.
|
||||
<% end %>
|
||||
|
||||
Copyright <%= Time.now.year %> JamKazam, Inc. All rights reserved.
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>JamKazam</title>
|
||||
|
||||
<style>
|
||||
p {
|
||||
margin-bottom:0px;
|
||||
line-height:140%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body bgcolor="#000000" style="margin-top:10px;font-family:Arial, Helvetica, sans-serif;">
|
||||
<table bgcolor="#262626" width="650" align="center" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td><img src="http://www.jamkazam.com/assets/email/header.png" width="650" height="183" alt="JamKazam"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table bgcolor="#262626" width="650" align="center" cellpadding="30" cellspacing="0">
|
||||
|
||||
<tr>
|
||||
<td align="left"><h1 style="font-size:22px;font-weight:normal;margin-top:0px"><font color="#F34E1C" face="Arial, Helvetica, sans-serif"><%= yield(:title) %></font></h1>
|
||||
<p><font size="3" color="#AAAAAA" face="Arial, Helvetica, sans-serif"><%= yield %></font></p>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<% unless @suppress_user_has_account_footer == true %>
|
||||
<tr>
|
||||
<td>
|
||||
<table bgcolor="#21474C" cellpadding="10" cellspacing="0">
|
||||
<tr>
|
||||
<td align="left">
|
||||
|
||||
<!-- CALL OUT BOX -->
|
||||
</font></p>
|
||||
<p style="margin-top:0px"><font size="2" color="#7FACBA" face="Arial, Helvetica, sans-serif">This email was sent to you because you have an account at <a href="http://www.jamkazam.com">Jamkazam</a>.
|
||||
</td></tr></table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<% end %>
|
||||
</table>
|
||||
<table align="center" width="650" cellpadding="10" bgcolor="#156572" cellspacing="0">
|
||||
<tr>
|
||||
<td align="center"><font size="1" color="#ffffff" face="Arial, Helvetica, sans-serif">Copyright © <%= Time.now.year %> JamKazam, Inc. All rights reserved.</font>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<%= yield %>
|
||||
|
||||
|
||||
<% unless @suppress_user_has_account_footer == true %>
|
||||
This email was sent to you because you have an account at Jamkazam / http://www.jamkazam.com.
|
||||
<% end %>
|
||||
|
||||
Copyright <%= Time.now.year %> JamKazam, Inc. All rights reserved.
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
module JamRuby
|
||||
class BaseManager
|
||||
|
||||
attr_accessor :pg_conn
|
||||
|
||||
def initialize(options={})
|
||||
@log = Logging.logger[self]
|
||||
@pg_conn = options[:conn]
|
||||
|
||||
unless PG.threadsafe?
|
||||
raise Exception, "a non-threadsafe build of libpq is present."
|
||||
end
|
||||
end
|
||||
|
||||
# Creates a connection manager, and associates the connection created by active_record with ourselves
|
||||
def self.active_record_transaction
|
||||
|
||||
manager = self.new
|
||||
ActiveRecord::Base.connection_pool.with_connection do |connection|
|
||||
# create a transaction, and pass the current connection to ConnectionManager.
|
||||
# this lets the entire operation work with the same transaction,
|
||||
# across Rails ActiveRecord and the pg-gem based code in ConnectionManager.
|
||||
manager.pg_conn = connection.instance_variable_get("@connection")
|
||||
|
||||
connection.transaction do
|
||||
yield manager
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,371 @@
|
|||
module JamRuby
|
||||
# All writes should occur through the ConnectionManager for the connection table
|
||||
# Reads can occur freely elsewhere, though
|
||||
# Because connections are tied to the websocket-connection and we bookkeep them in the database purely
|
||||
# for 'SQL convenience', this is a obvious place we can go away from a database
|
||||
# as an optimization if we find it's too much db traffic created'
|
||||
# At a minimum, though, we could make connections an UNLOGGED table because if the database crashes,
|
||||
# all clients should reconnect and restablish their connection anyway
|
||||
#
|
||||
# All methods in here could also be refactored as stored procedures, if we stick with a database.
|
||||
# This may make sense in the short term if we are still managing connections in the database, but
|
||||
# we move to the node-js in the websocket gateway (because the websocket gateway needs to call some of these methods).
|
||||
# Or of course we could just port the relevant methods to node-js
|
||||
#
|
||||
# Also we don't send notifications from ConnectionManager;
|
||||
# we just return enough data so that a caller can make the determination if it needs to
|
||||
|
||||
class ConnectionManager < BaseManager
|
||||
|
||||
def initialize(options={})
|
||||
super(options)
|
||||
@log = Logging.logger[self]
|
||||
end
|
||||
|
||||
def update_staleness()
|
||||
#TODO
|
||||
end
|
||||
|
||||
##### TODO: refactored to notification.rb but left here for backwards compatibility w/ connection_manager_spec.rb
|
||||
def gather_friends(connection, user_id)
|
||||
friend_ids = []
|
||||
connection.exec("SELECT f1.friend_id as friend_id FROM friendships f1 WHERE f1.user_id = $1 AND f1.friend_id IN (SELECT f2.user_id FROM friendships f2 WHERE f2.friend_id = $1)", [user_id]) do |friend_results|
|
||||
friend_results.each do |friend_result|
|
||||
friend_ids.push(friend_result['friend_id'])
|
||||
end
|
||||
end
|
||||
return friend_ids
|
||||
end
|
||||
|
||||
# reclaim the existing connection,
|
||||
def reconnect(conn, reconnect_music_session_id)
|
||||
music_session_id = nil
|
||||
reconnected = false
|
||||
|
||||
# we will reconnect the same music_session that the connection was previously in,
|
||||
# if it matches the same value currently in the database for music_session_id
|
||||
music_session_id_expression = 'NULL'
|
||||
unless reconnect_music_session_id.nil?
|
||||
music_session_id_expression = "(CASE WHEN music_session_id='#{reconnect_music_session_id}' THEN music_session_id ELSE NULL END)"
|
||||
end
|
||||
|
||||
|
||||
sql =<<SQL
|
||||
UPDATE connections SET (aasm_state, updated_at, music_session_id) = ('#{Connection::CONNECT_STATE.to_s}', NOW(), #{music_session_id_expression})
|
||||
WHERE
|
||||
client_id = '#{conn.client_id}'
|
||||
RETURNING music_session_id
|
||||
SQL
|
||||
self.pg_conn.exec(sql) do |result|
|
||||
if result.cmd_tuples == 1
|
||||
music_session_id = result[0]['music_session_id']
|
||||
end
|
||||
end
|
||||
|
||||
# we tell the client they reconnected if they specified a reconnect music_session_id, and if that is now the
|
||||
# current value in the database
|
||||
reconnected = true if !reconnect_music_session_id.nil? && reconnect_music_session_id == music_session_id
|
||||
|
||||
return music_session_id, reconnected
|
||||
end
|
||||
|
||||
# returns the music_session_id, if any, associated with the client
|
||||
def flag_connection_stale_with_client_id(client_id)
|
||||
music_session_id = nil
|
||||
sql =<<SQL
|
||||
UPDATE connections SET aasm_state = '#{Connection::STALE_STATE.to_s}'
|
||||
WHERE
|
||||
client_id = '#{client_id}' AND
|
||||
aasm_state = '#{Connection::CONNECT_STATE.to_s}'
|
||||
RETURNING music_session_id
|
||||
SQL
|
||||
# @log.info("*** flag_connection_stale_with_client_id: client_id = #{client_id}; sql = #{sql}")
|
||||
self.pg_conn.exec(sql) do |result|
|
||||
|
||||
# if we did update a client to stale, retriee music_session_id
|
||||
if result.cmd_tuples == 1
|
||||
music_session_id = result[0]['music_session_id']
|
||||
end
|
||||
end
|
||||
|
||||
music_session_id
|
||||
end
|
||||
|
||||
# flag connections as stale
|
||||
def flag_stale_connections(max_seconds)
|
||||
ConnectionManager.active_record_transaction do |connection_manager|
|
||||
conn = connection_manager.pg_conn
|
||||
sql =<<SQL
|
||||
SELECT count(user_id) FROM connections
|
||||
WHERE
|
||||
updated_at < (NOW() - interval '#{max_seconds} second') AND
|
||||
aasm_state = '#{Connection::CONNECT_STATE.to_s}'
|
||||
SQL
|
||||
conn.exec(sql) do |result|
|
||||
count = result.getvalue(0, 0)
|
||||
# @log.info("flag_stale_connections: flagging #{count} stale connections")
|
||||
if 0 < count.to_i
|
||||
# @log.info("flag_stale_connections: flagging #{count} stale connections")
|
||||
sql =<<SQL
|
||||
UPDATE connections SET aasm_state = '#{Connection::STALE_STATE.to_s}'
|
||||
WHERE
|
||||
updated_at < (NOW() - interval '#{max_seconds} second') AND
|
||||
aasm_state = '#{Connection::CONNECT_STATE.to_s}'
|
||||
SQL
|
||||
conn.exec(sql)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# NOTE this is only used for testing purposes;
|
||||
# actual deletes will be processed in the websocket context which cleans up dependencies
|
||||
def expire_stale_connections(max_seconds)
|
||||
self.stale_connection_client_ids(max_seconds).each { |cid| self.delete_connection(cid) }
|
||||
end
|
||||
|
||||
# expiring connections in stale state, which deletes them
|
||||
def stale_connection_client_ids(max_seconds)
|
||||
client_ids = []
|
||||
ConnectionManager.active_record_transaction do |connection_manager|
|
||||
conn = connection_manager.pg_conn
|
||||
sql =<<SQL
|
||||
SELECT client_id, music_session_id, user_id FROM connections
|
||||
WHERE
|
||||
updated_at < (NOW() - interval '#{max_seconds} second') AND
|
||||
aasm_state = '#{Connection::STALE_STATE.to_s}'
|
||||
SQL
|
||||
conn.exec(sql) do |result|
|
||||
result.each { |row|
|
||||
client_id = row['client_id']
|
||||
music_session_id = row['music_session_id']
|
||||
user_id = row['user_id']
|
||||
|
||||
client_ids << client_id
|
||||
|
||||
}
|
||||
end
|
||||
end
|
||||
client_ids
|
||||
end
|
||||
|
||||
|
||||
# returns the number of connections that this user currently has across all clients
|
||||
# this number is used by notification logic elsewhere to know
|
||||
# 'oh the user joined for the 1st time, so send a friend update', or
|
||||
# 'don't bother because the user has connected somewhere else already'
|
||||
def create_connection(user_id, client_id, ip_address, &blk)
|
||||
count = 0
|
||||
ConnectionManager.active_record_transaction do |connection_manager|
|
||||
conn = connection_manager.pg_conn
|
||||
|
||||
lock_connections(conn)
|
||||
|
||||
conn.exec("INSERT INTO connections (user_id, client_id, ip_address, aasm_state) VALUES ($1, $2, $3, $4)",
|
||||
[user_id, client_id, ip_address, Connection::CONNECT_STATE.to_s]).clear
|
||||
|
||||
# we just created a new connection-if this is the first time the user has shown up, we need to send out a message to his friends
|
||||
conn.exec("SELECT count(user_id) FROM connections WHERE user_id = $1", [user_id]) do |result|
|
||||
count = result.getvalue(0, 0) .to_i
|
||||
blk.call(conn, count) unless blk.nil?
|
||||
end
|
||||
return count
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# once a connection is known gone (whether timeout or because a TCP connection is observed lost)
|
||||
# this code is responsible for all cleanup logic associated with a connection going away
|
||||
# returns how many connections are left for this user; this data is used by callers to know whether
|
||||
# to tell friends if the user is offline (count==0) or not (count > 0)
|
||||
# If a blk is passed in, on success, count is also passed back an the db connection, allowing for
|
||||
# notifications to go out within the table log. music_session_id is also passed, if the music_session still exists
|
||||
# and this connection was in a session
|
||||
def delete_connection(client_id, &blk)
|
||||
|
||||
ConnectionManager.active_record_transaction do |connection_manager|
|
||||
conn = connection_manager.pg_conn
|
||||
count = 0
|
||||
user_id = nil
|
||||
music_session_id = nil
|
||||
|
||||
lock_connections(conn)
|
||||
|
||||
previous_music_session_id = check_already_session(conn, client_id)
|
||||
|
||||
conn.exec("DELETE FROM connections WHERE client_id = $1 RETURNING user_id, music_session_id", [client_id]) do |result|
|
||||
|
||||
if result.cmd_tuples == 0
|
||||
# the client is already gone from the database... do nothing but log error
|
||||
@log.warn("unable to delete client #{client_id}")
|
||||
return
|
||||
elsif result.cmd_tuples == 1
|
||||
user_id = result[0]['user_id']
|
||||
music_session_id = result[0]['music_session_id']
|
||||
|
||||
else
|
||||
raise Exception, 'uniqueness constraint has been lost on client_id'
|
||||
end
|
||||
end
|
||||
|
||||
session_checks(conn, previous_music_session_id, user_id)
|
||||
|
||||
# since we did delete a row, check and see if any more connections for that user exist
|
||||
# if we are down to zero, send out user gone message
|
||||
conn.exec("SELECT count(user_id) FROM connections where user_id = $1", [user_id]) do |result|
|
||||
count = result.getvalue(0, 0).to_i
|
||||
end
|
||||
|
||||
# same for session-if we are down to the last participant, delete the session
|
||||
unless music_session_id.nil?
|
||||
result = conn.exec("DELETE FROM music_sessions WHERE id = $1 AND 0 = (select count(music_session_id) FROM connections where music_session_id = $1)", [music_session_id])
|
||||
if result.cmd_tuples == 1
|
||||
music_session_id = nil
|
||||
end
|
||||
end
|
||||
|
||||
blk.call(conn, count, music_session_id, user_id) unless blk.nil?
|
||||
return count
|
||||
end
|
||||
end
|
||||
|
||||
def check_already_session(conn, client_id)
|
||||
conn.exec("SELECT music_session_id FROM connections WHERE client_id = $1", [client_id]) do |result|
|
||||
if result.num_tuples == 1
|
||||
previous_music_session_id = result.getvalue(0, 0)
|
||||
return previous_music_session_id
|
||||
elsif result.num_tuples == 0
|
||||
# there is no connection found matching this criteria; we are done.
|
||||
@log.debug("when checking for existing session, no connection found with client=#{client_id}")
|
||||
return nil
|
||||
else
|
||||
@log.error("connection table data integrity violation; multiple rows found. client_id=#{client_id}")
|
||||
raise Exception, "connection table data integrity violation; multiple rows found. client_id=#{client_id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def session_checks(conn, previous_music_session_id, user_id)
|
||||
unless previous_music_session_id.nil?
|
||||
# TODO: send notification to friends that this user left this session?
|
||||
@log.debug("user #{user_id} left music_session #{previous_music_session_id}")
|
||||
|
||||
# destroy the music_session if it's empty
|
||||
num_participants = nil
|
||||
conn.exec("SELECT count(*) FROM connections WHERE music_session_id = $1",
|
||||
[previous_music_session_id]) do |result|
|
||||
num_participants = result.getvalue(0, 0).to_i
|
||||
end
|
||||
if num_participants == 0
|
||||
# delete the music_session
|
||||
conn.exec("DELETE from music_sessions WHERE id = $1",
|
||||
[previous_music_session_id]) do |result|
|
||||
if result.cmd_tuples == 1
|
||||
# music session deleted!
|
||||
@log.debug("deleted music session #{previous_music_session_id}")
|
||||
JamRuby::MusicSessionHistory.removed_music_session(user_id,
|
||||
previous_music_session_id)
|
||||
elsif 1 < result.cmd_tuples
|
||||
msg = "music_sessions table data integrity violation; multiple rows found with music_session_id=#{previous_music_session_id}"
|
||||
@log.error(msg)
|
||||
raise Exception, msg
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# if a blk is passed in, upon success, it will be called and you can issue notifications
|
||||
# within the connection table lock
|
||||
def join_music_session(user, client_id, music_session, as_musician, tracks, &blk)
|
||||
connection = nil
|
||||
user_id = user.id
|
||||
music_session_id = music_session.id
|
||||
|
||||
ConnectionManager.active_record_transaction do |connection_manager|
|
||||
db_conn = connection_manager.pg_conn
|
||||
|
||||
connection = Connection.find_by_client_id_and_user_id!(client_id, user_id)
|
||||
connection.music_session_id = music_session_id
|
||||
connection.as_musician = as_musician
|
||||
connection.joining_session = true
|
||||
associate_tracks(connection, tracks)
|
||||
connection.save
|
||||
|
||||
if connection.errors.any?
|
||||
raise ActiveRecord::Rollback
|
||||
else
|
||||
blk.call(db_conn, connection) unless blk.nil?
|
||||
MusicSessionUserHistory.save(music_session_id, user_id, client_id)
|
||||
end
|
||||
end
|
||||
|
||||
return connection
|
||||
end
|
||||
|
||||
# if a blk is passed in, upon success, it will be called and you can issue notifications
|
||||
# within the connection table lock
|
||||
def leave_music_session(user, connection, music_session, &blk)
|
||||
ConnectionManager.active_record_transaction do |connection_manager|
|
||||
|
||||
conn = connection_manager.pg_conn
|
||||
|
||||
lock_connections(conn)
|
||||
|
||||
music_session_id = music_session.id
|
||||
user_id = user.id
|
||||
client_id = connection.client_id
|
||||
|
||||
previous_music_session_id = check_already_session(conn, client_id)
|
||||
|
||||
if previous_music_session_id == nil
|
||||
@log.debug "the client is not in a session. user=#{user_id}, client=#{client_id}, music_session=#{music_session_id}"
|
||||
raise StateError, "not in session"
|
||||
elsif previous_music_session_id != music_session_id
|
||||
@log.debug "the client is in a different session. user=#{user_id}, client=#{client_id}, music_session=#{music_session_id}"
|
||||
raise StateError, "in a session different than that specified"
|
||||
end
|
||||
|
||||
# can throw exception if the session is deleted just before this
|
||||
conn.exec("UPDATE connections SET music_session_id = NULL, as_musician = NULL WHERE client_id = $1 AND user_id =$2", [client_id, user_id]) do |result|
|
||||
if result.cmd_tuples == 1
|
||||
@log.debug("disassociated music_session with connection for client_id=#{client_id}, user_id=#{user_id}")
|
||||
|
||||
JamRuby::MusicSessionUserHistory.removed_music_session(user_id, music_session_id)
|
||||
session_checks(conn, previous_music_session_id, user_id)
|
||||
blk.call() unless blk.nil?
|
||||
|
||||
elsif result.cmd_tuples == 0
|
||||
@log.debug "leave_music_session no connection found with client_id=#{client_id}"
|
||||
raise ActiveRecord::RecordNotFound
|
||||
else
|
||||
@log.error("database failure or logic error; this path should be impossible if the table is locked (leave_music_session)")
|
||||
raise Exception, "locked table changed state"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def lock_connections(conn)
|
||||
conn.exec("LOCK connections IN EXCLUSIVE MODE").clear
|
||||
end
|
||||
|
||||
def associate_tracks(connection, tracks)
|
||||
@log.debug "Tracks:"
|
||||
@log.debug tracks
|
||||
connection.tracks.clear()
|
||||
|
||||
unless tracks.nil?
|
||||
tracks.each do |track|
|
||||
instrument = Instrument.find(track["instrument_id"])
|
||||
t = Track.new
|
||||
t.instrument = instrument
|
||||
t.connection = connection
|
||||
t.sound = track["sound"]
|
||||
t.save
|
||||
connection.tracks << t
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
module Limits
|
||||
|
||||
# band genres
|
||||
MIN_GENRES_PER_BAND = 1
|
||||
MAX_GENRES_PER_BAND = 1
|
||||
|
||||
# recording genres
|
||||
MIN_GENRES_PER_RECORDING = 1
|
||||
MAX_GENRES_PER_RECORDING = 1
|
||||
|
||||
# instruments
|
||||
MIN_INSTRUMENTS_PER_MUSICIAN = 1
|
||||
MAX_INSTRUMENTS_PER_MUSICIAN = 5
|
||||
|
||||
# users
|
||||
USERS_CAN_INVITE = false # in BETA release, only first level users can invite others
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
module NotificationTypes
|
||||
|
||||
# friend notifications
|
||||
FRIEND_UPDATE = "FRIEND_UPDATE"
|
||||
FRIEND_REQUEST = "FRIEND_REQUEST"
|
||||
FRIEND_REQUEST_ACCEPTED = "FRIEND_REQUEST_ACCEPTED"
|
||||
FRIEND_SESSION_JOIN = "FRIEND_SESSION_JOIN"
|
||||
|
||||
# session notifications
|
||||
SESSION_INVITATION = "SESSION_INVITATION"
|
||||
|
||||
# musician notifications
|
||||
MUSICIAN_SESSION_JOIN = "MUSICIAN_SESSION_JOIN"
|
||||
MUSICIAN_SESSION_DEPART = "MUSICIAN_SESSION_DEPART"
|
||||
|
||||
# recording notifications
|
||||
RECORDING_CREATED = "RECORDING_CREATED"
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
module ValidationMessages
|
||||
|
||||
# Note that these are not set up to be internationalizable
|
||||
|
||||
# general messages
|
||||
PERMISSION_VALIDATION_ERROR = "You do not have permissions to perform this action."
|
||||
USER_NOT_MUSICIAN_VALIDATION_ERROR = "You must be a Musician to perform this action."
|
||||
USER_NOT_BAND_MEMBER_VALIDATION_ERROR = "You must be a band member to perform this action."
|
||||
|
||||
# band invitations
|
||||
BAND_INVITATION_NOT_FOUND = "Band invitation not found."
|
||||
|
||||
# recordings
|
||||
RECORDING_NOT_FOUND = "Recording not found."
|
||||
|
||||
# tracks
|
||||
TRACK_NOT_FOUND = "Track not found."
|
||||
|
||||
# sessions
|
||||
SESSION_NOT_FOUND = "Session not found."
|
||||
|
||||
# genres
|
||||
GENRE_LIMIT_EXCEEDED = "No more than 1 genre is allowed."
|
||||
GENRE_MINIMUM_NOT_MET = "At least 1 genre is required."
|
||||
|
||||
# instruments
|
||||
INSTRUMENT_LIMIT_EXCEEDED = "No more than 5 instruments are allowed."
|
||||
INSTRUMENT_MINIMUM_NOT_MET = "At least 1 instrument is required."
|
||||
|
||||
# user
|
||||
OLD_PASSWORD_DOESNT_MATCH = "Your old password is incorrect."
|
||||
EMAIL_NOT_FOUND = "Email address not found."
|
||||
NOT_YOUR_PASSWORD = "is not your current password"
|
||||
EMAIL_ALREADY_TAKEN = "has already been taken"
|
||||
EMAIL_MATCHES_CURRENT = "is same as your current email"
|
||||
INVALID_FPFILE = "is not valid"
|
||||
end
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
module JamRuby
|
||||
class DbUtil
|
||||
def self.create(connection_hash)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
module JamRuby
|
||||
class Environment
|
||||
def self.mode
|
||||
if Object.const_defined?('Rails')
|
||||
return Rails.env
|
||||
else
|
||||
# right now, there is no idea of a non-test jam-ruby usage, because it's solely a library
|
||||
# this will need to change if we add executables to jam-ruby
|
||||
return "test"
|
||||
end
|
||||
end
|
||||
|
||||
def self.application
|
||||
if Object.const_defined?('Rails')
|
||||
return 'jamweb'
|
||||
else
|
||||
return 'jamruby'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
module JamRuby
|
||||
# if a bad argument is supplied.
|
||||
# Why not use the default ruby argument error? Using this one allows us to know our API layer threw this, versus us using some core library incorrectly
|
||||
class JamArgumentError < ArgumentError
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
module JamRuby
|
||||
class PermissionError < Exception
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
module JamRuby
|
||||
# this exception can be thrown if the server is not in a state that allows the operation to succeed
|
||||
# however, it's not necessarily a bad thing; it just means
|
||||
class StateError < Exception
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# initialize actionmailer
|
||||
ActionMailer::Base.raise_delivery_errors = true
|
||||
ActionMailer::Base.view_paths = File.expand_path('../../jam_ruby/app/views/', __FILE__)
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
require 'openssl'
|
||||
require 'digest/sha1'
|
||||
require 'base64'
|
||||
require 'cgi'
|
||||
require 'time'
|
||||
require 'json'
|
||||
|
||||
module JamRuby
|
||||
|
||||
# Most of the code below was taken from the example located here:
|
||||
# https://github.com/assistly/multipass-examples/blob/master/ruby.rb
|
||||
class DeskMultipass
|
||||
|
||||
API_KEY = "453ddfc0bab00130a9c13bc9a68cf24c"
|
||||
SITE_KEY = "jamkazam"
|
||||
|
||||
def initialize(user)
|
||||
@user = user
|
||||
generate_token_and_signature
|
||||
end
|
||||
|
||||
def token
|
||||
@token
|
||||
end
|
||||
|
||||
def signature
|
||||
@signature
|
||||
end
|
||||
|
||||
private
|
||||
def generate_token_and_signature
|
||||
key = Digest::SHA1.digest(API_KEY + SITE_KEY)[0...16]
|
||||
|
||||
# Generate a random 16 byte IV
|
||||
iv = OpenSSL::Random.random_bytes(16)
|
||||
|
||||
json = JSON.generate(
|
||||
:uid => @user.id,
|
||||
:expires => (Time.now + 300).iso8601,
|
||||
:customer_name => @user.name,
|
||||
:customer_email => @user.email)
|
||||
|
||||
cipher = OpenSSL::Cipher::Cipher.new("aes-128-cbc")
|
||||
cipher.encrypt
|
||||
cipher.key = key
|
||||
cipher.iv = iv
|
||||
encrypted = cipher.update(json) + cipher.final
|
||||
|
||||
prepended = iv + encrypted
|
||||
token = Base64.encode64(prepended)
|
||||
signature = Base64.encode64(OpenSSL::HMAC.digest('sha1', API_KEY, token))
|
||||
|
||||
@token = CGI.escape(token)
|
||||
@signature = CGI.escape(signature)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
module JamRuby
|
||||
|
||||
class Profanity
|
||||
@@dictionary_file = File.join('config/profanity.yml')
|
||||
@@dictionary = nil
|
||||
|
||||
def self.dictionary
|
||||
if File.exists? @@dictionary_file
|
||||
@@dictionary ||= YAML.load_file(@@dictionary_file)
|
||||
else
|
||||
@@dictionary = []
|
||||
end
|
||||
@@dictionary
|
||||
end
|
||||
|
||||
def self.check_word(word)
|
||||
dictionary.include?(word.downcase)
|
||||
end
|
||||
|
||||
def self.is_profane?(text)
|
||||
return false if text.nil?
|
||||
|
||||
text.split(/\W+/).each do |word|
|
||||
return true if check_word(word)
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
# This needs to be outside the module to work.
|
||||
class NoProfanityValidator < ActiveModel::EachValidator
|
||||
# implement the method called during validation
|
||||
def validate_each(record, attribute, value)
|
||||
record.errors[attribute] << 'Cannot contain profanity' if Profanity.is_profane?(value)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
require 'aws-sdk'
|
||||
require 'active_support/all'
|
||||
|
||||
module JamRuby
|
||||
class S3Manager
|
||||
|
||||
SECRET = "krQP3fKpjAtWkApBEJwJJrCZ"
|
||||
|
||||
def self.s3_url(filename)
|
||||
"s3://#{aws_bucket}/#{filename}"
|
||||
end
|
||||
|
||||
def self.url(filename)
|
||||
"https://s3.amazonaws.com/#{aws_bucket}/#{filename}"
|
||||
end
|
||||
|
||||
def self.upload_sign(filename, content_md5, upload_id)
|
||||
hdt = http_date_time
|
||||
str_to_sign = "PUT\n#{content_md5}\n#{content_type}\n#{hdt}\n/#{aws_bucket}/#{filename}"
|
||||
signature = Base64.encode64(HMAC::SHA1.digest(aws_secret_key, str_to_sign)).chomp
|
||||
{ :filename => filename, :signature => signature, :datetime => hdt, :upload_id => upload_id }
|
||||
end
|
||||
|
||||
def self.hashed_filename(type, id)
|
||||
Digest::SHA1.hexdigest "#{SECRET}_#{type}_#{id}"
|
||||
end
|
||||
|
||||
def self.multipart_upload_start(upload_filename)
|
||||
return 0 if @is_unit_test
|
||||
s3_bucket.objects[upload_filename].multipart_upload.id
|
||||
end
|
||||
|
||||
def self.multipart_upload_complete(upload_id)
|
||||
return if @is_unit_test
|
||||
s3_bucket.objects[upload_filename].multipart_uploads[upload_id].upload_complete(:remote_parts)
|
||||
end
|
||||
|
||||
def self.delete(filename)
|
||||
return if @is_unit_test
|
||||
s3_bucket.objects[filename].delete
|
||||
end
|
||||
|
||||
def self.set_unit_test
|
||||
@is_unit_test = true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.s3_bucket
|
||||
@s3 ||= AWS::S3.new
|
||||
@s3.buckets[aws_bucket]
|
||||
end
|
||||
|
||||
def self.aws_bucket
|
||||
"jamkazam-dev"
|
||||
end
|
||||
|
||||
def self.aws_secret_key
|
||||
"XLq2mpJHNyA0bN7GBSdYyF/pWjfzGkDx92b1C+Wv"
|
||||
end
|
||||
|
||||
def self.content_type
|
||||
"application/octet-stream"
|
||||
end
|
||||
|
||||
def self.http_date_time
|
||||
Time.now.strftime("%a, %d %b %Y %H:%M:%S %z")
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
require 'aws-sdk'
|
||||
require 'active_support/all'
|
||||
|
||||
module JamRuby
|
||||
class S3Util
|
||||
@@def_opts = { :expires => 3600 * 24, :secure => true } # 24 hours from now
|
||||
@@s3 = AWS::S3.new(:access_key_id => ENV['AWS_KEY'], :secret_access_key => ENV['AWS_SECRET'])
|
||||
|
||||
def self.sign_url(bucket, path, options = @@def_opts)
|
||||
|
||||
bucket_gen = @@s3.buckets[bucket]
|
||||
|
||||
return "#{bucket_gen.objects[path].url_for(:read, :expires => options[:expires]).to_s}"
|
||||
end
|
||||
|
||||
def self.url(aws_bucket, filename, options = @@def_opts)
|
||||
"http#{options[:secure] ? "s" : ""}://s3.amazonaws.com/#{aws_bucket}/#{filename}"
|
||||
end
|
||||
|
||||
def self.move(aws_bucket, source, destination)
|
||||
@@s3.buckets[aws_bucket].objects[source].move_to[destination]
|
||||
end
|
||||
|
||||
def self.delete(aws_bucket, path)
|
||||
@@s3.buckets[aws_bucket].objects[path].delete()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
module JamRuby
|
||||
# creates messages (implementation: protocol buffer) objects cleanly
|
||||
class MessageFactory
|
||||
|
||||
CLIENT_TARGET = "client"
|
||||
SERVER_TARGET = "server"
|
||||
SESSION_TARGET_PREFIX = "session:"
|
||||
USER_TARGET_PREFIX = "user:"
|
||||
CLIENT_TARGET_PREFIX = "client:"
|
||||
|
||||
def initialize()
|
||||
@type_values = {}
|
||||
|
||||
Jampb::ClientMessage::Type.constants.each do |constant|
|
||||
@type_values[Jampb::ClientMessage::Type.const_get(constant)] = constant
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# given a string (bytes) payload, return a client message
|
||||
def parse_client_msg(payload)
|
||||
return Jampb::ClientMessage.parse(payload)
|
||||
end
|
||||
|
||||
# create a login message using user/pass
|
||||
def login_with_user_pass(username, password, options = {})
|
||||
login = Jampb::Login.new(:username => username, :password => password, :client_id => options[:client_id])
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::LOGIN, :route_to => SERVER_TARGET, :login => login)
|
||||
end
|
||||
|
||||
# create a login message using token (a cookie or similar)
|
||||
def login_with_token(token, options = {})
|
||||
login = Jampb::Login.new(:token => token, :client_id => options[:client_id])
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::LOGIN, :route_to => SERVER_TARGET, :login => login)
|
||||
end
|
||||
|
||||
# create a login ack (login was successful)
|
||||
def login_ack(public_ip, client_id, token, heartbeat_interval, music_session_id, reconnected)
|
||||
login_ack = Jampb::LoginAck.new(:public_ip => public_ip, :client_id => client_id, :token => token, :heartbeat_interval => heartbeat_interval, :music_session_id => music_session_id, :reconnected => reconnected)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::LOGIN_ACK, :route_to => CLIENT_TARGET, :login_ack => login_ack)
|
||||
end
|
||||
|
||||
# create a music session login message
|
||||
def login_music_session(music_session)
|
||||
login_music_session = Jampb::LoginMusicSession.new(:music_session => music_session)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::LOGIN_MUSIC_SESSION, :route_to => SERVER_TARGET, :login_music_session => login_music_session)
|
||||
end
|
||||
|
||||
# create a music session login message ack (success or on failure)
|
||||
def login_music_session_ack(error, error_reason)
|
||||
login_music_session_ack = Jampb::LoginMusicSessionAck.new(:error => error, :error_reason => error_reason)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::LOGIN_MUSIC_SESSION_ACK, :route_to => CLIENT_TARGET, :login_music_session_ack => login_music_session_ack)
|
||||
end
|
||||
|
||||
# create a music session 'leave session' message
|
||||
def leave_music_session(music_session)
|
||||
leave_music_session = Jampb::LeaveMusicSession.new(:music_session => music_session)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::LEAVE_MUSIC_SESSION, :route_to => SERVER_TARGET, :leave_music_session => leave_music_session)
|
||||
end
|
||||
|
||||
# create a music session leave message ack (success or on failure)
|
||||
def leave_music_session_ack(error, error_reason)
|
||||
leave_music_session_ack = Jampb::LeaveMusicSessionAck.new(:error => error, :error_reason => error_reason)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::LEAVE_MUSIC_SESSION_ACK, :route_to => CLIENT_TARGET, :leave_music_session_ack => leave_music_session_ack)
|
||||
end
|
||||
|
||||
# create a server bad state recovered msg
|
||||
def server_bad_state_recovered(original_message_id)
|
||||
recovered = Jampb::ServerBadStateRecovered.new()
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::SERVER_BAD_STATE_RECOVERED, :route_to => CLIENT_TARGET, :server_bad_state_recovered => recovered, :in_reply_to => original_message_id)
|
||||
end
|
||||
|
||||
# create a server error
|
||||
def server_generic_error(error_msg)
|
||||
error = Jampb::ServerGenericError.new(:error_msg => error_msg)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::SERVER_GENERIC_ERROR, :route_to => CLIENT_TARGET, :server_generic_error => error)
|
||||
end
|
||||
|
||||
# create a server rejection error
|
||||
def server_rejection_error(error_msg)
|
||||
error = Jampb::ServerRejectionError.new(:error_msg => error_msg)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::SERVER_REJECTION_ERROR, :route_to => CLIENT_TARGET, :server_rejection_error => error)
|
||||
end
|
||||
|
||||
# create a server rejection error
|
||||
def server_permission_error(original_message_id, error_msg)
|
||||
error = Jampb::ServerPermissionError.new(:error_msg => error_msg)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::SERVER_PERMISSION_ERROR, :route_to => CLIENT_TARGET, :server_permission_error => error, :in_reply_to => original_message_id)
|
||||
end
|
||||
|
||||
# create a server bad state error
|
||||
def server_bad_state_error(original_message_id, error_msg)
|
||||
error = Jampb::ServerBadStateError.new(:error_msg => error_msg)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::SERVER_BAD_STATE_ERROR, :route_to => CLIENT_TARGET, :server_bad_state_error => error, :in_reply_to => original_message_id)
|
||||
end
|
||||
|
||||
# create a friend joined session message
|
||||
def friend_session_join(session_id, user_id, username, photo_url)
|
||||
join = Jampb::FriendSessionJoin.new(:session_id => session_id, :user_id => user_id, :username => username, :photo_url => photo_url)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::FRIEND_SESSION_JOIN, :route_to => CLIENT_TARGET, :friend_session_join => join)
|
||||
end
|
||||
|
||||
# create a musician joined session message
|
||||
def musician_session_join(session_id, user_id, username, photo_url)
|
||||
join = Jampb::MusicianSessionJoin.new(:session_id => session_id, :user_id => user_id, :username => username, :photo_url => photo_url)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::MUSICIAN_SESSION_JOIN, :route_to => CLIENT_TARGET, :musician_session_join => join)
|
||||
end
|
||||
|
||||
# create a musician left session message
|
||||
def musician_session_depart(session_id, user_id, username, photo_url)
|
||||
left = Jampb::MusicianSessionDepart.new(:session_id => session_id, :user_id => user_id, :username => username, :photo_url => photo_url)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::MUSICIAN_SESSION_DEPART, :route_to => CLIENT_TARGET, :musician_session_depart => left)
|
||||
end
|
||||
|
||||
# create a musician fresh session message
|
||||
def musician_session_fresh(session_id, user_id, username, photo_url)
|
||||
fresh = Jampb::MusicianSessionFresh.new(:session_id => session_id, :user_id => user_id, :username => username, :photo_url => photo_url)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::MUSICIAN_SESSION_FRESH, :route_to => CLIENT_TARGET, :musician_session_fresh => fresh)
|
||||
end
|
||||
|
||||
# create a musician stale session message
|
||||
def musician_session_stale(session_id, user_id, username, photo_url)
|
||||
stale = Jampb::MusicianSessionStale.new(:session_id => session_id, :user_id => user_id, :username => username, :photo_url => photo_url)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::MUSICIAN_SESSION_STALE, :route_to => CLIENT_TARGET, :musician_session_stale => stale)
|
||||
end
|
||||
|
||||
|
||||
# create a user-joined session message
|
||||
def join_request(session_id, join_request_id, username, text)
|
||||
join_request = Jampb::JoinRequest.new(:join_request_id => join_request_id, :username => username, :text => text)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::JOIN_REQUEST, :route_to => SESSION_TARGET_PREFIX + session_id, :join_request => join_request)
|
||||
end
|
||||
|
||||
# create a test message to send in session
|
||||
def test_session_message(session_id, msg)
|
||||
test = Jampb::TestSessionMessage.new(:msg => msg)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::TEST_SESSION_MESSAGE, :route_to => SESSION_TARGET_PREFIX + session_id, :test_session_message => test)
|
||||
end
|
||||
|
||||
def session_invitation(receiver_id, invitation_id)
|
||||
session_invitation = Jampb::SessionInvitation.new(:invitation => invitation_id)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::SESSION_INVITATION, :route_to => USER_TARGET_PREFIX + receiver_id, :session_invitation => session_invitation)
|
||||
end
|
||||
|
||||
# create a friend update message
|
||||
def friend_update(user_id, name, photo_url, online, msg)
|
||||
friend = Jampb::FriendUpdate.new(:user_id => user_id, :name => name, :photo_url => photo_url, :online => online, :msg => msg)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::FRIEND_UPDATE, :route_to => USER_TARGET_PREFIX + user_id, :friend_update => friend)
|
||||
end
|
||||
|
||||
# create a friend request message
|
||||
def friend_request(friend_request_id, user_id, name, photo_url, friend_id, msg, notification_id, created_at)
|
||||
friend_request = Jampb::FriendRequest.new(:friend_request_id => friend_request_id,
|
||||
:user_id => user_id, :name => name, :photo_url => photo_url, :friend_id => friend_id, :msg => msg,
|
||||
:notification_id => notification_id, :created_at => created_at)
|
||||
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::FRIEND_REQUEST, :route_to => USER_TARGET_PREFIX + friend_id, :friend_request => friend_request)
|
||||
end
|
||||
|
||||
# create a friend request acceptance message
|
||||
def friend_request_accepted(friend_id, name, photo_url, user_id, msg, notification_id, created_at)
|
||||
friend_request_accepted = Jampb::FriendRequestAccepted.new(:friend_id => friend_id,
|
||||
:name => name, :photo_url => photo_url, :user_id => user_id, :msg => msg,
|
||||
:notification_id => notification_id, :created_at => created_at)
|
||||
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::FRIEND_REQUEST_ACCEPTED, :route_to => USER_TARGET_PREFIX + user_id, :friend_request_accepted => friend_request_accepted)
|
||||
end
|
||||
|
||||
############## P2P CLIENT MESSAGES #################
|
||||
|
||||
# send a request to do a ping
|
||||
def ping_request(client_id, from)
|
||||
ping_request = Jampb::PingRequest.new()
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::PING_REQUEST, :route_to => CLIENT_TARGET_PREFIX + client_id, :from => from, :ping_request => ping_request)
|
||||
end
|
||||
|
||||
# respond to a ping_request with an ack
|
||||
def ping_ack(client_id, from)
|
||||
ping_ack = Jampb::PingAck.new()
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::PING_ACK, :route_to => CLIENT_TARGET_PREFIX + client_id, :from => from, :ping_ack => ping_ack)
|
||||
end
|
||||
|
||||
# create a test message to send in session
|
||||
def test_client_message(client_id, from, msg)
|
||||
test = Jampb::TestClientMessage.new(:msg => msg)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::TEST_CLIENT_MESSAGE, :route_to => CLIENT_TARGET_PREFIX + client_id, :from => from, :test_client_message => test)
|
||||
end
|
||||
|
||||
####################################################
|
||||
|
||||
# create a heartbeat
|
||||
def heartbeat()
|
||||
heartbeat = Jampb::Heartbeat.new
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::HEARTBEAT, :route_to => SERVER_TARGET, :heartbeat => heartbeat)
|
||||
end
|
||||
|
||||
# create a heartbeat ack
|
||||
def heartbeat_ack()
|
||||
heartbeat_ack = Jampb::HeartbeatAck.new()
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::HEARTBEAT_ACK, :route_to => CLIENT_TARGET, :heartbeat_ack => heartbeat_ack)
|
||||
end
|
||||
|
||||
# is this message directed to the server?
|
||||
def server_directed? msg
|
||||
return msg.route_to == MessageFactory::SERVER_TARGET
|
||||
end
|
||||
|
||||
# is this message directed to the client?
|
||||
def client_directed? msg
|
||||
return msg.route_to.start_with? MessageFactory::CLIENT_TARGET_PREFIX
|
||||
end
|
||||
|
||||
# is this message directed to a (music) session?
|
||||
def session_directed? msg
|
||||
return msg.route_to.start_with? MessageFactory::SESSION_TARGET_PREFIX
|
||||
end
|
||||
|
||||
# is this message directed to a user?
|
||||
def user_directed? msg
|
||||
return msg.route_to.start_with? MessageFactory::USER_TARGET_PREFIX
|
||||
end
|
||||
|
||||
def extract_session(msg)
|
||||
return msg.route_to[MessageFactory::SESSION_TARGET_PREFIX..-1]
|
||||
end
|
||||
|
||||
def get_message_type msg
|
||||
return @type_values[msg.type]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
module JamRuby
|
||||
class ArtifactUpdate < ActiveRecord::Base
|
||||
|
||||
DEFAULT_ENVIRONMENT = 'public'
|
||||
|
||||
PRODUCTS = ['JamClient/Win32', 'JamClient/MacOSX']
|
||||
|
||||
self.primary_key = 'id'
|
||||
attr_accessible :version, :uri, :sha1, :environment, :product
|
||||
|
||||
|
||||
mount_uploader :uri, ArtifactUploader
|
||||
|
||||
validates :version, :presence => true
|
||||
validates :uri, :presence => true
|
||||
validates :sha1, :presence => true
|
||||
validates :size, :presence => true
|
||||
validates :environment, :presence => true
|
||||
validates :product, :inclusion => {:in => PRODUCTS}
|
||||
|
||||
before_validation do
|
||||
if uri.present? && uri_changed?
|
||||
self.size = uri.file.size
|
||||
self.sha1 = Digest::MD5.hexdigest(File.read(uri.current_path))
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
module JamRuby
|
||||
class Band < ActiveRecord::Base
|
||||
|
||||
attr_accessible :name, :website, :biography, :city, :state, :country
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
validates :biography, no_profanity: true
|
||||
|
||||
# musicians
|
||||
has_many :band_musicians, :class_name => "JamRuby::BandMusician"
|
||||
has_many :users, :through => :band_musicians, :class_name => "JamRuby::User"
|
||||
|
||||
# genres
|
||||
has_and_belongs_to_many :genres, :class_name => "JamRuby::Genre", :join_table => "bands_genres"
|
||||
|
||||
# recordings
|
||||
has_many :recordings, :class_name => "JamRuby::Recording", :foreign_key => "band_id"
|
||||
|
||||
# likers
|
||||
has_many :likers, :class_name => "JamRuby::BandLiker", :foreign_key => "band_id", :inverse_of => :band
|
||||
has_many :inverse_likers, :through => :likers, :class_name => "JamRuby::User", :foreign_key => "liker_id"
|
||||
|
||||
# followers
|
||||
has_many :band_followers, :class_name => "JamRuby::BandFollower", :foreign_key => "band_id"
|
||||
has_many :followers, :through => :band_followers, :class_name => "JamRuby::User"
|
||||
has_many :inverse_band_followers, :through => :followers, :class_name => "JamRuby::BandFollower", :foreign_key => "follower_id"
|
||||
has_many :inverse_followers, :through => :inverse_band_followers, :source => :band, :class_name => "JamRuby::Band"
|
||||
|
||||
# invitations
|
||||
has_many :invitations, :inverse_of => :band, :class_name => "JamRuby::BandInvitation", :foreign_key => "band_id"
|
||||
|
||||
# music_sessions
|
||||
has_many :music_sessions, :class_name => "JamRuby::MusicSession", :foreign_key => "band_id"
|
||||
has_many :music_session_history, :class_name => "JamRuby::MusicSessionHistory", :foreign_key => "band_id", :inverse_of => :band
|
||||
|
||||
def liker_count
|
||||
return self.likers.size
|
||||
end
|
||||
|
||||
def follower_count
|
||||
return self.followers.size
|
||||
end
|
||||
|
||||
def recording_count
|
||||
return self.recordings.size
|
||||
end
|
||||
|
||||
def session_count
|
||||
return self.music_sessions.size
|
||||
end
|
||||
|
||||
def location
|
||||
loc = self.city.blank? ? '' : self.city
|
||||
loc = loc.blank? ? self.state : "#{loc}, #{self.state}" unless self.state.blank?
|
||||
#loc = loc.blank? ? self.country : "#{loc}, #{self.country}" unless self.country.blank?
|
||||
loc
|
||||
end
|
||||
|
||||
def add_member(user_id, admin)
|
||||
BandMusician.create(:band_id => self.id, :user_id => user_id, :admin => admin)
|
||||
end
|
||||
|
||||
def self.recording_index(current_user, band_id)
|
||||
hide_private = false
|
||||
band = Band.find(band_id)
|
||||
|
||||
# hide private Recordings from anyone who's not in the Band
|
||||
unless band.users.exists? current_user
|
||||
hide_private = true
|
||||
end
|
||||
|
||||
if hide_private
|
||||
recordings = Recording.joins(:band_recordings)
|
||||
.where(:bands_recordings => {:band_id => "#{band_id}"}, :public => true)
|
||||
|
||||
else
|
||||
recordings = Recording.joins(:band_recordings)
|
||||
.where(:bands_recordings => {:band_id => "#{band_id}"})
|
||||
end
|
||||
|
||||
return recordings
|
||||
end
|
||||
|
||||
def self.search(query, options = { :limit => 10 })
|
||||
|
||||
# only issue search if at least 2 characters are specified
|
||||
if query.nil? || query.length < 2
|
||||
return []
|
||||
end
|
||||
|
||||
# create 'anded' statement
|
||||
query = Search.create_tsquery(query)
|
||||
|
||||
if query.nil? || query.length == 0
|
||||
return []
|
||||
end
|
||||
|
||||
return Band.where("name_tsv @@ to_tsquery('jamenglish', ?)", query).limit(options[:limit])
|
||||
end
|
||||
|
||||
# helper method for creating / updating a Band
|
||||
def self.save(id, name, website, biography, city, state, country, genres, user_id, photo_url, logo_url)
|
||||
|
||||
user = User.find(user_id)
|
||||
|
||||
# new band
|
||||
if id.nil?
|
||||
|
||||
# ensure person creating this Band is a Musician
|
||||
unless user.musician?
|
||||
raise JamRuby::PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
|
||||
end
|
||||
|
||||
validate_genres(genres, false)
|
||||
band = Band.new()
|
||||
|
||||
# band update
|
||||
else
|
||||
validate_genres(genres, true)
|
||||
band = Band.find(id)
|
||||
|
||||
# ensure user updating Band details is a Band member
|
||||
unless band.users.exists? user
|
||||
raise PermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR
|
||||
end
|
||||
end
|
||||
|
||||
# name
|
||||
unless name.nil?
|
||||
band.name = name
|
||||
end
|
||||
|
||||
# website
|
||||
unless website.nil?
|
||||
band.website = website
|
||||
end
|
||||
|
||||
# biography
|
||||
unless biography.nil?
|
||||
band.biography = biography
|
||||
end
|
||||
|
||||
# city
|
||||
unless city.nil?
|
||||
band.city = city
|
||||
end
|
||||
|
||||
# state
|
||||
unless state.nil?
|
||||
band.state = state
|
||||
end
|
||||
|
||||
# country
|
||||
unless country.nil?
|
||||
band.country = country
|
||||
end
|
||||
|
||||
# genres
|
||||
unless genres.nil?
|
||||
ActiveRecord::Base.transaction do
|
||||
# delete all genres for this band first
|
||||
unless band.id.nil? || band.id.length == 0
|
||||
band.genres.delete_all
|
||||
end
|
||||
|
||||
# loop through each genre in the array and save to the db
|
||||
genres.each do |genre_id|
|
||||
g = Genre.find(genre_id)
|
||||
band.genres << g
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# photo url
|
||||
unless photo_url.nil?
|
||||
band.photo_url = photo_url
|
||||
end
|
||||
|
||||
# logo url
|
||||
unless logo_url.nil?
|
||||
band.logo_url = logo_url
|
||||
end
|
||||
|
||||
band.updated_at = Time.now.getutc
|
||||
band.save
|
||||
|
||||
# add the creator as the admin
|
||||
if id.nil?
|
||||
BandMusician.create(:band_id => band.id, :user_id => user_id, :admin => true)
|
||||
end
|
||||
|
||||
return band
|
||||
end
|
||||
|
||||
private
|
||||
def self.validate_genres(genres, is_nil_ok)
|
||||
if is_nil_ok && genres.nil?
|
||||
return
|
||||
end
|
||||
|
||||
if genres.nil?
|
||||
raise JamRuby::JamArgumentError, ValidationMessages::GENRE_MINIMUM_NOT_MET
|
||||
else
|
||||
if genres.size < Limits::MIN_GENRES_PER_BAND
|
||||
raise JamRuby::JamArgumentError, ValidationMessages::GENRE_MINIMUM_NOT_MET
|
||||
end
|
||||
|
||||
if genres.size > Limits::MAX_GENRES_PER_BAND
|
||||
raise JamRuby::JamArgumentError, ValidationMessages::GENRE_LIMIT_EXCEEDED
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
module JamRuby
|
||||
class BandFollower < ActiveRecord::Base
|
||||
|
||||
self.table_name = "bands_followers"
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
belongs_to :band, :class_name => "JamRuby::Band", :foreign_key => "band_id"
|
||||
belongs_to :follower, :class_name => "JamRuby::User", :foreign_key => "follower_id"
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
module JamRuby
|
||||
class BandFollowing < ActiveRecord::Base
|
||||
|
||||
self.table_name = "bands_followers"
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
belongs_to :user, :class_name => "JamRuby::User", :foreign_key => "follower_id", :inverse_of => :inverse_band_followings
|
||||
belongs_to :band_following, :class_name => "JamRuby::Band", :foreign_key => "band_id", :inverse_of => :band_followings
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
module JamRuby
|
||||
class BandInvitation < ActiveRecord::Base
|
||||
|
||||
self.table_name = "band_invitations"
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
BAND_INVITATION_FAN_RECIPIENT_ERROR = "A Band invitation can only be sent to a Musician."
|
||||
|
||||
belongs_to :receiver, :inverse_of => :received_band_invitations, :foreign_key => "user_id", :class_name => "JamRuby::User"
|
||||
belongs_to :sender, :inverse_of => :sent_band_invitations, :foreign_key => "creator_id", :class_name => "JamRuby::User"
|
||||
belongs_to :band, :inverse_of => :invitations, :foreign_key => "band_id", :class_name => "JamRuby::Band"
|
||||
|
||||
def self.save(id, band_id, user_id, creator_id, accepted)
|
||||
|
||||
band_invitation = BandInvitation.new()
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
# ensure certain fields are only updated on creation
|
||||
if id.nil?
|
||||
# ensure recipient is a Musician
|
||||
user = User.find(user_id)
|
||||
unless user.musician?
|
||||
raise JamRuby::JamArgumentError, BAND_INVITATION_FAN_RECIPIENT_ERROR
|
||||
end
|
||||
|
||||
band_invitation.band_id = band_id
|
||||
band_invitation.user_id = user_id
|
||||
band_invitation.creator_id = creator_id
|
||||
|
||||
# only the accepted flag can be updated after initial creation
|
||||
else
|
||||
band_invitation = BandInvitation.find(id)
|
||||
band_invitation.accepted = accepted
|
||||
end
|
||||
|
||||
band_invitation.updated_at = Time.now.getutc
|
||||
band_invitation.save
|
||||
|
||||
# accept logic => (1) auto-friend each band member and (2) add the musician to the band
|
||||
if accepted
|
||||
band_musicians = BandMusician.where(:band_id => band_invitation.band.id)
|
||||
unless band_musicians.nil?
|
||||
band_musicians.each do |bm|
|
||||
Friendship.save(band_invitation.receiver.id, bm.user_id)
|
||||
end
|
||||
end
|
||||
|
||||
# accepting an invitation adds the musician to the band
|
||||
BandMusician.create(:band_id => band_invitation.band.id, :user_id => band_invitation.receiver.id, :admin => false)
|
||||
end
|
||||
end
|
||||
return band_invitation
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
module JamRuby
|
||||
class BandLiker < ActiveRecord::Base
|
||||
|
||||
self.table_name = "bands_likers"
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
belongs_to :band, :class_name => "JamRuby::Band", :foreign_key => "band_id", :inverse_of => :likers
|
||||
belongs_to :user, :class_name => "JamRuby::User", :foreign_key => "liker_id", :inverse_of => :band_likes
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
module JamRuby
|
||||
class BandMusician < ActiveRecord::Base
|
||||
|
||||
self.table_name = "bands_musicians"
|
||||
|
||||
attr_accessible :band_id, :user_id, :admin
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
belongs_to :user
|
||||
belongs_to :band
|
||||
|
||||
# name, genres, photo_url, and logo_url are needed here for the RABL file
|
||||
def name
|
||||
@name = self.band.name
|
||||
end
|
||||
|
||||
def genres
|
||||
@genres = self.band.genres
|
||||
end
|
||||
|
||||
def photo_url
|
||||
@photo_url = self.band.photo_url
|
||||
end
|
||||
|
||||
def logo_url
|
||||
@logo_url = self.band.logo_url
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
module JamRuby
|
||||
class ClaimedRecording < ActiveRecord::Base
|
||||
|
||||
validates :name, no_profanity: true
|
||||
|
||||
belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :claimed_recordings
|
||||
belongs_to :user, :class_name => "JamRuby::User", :inverse_of => :claimed_recordings
|
||||
belongs_to :genre, :class_name => "JamRuby::Genre"
|
||||
has_many :recorded_tracks, :through => :recording, :class_name => "JamRuby::RecordedTrack"
|
||||
|
||||
# user must own this object
|
||||
# params is a hash, and everything is optional
|
||||
def update_fields(user, params)
|
||||
if user != self.user
|
||||
raise PermissionError, "user doesn't own claimed_recording"
|
||||
end
|
||||
|
||||
self.name = params[:name] unless params[:name].nil?
|
||||
self.genre = Genre.find(params[:genre]) unless params[:genre].nil?
|
||||
self.is_public = params[:is_public] unless params[:is_public].nil?
|
||||
self.is_downloadable = params[:is_downloadable] unless params[:is_downloadable].nil?
|
||||
save
|
||||
end
|
||||
|
||||
def discard(user)
|
||||
if user != self.user
|
||||
raise PermissionError, "user doesn't own claimed_recording"
|
||||
end
|
||||
|
||||
# If this is the only copy, destroy the entire recording. Otherwise, just destroy this claimed_recording
|
||||
if recording.claimed_recordings.count == 1
|
||||
recording.discard
|
||||
else
|
||||
self.destroy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
require 'aasm'
|
||||
|
||||
module JamRuby
|
||||
class Connection < ActiveRecord::Base
|
||||
|
||||
SELECT_AT_LEAST_ONE = "Please select at least one track"
|
||||
FAN_CAN_NOT_JOIN_AS_MUSICIAN = "A fan can not join a music session as a musician"
|
||||
MUSIC_SESSION_MUST_BE_SPECIFIED = "A music session must be specified"
|
||||
INVITE_REQUIRED = "You must be invited to join this session"
|
||||
FANS_CAN_NOT_JOIN = "Fans can not join this session"
|
||||
|
||||
attr_accessor :joining_session
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
belongs_to :user, :class_name => "JamRuby::User"
|
||||
belongs_to :music_session, :class_name => "JamRuby::MusicSession"
|
||||
has_many :tracks, :class_name => "JamRuby::Track", :inverse_of => :connection
|
||||
|
||||
|
||||
validates :as_musician, :inclusion => {:in => [true, false]}
|
||||
validate :can_join_music_session, :if => :joining_session?
|
||||
after_save :require_at_least_one_track_when_in_session, :if => :joining_session?
|
||||
|
||||
include AASM
|
||||
IDLE_STATE = :idle
|
||||
CONNECT_STATE = :connected
|
||||
STALE_STATE = :stale
|
||||
EXPIRED_STATE = :expired
|
||||
|
||||
aasm do
|
||||
state IDLE_STATE, :initial => true
|
||||
state CONNECT_STATE
|
||||
state STALE_STATE
|
||||
state EXPIRED_STATE
|
||||
|
||||
event :connect do
|
||||
transitions :from => IDLE_STATE, :to => CONNECT_STATE
|
||||
transitions :from => STALE_STATE, :to => CONNECT_STATE
|
||||
end
|
||||
event :stale do
|
||||
transitions :from => CONNECT_STATE, :to => STALE_STATE
|
||||
transitions :from => IDLE_STATE, :to => STALE_STATE
|
||||
end
|
||||
event :expire, :after => :did_expire do
|
||||
transitions :from => CONNECT_STATE, :to => EXPIRED_STATE
|
||||
transitions :from => STALE_STATE, :to => EXPIRED_STATE
|
||||
transitions :from => IDLE_STATE, :to => EXPIRED_STATE
|
||||
end
|
||||
end
|
||||
|
||||
def state_message
|
||||
case self.aasm_state.to_sym
|
||||
when CONNECT_STATE
|
||||
'Connected'
|
||||
when STALE_STATE
|
||||
'Stale'
|
||||
else
|
||||
'Idle'
|
||||
end
|
||||
end
|
||||
|
||||
def did_expire
|
||||
self.destroy
|
||||
end
|
||||
|
||||
def joining_session?
|
||||
return joining_session
|
||||
end
|
||||
|
||||
def can_join_music_session
|
||||
|
||||
if music_session.nil?
|
||||
errors.add(:music_session, MUSIC_SESSION_MUST_BE_SPECIFIED)
|
||||
return false
|
||||
end
|
||||
|
||||
if as_musician
|
||||
unless self.user.musician
|
||||
errors.add(:as_musician, FAN_CAN_NOT_JOIN_AS_MUSICIAN)
|
||||
return false
|
||||
end
|
||||
|
||||
if music_session.musician_access
|
||||
if music_session.approval_required
|
||||
unless music_session.creator == user || music_session.invited_musicians.exists?(user)
|
||||
errors.add(:approval_required, INVITE_REQUIRED)
|
||||
return false
|
||||
end
|
||||
end
|
||||
else
|
||||
unless music_session.creator == user || music_session.invited_musicians.exists?(user)
|
||||
errors.add(:musician_access, INVITE_REQUIRED)
|
||||
return false
|
||||
end
|
||||
end
|
||||
else
|
||||
unless self.music_session.fan_access
|
||||
# it's someone joining as a fan, and the only way a fan can join is if fan_access is true
|
||||
errors.add(:fan_access, FANS_CAN_NOT_JOIN)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
# decides if a given user can access this client with p2p messaging
|
||||
# the answer is yes if the user is in the same music session
|
||||
def access_p2p?(user)
|
||||
return self.music_session.users.exists?(user)
|
||||
end
|
||||
|
||||
private
|
||||
def require_at_least_one_track_when_in_session
|
||||
if tracks.count == 0
|
||||
errors.add(:genres, SELECT_AT_LEAST_ONE)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
module JamRuby
|
||||
class CrashDump < ActiveRecord::Base
|
||||
|
||||
self.table_name = "crash_dumps"
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
belongs_to :user, :inverse_of => :crash_dumps, :class_name => "JamRuby::User"
|
||||
|
||||
validates :client_type, presence: true
|
||||
validates :client_version, presence: true
|
||||
|
||||
attr_accessor :user_email
|
||||
|
||||
before_validation(:on => :create) do
|
||||
self.created_at ||= Time.now
|
||||
self.id = SecureRandom.uuid
|
||||
self.uri = "dump/#{self.id}-#{self.created_at.to_i}"
|
||||
end
|
||||
|
||||
def user_email
|
||||
nil if user_id.nil?
|
||||
self.user.email
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
module JamRuby
|
||||
class FanInvitation < ActiveRecord::Base
|
||||
|
||||
FRIENDSHIP_REQUIRED_VALIDATION_ERROR = "You can only invite friends"
|
||||
MEMBERSHIP_REQUIRED_OF_MUSIC_SESSION = "You must be a member of the music session to send invitations on behalf of it"
|
||||
|
||||
self.primary_key = 'id'
|
||||
belongs_to :sender, :inverse_of => :sent_fan_invitations, :class_name => "JamRuby::User", :foreign_key => "sender_id"
|
||||
belongs_to :receiver, :inverse_of => :received_fan_invitations, :class_name => "JamRuby::User", :foreign_key => "receiver_id"
|
||||
belongs_to :music_session, :inverse_of => :fan_invitations, :class_name => "JamRuby::MusicSession"
|
||||
|
||||
validates :sender, :presence => true
|
||||
validates :receiver, :presence => true
|
||||
validates :music_session, :presence => true
|
||||
|
||||
validate :require_sender_in_music_session, :require_are_friends
|
||||
|
||||
private
|
||||
|
||||
def require_sender_in_music_session
|
||||
unless music_session.users.exists? sender
|
||||
errors.add(:music_session, MEMBERSHIP_REQUIRED_OF_MUSIC_SESSION)
|
||||
end
|
||||
end
|
||||
|
||||
def require_are_friends
|
||||
unless receiver.friends.exists? sender
|
||||
errors.add(:receiver, FRIENDSHIP_REQUIRED_VALIDATION_ERROR)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
module JamRuby
|
||||
class Feedback
|
||||
include ActiveModel::Validations
|
||||
include ActiveModel::Validations::Callbacks
|
||||
include ActiveModel::Observing
|
||||
extend ActiveModel::Callbacks
|
||||
|
||||
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
|
||||
validates :email, presence: true, format: {with: VALID_EMAIL_REGEX}
|
||||
validates :body, :presence => true
|
||||
|
||||
attr_accessor :email, :body
|
||||
|
||||
def save
|
||||
return valid?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
module JamRuby
|
||||
class FeedbackObserver < ActiveRecord::Observer
|
||||
|
||||
observe JamRuby::Feedback
|
||||
|
||||
def after_validation(feedback)
|
||||
CorpMailer.feedback(feedback).deliver unless feedback.errors.any?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
module JamRuby
|
||||
class FriendRequest < ActiveRecord::Base
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
STATUS = %w(accept block spam ignore)
|
||||
|
||||
belongs_to :user, :class_name => "JamRuby::User"
|
||||
belongs_to :friend, :class_name => "JamRuby::User"
|
||||
|
||||
validates :user_id, :presence => true
|
||||
validates :friend_id, :presence => true
|
||||
#validates :status, :inclusion => {:in => STATUS}
|
||||
validates :message, no_profanity: true
|
||||
|
||||
def to_s
|
||||
return "#{self.id} => #{self.user.to_s}:#{self.friend.to_s}"
|
||||
end
|
||||
|
||||
def self.save(id, user_id, friend_id, status, message)
|
||||
# new friend request
|
||||
if id.nil?
|
||||
friend_request = FriendRequest.new()
|
||||
friend_request = validate_friend_request(friend_request, user_id, friend_id)
|
||||
friend_request.user_id = user_id
|
||||
friend_request.friend_id = friend_id
|
||||
friend_request.message = message
|
||||
friend_request.save
|
||||
|
||||
# send notification
|
||||
Notification.send_friend_request(friend_request.id, user_id, friend_id)
|
||||
|
||||
else
|
||||
ActiveRecord::Base.transaction do
|
||||
friend_request = FriendRequest.find(id)
|
||||
friend_request.status = status
|
||||
friend_request.updated_at = Time.now.getutc
|
||||
friend_request.save
|
||||
|
||||
# create both records for this friendship
|
||||
if friend_request.status == "accept"
|
||||
Friendship.save(friend_request.user_id, friend_request.friend_id)
|
||||
|
||||
# send notification
|
||||
Notification.send_friend_request_accepted(friend_request.user_id, friend_request.friend_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return friend_request
|
||||
end
|
||||
|
||||
private
|
||||
def self.validate_friend_request(friend_request, user_id, friend_id)
|
||||
friend_requests = FriendRequest.where("user_id='#{user_id}' AND friend_id='#{friend_id}'")
|
||||
|
||||
# check if there are any friend requests for this source/target user combo, and if
|
||||
# any have been marked as spam or blocked, set the status of this friend request
|
||||
# to match so it doesn't show up in the queue
|
||||
unless friend_requests.nil? || friend_requests.size == 0
|
||||
if friend_requests.exists?(:status => "spam")
|
||||
friend_request.status = "spam"
|
||||
|
||||
elsif friend_requests.exists?(:status => "block")
|
||||
friend_request.status = "block"
|
||||
end
|
||||
end
|
||||
return friend_request
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
module JamRuby
|
||||
class Friendship < ActiveRecord::Base
|
||||
|
||||
attr_accessible :user_id, :friend_id
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
belongs_to :user, :class_name => "JamRuby::User", :foreign_key => "user_id", :inverse_of => :inverse_friendships
|
||||
belongs_to :friend, :class_name => "JamRuby::User", :foreign_key => "friend_id", :inverse_of => :friendships
|
||||
|
||||
def self.save(user_id, friend_id)
|
||||
friendship = Friendship.where("user_id='#{user_id}' AND friend_id='#{friend_id}'")
|
||||
|
||||
if friendship.nil? || friendship.size == 0
|
||||
Friendship.create(:user_id => user_id, :friend_id => friend_id)
|
||||
Friendship.create(:user_id => friend_id, :friend_id => user_id)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# not like .save() in that it does not check for an existing friendship. The caller is responsible
|
||||
# for checking for errors on the models
|
||||
def self.save_using_models(user, friend)
|
||||
this = Friendship.new
|
||||
this.user = user
|
||||
this.friend = friend
|
||||
|
||||
that = Friendship.new
|
||||
that.user = friend
|
||||
that.friend = user
|
||||
|
||||
this.save
|
||||
that.save
|
||||
return [this, that]
|
||||
end
|
||||
|
||||
def self.search(query, user_id, options = { :limit => 10 })
|
||||
# only issue search if at least 2 characters are specified
|
||||
if query.nil? || query.length < 2 || user_id.nil?
|
||||
return []
|
||||
end
|
||||
|
||||
# create 'anded' statement
|
||||
query = Search.create_tsquery(query)
|
||||
|
||||
if query.nil? || query.length == 0
|
||||
return []
|
||||
end
|
||||
|
||||
friends = Friendship.joins(
|
||||
%Q{
|
||||
INNER JOIN
|
||||
users
|
||||
ON friendships.friend_id = users.id
|
||||
WHERE friendships.user_id = '#{user_id}'
|
||||
AND users.name_tsv @@ to_tsquery('jamenglish', '#{query}')
|
||||
}
|
||||
)
|
||||
|
||||
friends = friends.limit(options[:limit])
|
||||
return friends
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
module JamRuby
|
||||
class Genre < ActiveRecord::Base
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
# bands
|
||||
has_and_belongs_to_many :bands, :class_name => "JamRuby::Band", :join_table => "bands_genres"
|
||||
|
||||
# genres
|
||||
has_and_belongs_to_many :recordings, :class_name => "JamRuby::Recording", :join_table => "recordings_genres"
|
||||
|
||||
# music sessions
|
||||
has_and_belongs_to_many :music_sessions, :class_name => "JamRuby::MusicSession", :join_table => "genres_music_sessions"
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
module JamRuby
|
||||
class Instrument < ActiveRecord::Base
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
# users
|
||||
has_many :musician_instruments, :class_name => "JamRuby::MusicianInstrument"
|
||||
has_many :users, :through => :musician_instruments, :class_name => "JamRuby::User"
|
||||
has_many :tracks, :class_name => "JamRuby::Track", :inverse_of => :instrument
|
||||
has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :inverse_of => :instrument
|
||||
|
||||
# music sessions
|
||||
has_and_belongs_to_many :music_sessions, :class_name => "JamRuby::MusicSession", :join_table => "genres_music_sessions"
|
||||
|
||||
def self.standard_list
|
||||
return Instrument.where('instruments.popularity > 0').order('instruments.popularity DESC, instruments.description ASC')
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
module JamRuby
|
||||
class Invitation < ActiveRecord::Base
|
||||
|
||||
FRIENDSHIP_REQUIRED_VALIDATION_ERROR = "You can only invite friends"
|
||||
MEMBERSHIP_REQUIRED_OF_MUSIC_SESSION = "You must be a member of the music session to send invitations on behalf of it"
|
||||
JOIN_REQUEST_IS_NOT_FOR_RECEIVER_AND_MUSIC_SESSION = "You can only associate a join request with an invitation if that join request comes from the invited user and if it's for the same music session"
|
||||
|
||||
self.primary_key = 'id'
|
||||
belongs_to :sender, :inverse_of => :sent_invitations, :class_name => "JamRuby::User", :foreign_key => "sender_id"
|
||||
belongs_to :receiver, :inverse_of => :received_invitations, :class_name => "JamRuby::User", :foreign_key => "receiver_id"
|
||||
belongs_to :music_session, :inverse_of => :invitations, :class_name => "JamRuby::MusicSession"
|
||||
belongs_to :join_request, :inverse_of => :invitations, :class_name => "JamRuby::JoinRequest"
|
||||
|
||||
validates :sender, :presence => true
|
||||
validates :receiver, :presence => true
|
||||
validates :music_session, :presence => true
|
||||
|
||||
validate :require_sender_in_music_session, :require_are_friends_or_requested_to_join
|
||||
|
||||
private
|
||||
|
||||
def require_sender_in_music_session
|
||||
unless music_session.users.exists? sender
|
||||
errors.add(:music_session, MEMBERSHIP_REQUIRED_OF_MUSIC_SESSION)
|
||||
end
|
||||
end
|
||||
|
||||
def require_are_friends_or_requested_to_join
|
||||
if !join_request.nil? && (join_request.user != receiver || join_request.music_session != music_session)
|
||||
errors.add(:join_request, JOIN_REQUEST_IS_NOT_FOR_RECEIVER_AND_MUSIC_SESSION )
|
||||
elsif join_request.nil?
|
||||
# we only check for friendship requirement if this was not in response to a join_request
|
||||
unless receiver.friends.exists? sender
|
||||
errors.add(:receiver, FRIENDSHIP_REQUIRED_VALIDATION_ERROR)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
module JamRuby
|
||||
class InvitedUser < ActiveRecord::Base
|
||||
|
||||
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
|
||||
|
||||
attr_accessible :email, :sender_id, :autofriend, :note
|
||||
attr_accessor :accepted_twice
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
### Who sent this invitatio?
|
||||
# either admin_sender or user_sender is not null. If an administrator sends the invitation, then
|
||||
belongs_to :sender , :inverse_of => :invited_users, :class_name => "JamRuby::User", :foreign_key => "sender_id"
|
||||
|
||||
# who is the invitation sent to?
|
||||
validates :email, :presence => true, format: {with: VALID_EMAIL_REGEX}
|
||||
validates :autofriend, :inclusion => {:in => [nil, true, false]}
|
||||
validates :invitation_code, :presence => true
|
||||
validates :note, length: {maximum: 400}, no_profanity: true # 400 == arbitrary.
|
||||
|
||||
validate :valid_personalized_invitation
|
||||
validate :not_accepted_twice
|
||||
validate :can_invite?
|
||||
|
||||
# ensure invitation code is always created
|
||||
before_validation(:on => :create) do
|
||||
self.invitation_code = SecureRandom.urlsafe_base64 if self.invitation_code.nil?
|
||||
self.sender_id = nil if self.sender_id.blank? # this coercion was done just to make activeadmin work
|
||||
end
|
||||
|
||||
def self.index(user)
|
||||
return InvitedUser.where(:sender_id => user).order(:updated_at)
|
||||
end
|
||||
|
||||
def sender_display_name
|
||||
return sender.name
|
||||
end
|
||||
|
||||
def accept!
|
||||
if self.accepted
|
||||
accepted_twice = true
|
||||
end
|
||||
|
||||
self.accepted = true
|
||||
end
|
||||
|
||||
def invited_by_administrator?
|
||||
sender.nil? || sender.admin # a nil sender can only be created by someone using jam-admin
|
||||
end
|
||||
private
|
||||
|
||||
def can_invite?
|
||||
errors.add(:sender, "can not invite others") if !invited_by_administrator? && !sender.can_invite?
|
||||
end
|
||||
|
||||
def valid_personalized_invitation
|
||||
errors.add(:autofriend, "must be true if sender is specified") if autofriend && sender.nil?
|
||||
end
|
||||
|
||||
def not_accepted_twice
|
||||
errors.add(:accepted, "you can only accept an invitation once") if accepted_twice
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
module JamRuby
|
||||
class InvitedUserObserver < ActiveRecord::Observer
|
||||
|
||||
observe JamRuby::InvitedUser
|
||||
|
||||
def after_create(invited_user)
|
||||
if invited_user.sender.nil?
|
||||
InvitedUserMailer.welcome_betauser(invited_user).deliver
|
||||
else
|
||||
InvitedUserMailer.friend_invitation(invited_user).deliver
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
module JamRuby
|
||||
class JoinRequest < ActiveRecord::Base
|
||||
|
||||
REQUESTOR_MUST_BE_A_MUSICIAN = "requestor must be a musician"
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
belongs_to :user, :class_name => "JamRuby::User"
|
||||
belongs_to :music_session, :class_name => "JamRuby::MusicSession"
|
||||
has_many :invitations, :inverse_of => :join_request, :class_name => "JamRuby::Invitation"
|
||||
|
||||
validates :user, :presence => true
|
||||
validates :music_session, :presence => true
|
||||
validates :text, presence: false, no_profanity: true, length: {maximum: 140} # arbitrary decision of 140. the database is at 2000 max on this field
|
||||
|
||||
validates_uniqueness_of :user_id, :scope => :music_session_id
|
||||
|
||||
validate :requestor_is_musician
|
||||
|
||||
# list all paginations for the current user
|
||||
def self.index(current_user)
|
||||
# TODO pagination
|
||||
return JoinRequest.where("join_requests.user_id = '#{current_user.id}'").order('join_requests.created_at DESC')
|
||||
end
|
||||
|
||||
def requestor_is_musician
|
||||
unless user.musician?
|
||||
errors.add(:user, REQUESTOR_MUST_BE_A_MUSICIAN)
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
return "#{self.user.to_s}:#{self.music_session.to_s}"
|
||||
end
|
||||
|
||||
|
||||
# permissions:
|
||||
# only the creator of the join request can do a get
|
||||
# or a member of the music_session that the join_request is designated for
|
||||
def self.show(id, user)
|
||||
return JoinRequest.find(id, :conditions => ["user_id = ? OR music_session_id IN (select music_session_id from connections WHERE user_id = ?)", user.id, user.id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
module JamRuby
|
||||
class MaxMindGeo < ActiveRecord::Base
|
||||
|
||||
self.table_name = 'max_mind_geo'
|
||||
|
||||
|
||||
def self.import_from_max_mind(file)
|
||||
# File Geo-124
|
||||
# Format:
|
||||
# startIpNum,endIpNum,country,region,city,postalCode,latitude,longitude,dmaCode,areaCode
|
||||
|
||||
MaxMindGeo.transaction do
|
||||
MaxMindGeo.delete_all
|
||||
File.open(file, 'r:ISO-8859-1') do |io|
|
||||
MaxMindGeo.pg_copy_from io, :map => { 'startIpNum' => 'ip_bottom', 'endIpNum' => 'ip_top', 'country' => 'country', 'region' => 'region', 'city' => 'city'}, :columns => [:startIpNum, :endIpNum, :country, :region, :city] do |row|
|
||||
row[0] = ip_address_to_int(row[0])
|
||||
row[1] = ip_address_to_int(row[1])
|
||||
row.delete_at(5)
|
||||
row.delete_at(5)
|
||||
row.delete_at(5)
|
||||
row.delete_at(5)
|
||||
row.delete_at(5)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Make an IP address fit in a signed int. Just divide it by 2, as the least significant part
|
||||
# just can't possibly matter. We can verify this if needed. My guess is the entire bottom octet is
|
||||
# actually irrelevant
|
||||
def self.ip_address_to_int(ip)
|
||||
ip.split('.').inject(0) {|total,value| (total << 8 ) + value.to_i} / 2
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
module JamRuby
|
||||
class MaxMindIsp < ActiveRecord::Base
|
||||
|
||||
self.table_name = 'max_mind_isp'
|
||||
|
||||
def self.import_from_max_mind(file)
|
||||
|
||||
# File Geo-142
|
||||
# Format:
|
||||
# "beginIp","endIp","countryCode","ISP"
|
||||
|
||||
MaxMindIsp.transaction do
|
||||
MaxMindIsp.delete_all
|
||||
File.open(file, 'r:ISO-8859-1') do |io|
|
||||
io.gets # eat the copyright line. gah, why do they have that in their file??
|
||||
MaxMindIsp.pg_copy_from io, :map => { 'beginIp' => 'ip_bottom', 'endIp' => 'ip_top', 'countryCode' => 'country', 'ISP' => 'isp'}, :columns => [:beginIp, :endIp, :countryCode, :ISP] do |row|
|
||||
row[0] = ip_address_to_int(strip_quotes(row[0]))
|
||||
row[1] = ip_address_to_int(strip_quotes(row[1]))
|
||||
row[2] = row[2]
|
||||
row[3] = row[3..-1].join(',') # this is because the parser just cuts on any ',' and ignores double quotes. essentially postgres-copy isn't a great csv parser -- or I need to configure it better
|
||||
while row.length > 4
|
||||
row.delete_at(4)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Make an IP address fit in a signed int. Just divide it by 2, as the least significant part
|
||||
# just can't possibly matter. We can verify this if needed. My guess is the entire bottom octet is
|
||||
# actually irrelevant
|
||||
def self.ip_address_to_int(ip)
|
||||
ip.split('.').inject(0) {|total,value| (total << 8 ) + value.to_i} / 2
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.strip_quotes str
|
||||
return nil if str.nil?
|
||||
|
||||
if str.chr == '"'
|
||||
str = str[1..-1]
|
||||
end
|
||||
|
||||
if str.rindex('"') == str.length - 1
|
||||
str = str.chop
|
||||
end
|
||||
|
||||
return str
|
||||
end
|
||||
|
||||
def self.escape str
|
||||
str.gsub(/\"/, '""')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
module JamRuby
|
||||
class Mix < ActiveRecord::Base
|
||||
MAX_MIX_TIME = 7200 # 2 hours
|
||||
|
||||
before_destroy :delete_s3_files
|
||||
|
||||
self.primary_key = 'id'
|
||||
belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :mixes
|
||||
|
||||
def self.schedule(recording, manifest)
|
||||
raise if recording.nil?
|
||||
|
||||
mix = Mix.new
|
||||
mix.recording = recording
|
||||
mix.manifest = manifest
|
||||
mix.save
|
||||
|
||||
mix
|
||||
end
|
||||
|
||||
def self.next(mix_server)
|
||||
# First check if there are any mixes started so long ago that we want to re-run them
|
||||
Mix.where("completed_at IS NULL AND started_at < ?", Time.now - MAX_MIX_TIME).each do |mix|
|
||||
# FIXME: This should probably throw some kind of log, since it means something went wrong
|
||||
mix.started_at = nil
|
||||
mix.mix_server = nil
|
||||
mix.save
|
||||
end
|
||||
|
||||
mix = Mix.where(:started_at => nil).limit(1).first
|
||||
return nil if mix.nil?
|
||||
|
||||
mix.started_at = Time.now
|
||||
mix.mix_server = mix_server
|
||||
mix.save
|
||||
|
||||
mix
|
||||
end
|
||||
|
||||
def finish(length, md5)
|
||||
self.completed_at = Time.now
|
||||
self.length = length
|
||||
self.md5 = md5
|
||||
save
|
||||
end
|
||||
|
||||
def s3_url
|
||||
S3Manager.s3_url(hashed_filename)
|
||||
end
|
||||
|
||||
def url
|
||||
S3Manager.url(hashed_filename)
|
||||
end
|
||||
|
||||
def is_completed
|
||||
!completed_at.nil?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def delete_s3_files
|
||||
S3Manager.delete(hashed_filename)
|
||||
end
|
||||
|
||||
def hashed_filename
|
||||
S3Manager.hashed_filename('mix', id)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
module JamRuby
|
||||
class MusicSession < ActiveRecord::Base
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
attr_accessor :legal_terms, :skip_genre_validation
|
||||
attr_accessible :creator, :description, :musician_access, :approval_required, :fan_chat, :fan_access, :genres
|
||||
|
||||
belongs_to :creator, :inverse_of => :music_sessions, :class_name => "JamRuby::User", :foreign_key => "user_id"
|
||||
|
||||
has_many :connections, :class_name => "JamRuby::Connection"
|
||||
has_many :users, :through => :connections, :class_name => "JamRuby::User"
|
||||
has_and_belongs_to_many :genres, :class_name => "::JamRuby::Genre", :join_table => "genres_music_sessions"
|
||||
has_many :join_requests, :foreign_key => "music_session_id", :inverse_of => :music_session, :class_name => "JamRuby::JoinRequest"
|
||||
has_many :invitations, :foreign_key => "music_session_id", :inverse_of => :music_session, :class_name => "JamRuby::Invitation"
|
||||
has_many :invited_musicians, :through => :invitations, :class_name => "JamRuby::User", :foreign_key => "receiver_id", :source => :receiver
|
||||
|
||||
has_many :fan_invitations, :foreign_key => "music_session_id", :inverse_of => :music_session, :class_name => "JamRuby::FanInvitation"
|
||||
has_many :invited_fans, :through => :fan_invitations, :class_name => "JamRuby::User", :foreign_key => "receiver_id", :source => :receiver
|
||||
has_one :recording, :class_name => "JamRuby::Recording", :inverse_of => :music_session
|
||||
|
||||
belongs_to :band, :inverse_of => :music_sessions, :class_name => "JamRuby::Band", :foreign_key => "band_id"
|
||||
|
||||
after_save :require_at_least_one_genre, :limit_max_genres
|
||||
|
||||
after_destroy do |obj|
|
||||
JamRuby::MusicSessionHistory.removed_music_session(obj.user_id, obj.id)
|
||||
end
|
||||
|
||||
validates :description, :presence => true, :no_profanity => true
|
||||
validates :fan_chat, :inclusion => {:in => [true, false]}
|
||||
validates :fan_access, :inclusion => {:in => [true, false]}
|
||||
validates :approval_required, :inclusion => {:in => [true, false]}
|
||||
validates :musician_access, :inclusion => {:in => [true, false]}
|
||||
validates :legal_terms, :inclusion => {:in => [true]}, :on => :create
|
||||
validates :creator, :presence => true
|
||||
validate :creator_is_musician
|
||||
|
||||
def creator_is_musician
|
||||
unless creator.musician?
|
||||
errors.add(:creator, "creator must be a musician")
|
||||
end
|
||||
end
|
||||
|
||||
# This is a little confusing. You can specify *BOTH* friends_only and my_bands_only to be true
|
||||
# If so, then it's an OR condition. If both are false, you can get sessions with anyone.
|
||||
def self.index(current_user, participants = nil, genres = nil, friends_only = false, my_bands_only = false, keyword = nil)
|
||||
|
||||
query = MusicSession
|
||||
.joins(
|
||||
%Q{
|
||||
LEFT OUTER JOIN
|
||||
connections
|
||||
ON
|
||||
music_sessions.id = connections.music_session_id
|
||||
}
|
||||
)
|
||||
.joins(
|
||||
%Q{
|
||||
LEFT OUTER JOIN
|
||||
friendships
|
||||
ON
|
||||
connections.user_id = friendships.user_id
|
||||
AND
|
||||
friendships.friend_id = '#{current_user.id}'
|
||||
}
|
||||
)
|
||||
.joins(
|
||||
%Q{
|
||||
LEFT OUTER JOIN
|
||||
invitations
|
||||
ON
|
||||
invitations.music_session_id = music_sessions.id
|
||||
AND
|
||||
invitations.receiver_id = '#{current_user.id}'
|
||||
}
|
||||
)
|
||||
.group(
|
||||
%Q{
|
||||
music_sessions.id
|
||||
}
|
||||
)
|
||||
.order(
|
||||
%Q{
|
||||
SUM(CASE WHEN invitations.id IS NULL THEN 0 ELSE 1 END) DESC,
|
||||
SUM(CASE WHEN friendships.user_id IS NULL THEN 0 ELSE 1 END) DESC,
|
||||
music_sessions.created_at DESC
|
||||
}
|
||||
)
|
||||
.where(
|
||||
%Q{
|
||||
musician_access = true
|
||||
OR
|
||||
invitations.id IS NOT NULL
|
||||
}
|
||||
)
|
||||
|
||||
query = query.where("music_sessions.description like '%#{keyword}%'") unless keyword.nil?
|
||||
query = query.where("connections.user_id" => participants.split(',')) unless participants.nil?
|
||||
query = query.joins(:genres).where("genres.id" => genres.split(',')) unless genres.nil?
|
||||
|
||||
if my_bands_only
|
||||
query = query.joins(
|
||||
%Q{
|
||||
LEFT OUTER JOIN
|
||||
bands_musicians
|
||||
ON
|
||||
bands_musicians.user_id = '#{current_user.id}'
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
if my_bands_only || friends_only
|
||||
query = query.where(
|
||||
%Q{
|
||||
#{friends_only ? "friendships.user_id IS NOT NULL" : "false"}
|
||||
OR
|
||||
#{my_bands_only ? "bands_musicians.band_id = music_sessions.band_id" : "false"}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
return query
|
||||
end
|
||||
|
||||
# Verifies that the specified user can join this music session
|
||||
def can_join? user, as_musician
|
||||
if as_musician
|
||||
if !user.musician
|
||||
return false # "a fan can not join a music session as a musician"
|
||||
raise PermissionError, "a fan can not join a music session as a musician"
|
||||
end
|
||||
|
||||
if self.musician_access
|
||||
if self.approval_required
|
||||
return self.invited_musicians.exists?(user)
|
||||
else
|
||||
return true
|
||||
end
|
||||
|
||||
else
|
||||
# the creator can always join, and the invited users can join
|
||||
return self.creator == user || self.invited_musicians.exists?(user)
|
||||
end
|
||||
else
|
||||
# it's a fan, and the only way a fan can join is if fan_access is true
|
||||
return self.fan_access
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Verifies that the specified user can see this music session
|
||||
def can_see? user
|
||||
if self.musician_access
|
||||
return true
|
||||
else
|
||||
# the creator can always see, and the invited users can see it too
|
||||
return self.creator == user || self.invited_musicians.exists?(user)
|
||||
end
|
||||
end
|
||||
|
||||
# Verifies that the specified user can delete this music session
|
||||
def can_delete? user
|
||||
# the creator can delete
|
||||
return self.creator == user
|
||||
end
|
||||
|
||||
def access? user
|
||||
return self.users.exists? user
|
||||
end
|
||||
|
||||
def to_s
|
||||
return description
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_at_least_one_genre
|
||||
unless skip_genre_validation
|
||||
if self.genres.count < Limits::MIN_GENRES_PER_RECORDING
|
||||
errors.add(:genres, ValidationMessages::GENRE_MINIMUM_NOT_MET)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def limit_max_genres
|
||||
unless skip_genre_validation
|
||||
if self.genres.count > Limits::MAX_GENRES_PER_RECORDING
|
||||
errors.add(:genres, ValidationMessages::GENRE_LIMIT_EXCEEDED)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
module JamRuby
|
||||
class MusicSessionHistory < ActiveRecord::Base
|
||||
|
||||
self.table_name = "music_sessions_history"
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
# for some reason the association is not working, i suspect has to do with the foreign key
|
||||
def music_session_user_histories
|
||||
@msuh ||= JamRuby::MusicSessionUserHistory
|
||||
.where(:music_session_id => self.music_session_id)
|
||||
.order('created_at DESC')
|
||||
end
|
||||
# has_many(:music_session_user_histories,
|
||||
# :class_name => "JamRuby::MusicSessionUserHistory",
|
||||
# :foreign_key => :music_session_id,
|
||||
# :order => 'created_at DESC',
|
||||
# :inverse_of => :music_session_history)
|
||||
|
||||
has_one(:perf_data,
|
||||
:class_name => "JamRuby::MusicSessionPerfData",
|
||||
:foreign_key => "music_session_id",
|
||||
:inverse_of => :music_session)
|
||||
|
||||
belongs_to(:user,
|
||||
:class_name => 'JamRuby::User',
|
||||
:foreign_key => :user_id,
|
||||
:inverse_of => :music_session_histories)
|
||||
|
||||
belongs_to(:band,
|
||||
:class_name => 'JamRuby::Band',
|
||||
:foreign_key => :band_id,
|
||||
:inverse_of => :music_session_history)
|
||||
|
||||
GENRE_SEPARATOR = '|'
|
||||
|
||||
def self.index(current_user, user_id, band_id = nil, genre = nil)
|
||||
hide_private = false
|
||||
if current_user.id != user_id
|
||||
hide_private = false # TODO: change to true once public flag exists
|
||||
end
|
||||
|
||||
query = MusicSessionHistory
|
||||
.joins(
|
||||
%Q{
|
||||
LEFT OUTER JOIN
|
||||
music_sessions_user_history
|
||||
ON
|
||||
music_sessions_history.music_session_id = music_sessions_user_history.music_session_id
|
||||
}
|
||||
)
|
||||
.where(
|
||||
%Q{
|
||||
music_sessions_history.user_id = '#{user_id}'
|
||||
}
|
||||
)
|
||||
|
||||
#query = query.where("public = false") unless !hide_private
|
||||
query = query.where("music_sessions_history.band_id = '#{band_id}") unless band_id.nil?
|
||||
query = query.where("music_sessions_history.genres like '%#{genre}%'") unless genre.nil?
|
||||
return query
|
||||
end
|
||||
|
||||
def unique_users
|
||||
User
|
||||
.joins(:music_session_user_histories)
|
||||
.group("users.id")
|
||||
.order("users.id")
|
||||
.where(%Q{ music_sessions_user_history.music_session_id = '#{music_session_id}'})
|
||||
end
|
||||
|
||||
def self.save(music_session)
|
||||
session_history = MusicSessionHistory.find_by_music_session_id(music_session.id)
|
||||
|
||||
if session_history.nil?
|
||||
session_history = MusicSessionHistory.new()
|
||||
end
|
||||
|
||||
session_history.music_session_id = music_session.id
|
||||
session_history.description = music_session.description unless music_session.description.nil?
|
||||
session_history.user_id = music_session.creator.id
|
||||
session_history.band_id = music_session.band.id unless music_session.band.nil?
|
||||
session_history.genres = music_session.genres.map { |g| g.id }.join GENRE_SEPARATOR
|
||||
session_history.save!
|
||||
end
|
||||
|
||||
def self.removed_music_session(user_id, session_id)
|
||||
hist = self
|
||||
.where(:user_id => user_id)
|
||||
.where(:music_session_id => session_id)
|
||||
.limit(1)
|
||||
.first
|
||||
hist.update_attribute(:session_removed_at, Time.now) if hist
|
||||
JamRuby::MusicSessionUserHistory.removed_music_session(user_id, session_id)
|
||||
end
|
||||
|
||||
def duration_minutes
|
||||
end_time = self.session_removed_at || Time.now
|
||||
(end_time - self.created_at) / 60.0
|
||||
end
|
||||
|
||||
def perf_uri
|
||||
self.perf_data.try(:uri)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
require 'securerandom'
|
||||
|
||||
module JamRuby
|
||||
class MusicSessionPerfData < ActiveRecord::Base
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
attr_accessible :uri
|
||||
|
||||
belongs_to(:music_session,
|
||||
:class_name => "JamRuby::MusicSessionHistory",
|
||||
:foreign_key => :music_session_id,
|
||||
:inverse_of => :perf_data)
|
||||
|
||||
# mount_uploader :uri, PerfDataUploader
|
||||
|
||||
validates :music_session, :presence => true
|
||||
validates :client_id, :presence => true
|
||||
validates :uri, :presence => true
|
||||
|
||||
before_validation(:on => :create) do
|
||||
self.created_at ||= Time.now
|
||||
self.id = SecureRandom.uuid
|
||||
self.uri = "perf_data/#{self.music_session_id}/#{self.client_id}-#{self.created_at.to_i}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
module JamRuby
|
||||
class MusicSessionUserHistory < ActiveRecord::Base
|
||||
|
||||
self.table_name = "music_sessions_user_history"
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
belongs_to(:user,
|
||||
:class_name => "JamRuby::User",
|
||||
:foreign_key => "user_id",
|
||||
:inverse_of => :music_session_user_histories)
|
||||
|
||||
# for some reason the association is not working, i suspect has to do with the foreign key
|
||||
def music_session_history
|
||||
@msh ||= JamRuby::MusicSessionHistory
|
||||
.where(:music_session_id => self.music_session_id)
|
||||
.limit(1)
|
||||
.first
|
||||
end
|
||||
# belongs_to(:music_session_history,
|
||||
# :class_name => "JamRuby::MusicSessionHistory",
|
||||
# :foreign_key => :music_session_id,
|
||||
# :inverse_of => :music_session_user_histories)
|
||||
|
||||
def self.save(music_session_id, user_id, client_id)
|
||||
session_user_history = MusicSessionUserHistory.new()
|
||||
session_user_history.music_session_id = music_session_id
|
||||
session_user_history.user_id = user_id
|
||||
session_user_history.client_id = client_id
|
||||
session_user_history.save
|
||||
end
|
||||
|
||||
def user_email
|
||||
self.user ? self.user.email : '<user deleted>'
|
||||
end
|
||||
|
||||
def duration_minutes
|
||||
end_time = self.session_removed_at || Time.now
|
||||
(end_time - self.created_at) / 60.0
|
||||
end
|
||||
|
||||
def self.removed_music_session(user_id, session_id)
|
||||
hist = self
|
||||
.where(:user_id => user_id)
|
||||
.where(:music_session_id => session_id)
|
||||
.limit(1)
|
||||
.first
|
||||
hist.update_attribute(:session_removed_at, Time.now) if hist
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
module JamRuby
|
||||
class MusicianInstrument < ActiveRecord::Base
|
||||
|
||||
self.table_name = "musicians_instruments"
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
# ensure most proficient, highest priority
|
||||
default_scope order('proficiency_level DESC, priority ASC')
|
||||
|
||||
belongs_to :user, :class_name => "JamRuby::User"
|
||||
belongs_to :instrument, :class_name => "JamRuby::Instrument"
|
||||
|
||||
def description
|
||||
@description = self.instrument.description
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,280 @@
|
|||
module JamRuby
|
||||
class Notification < ActiveRecord::Base
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
default_scope order('created_at DESC')
|
||||
|
||||
belongs_to :target_user, :class_name => "JamRuby::User", :foreign_key => "target_user_id"
|
||||
belongs_to :source_user, :class_name => "JamRuby::User", :foreign_key => "source_user_id"
|
||||
belongs_to :band, :class_name => "JamRuby::Band", :foreign_key => "band_id"
|
||||
belongs_to :session, :class_name => "JamRuby::MusicSession", :foreign_key => "session_id"
|
||||
belongs_to :recording, :class_name => "JamRuby::Recording", :foreign_key => "recording_id"
|
||||
|
||||
def index(user_id)
|
||||
results = Notification.where(:target_user_id => user_id).limit(50)
|
||||
return results
|
||||
end
|
||||
|
||||
def photo_url
|
||||
unless self.source_user.nil?
|
||||
self.source_user.photo_url
|
||||
end
|
||||
end
|
||||
|
||||
# used for persisted notifications
|
||||
def formatted_msg
|
||||
target_user, source_user, band, session, recording, invitation, join_request = nil
|
||||
|
||||
unless self.target_user_id.nil?
|
||||
target_user = User.find(self.target_user_id)
|
||||
end
|
||||
|
||||
unless self.source_user_id.nil?
|
||||
source_user = User.find(self.source_user_id)
|
||||
end
|
||||
|
||||
unless self.band_id.nil?
|
||||
band = Band.find(self.band_id)
|
||||
end
|
||||
|
||||
unless self.session_id.nil?
|
||||
session = MusicSession.find(self.session_id)
|
||||
end
|
||||
|
||||
unless self.recording_id.nil?
|
||||
recording = Recording.find(self.recording_id)
|
||||
end
|
||||
|
||||
unless self.invitation_id.nil?
|
||||
invitation = Invitation.find(self.invitation_id)
|
||||
end
|
||||
|
||||
unless self.join_request_id.nil?
|
||||
join_request = JoinRequest.find(self.join_request_id)
|
||||
end
|
||||
|
||||
return self.class.format_msg(self.description, source_user)
|
||||
end
|
||||
|
||||
# TODO: MAKE ALL METHODS BELOW ASYNC SO THE CLIENT DOESN'T BLOCK ON NOTIFICATION LOGIC
|
||||
# TODO: ADD TESTS FOR THIS CLASS
|
||||
|
||||
class << self
|
||||
|
||||
@@mq_router = MQRouter.new
|
||||
@@message_factory = MessageFactory.new
|
||||
|
||||
def delete_all(session_id)
|
||||
Notification.delete_all "(session_id = '#{session_id}')"
|
||||
end
|
||||
|
||||
################### HELPERS ###################
|
||||
def retrieve_friends(connection, user_id)
|
||||
friend_ids = []
|
||||
connection.exec("SELECT f.friend_id as friend_id FROM friendships f WHERE f.user_id = $1", [user_id]) do |friend_results|
|
||||
friend_results.each do |friend_result|
|
||||
friend_ids.push(friend_result['friend_id'])
|
||||
end
|
||||
end
|
||||
return friend_ids
|
||||
end
|
||||
|
||||
def retrieve_followers(connection, user_id)
|
||||
follower_ids = []
|
||||
connection.exec("SELECT uf.follower_id as friend_id FROM users_followers uf WHERE uf.user_id = $1", [user_id]) do |follower_results|
|
||||
follower_results.each do |follower_result|
|
||||
follower_ids.push(follower_result['follower_id'])
|
||||
end
|
||||
end
|
||||
return follower_ids
|
||||
end
|
||||
|
||||
def retrieve_friends_and_followers(connection, user_id)
|
||||
ids = retrieve_friends(connection, user_id)
|
||||
ids.concat(retrieve_followers(connection, user_id))
|
||||
ids.uniq! {|id| id}
|
||||
return ids
|
||||
end
|
||||
|
||||
def retrieve_friends_and_followers_not_in_session(connection, user_id, session_id)
|
||||
ids = retrieve_friends_and_followers(connection, user_id)
|
||||
connection.exec("SELECT c.user_id as musician_id FROM connections c WHERE c.music_session_id = $1", [session_id]) do |musicians|
|
||||
musicians.each do |musician_result|
|
||||
# remove users who are in the session
|
||||
ids.reject! {|item| item == musician_result['musician_id']}
|
||||
end
|
||||
end
|
||||
return ids
|
||||
end
|
||||
|
||||
def format_msg(description, user)
|
||||
case description
|
||||
when NotificationTypes::FRIEND_UPDATE
|
||||
return "#{user.name} is now "
|
||||
|
||||
when NotificationTypes::FRIEND_REQUEST
|
||||
return "#{user.name} has sent you a friend request."
|
||||
|
||||
when NotificationTypes::FRIEND_REQUEST_ACCEPTED
|
||||
return "#{user.name} has accepted your friend request."
|
||||
|
||||
when NotificationTypes::FRIEND_SESSION_JOIN
|
||||
return "#{user.name} has joined the session."
|
||||
|
||||
when NotificationTypes::MUSICIAN_SESSION_JOIN
|
||||
return "#{user.name} has joined the session."
|
||||
|
||||
when NotificationTypes::MUSICIAN_SESSION_DEPART
|
||||
return "#{user.name} has left the session."
|
||||
|
||||
# when "social_media_friend_joined"
|
||||
# when "join_request_approved"
|
||||
# when "join_request_rejected"
|
||||
# when "session_invitation"
|
||||
# when "band_invitation"
|
||||
# when "band_invitation_accepted"
|
||||
# when "recording_available"
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
|
||||
################### FRIEND UPDATE ###################
|
||||
def send_friend_update(user_id, online, connection)
|
||||
|
||||
# (1) get all of this user's friends
|
||||
friend_ids = retrieve_friends(connection, user_id)
|
||||
|
||||
if friend_ids.length > 0
|
||||
user = User.find(user_id)
|
||||
|
||||
# (2) create notification
|
||||
online_msg = online ? "online." : "offline."
|
||||
notification_msg = format_msg(NotificationTypes::FRIEND_UPDATE, user) + online_msg
|
||||
msg = @@message_factory.friend_update(user_id, user.name, user.photo_url, online, notification_msg)
|
||||
|
||||
# (3) send notification
|
||||
@@mq_router.publish_to_friends(friend_ids, msg, user_id)
|
||||
end
|
||||
end
|
||||
|
||||
################### FRIEND REQUEST ###################
|
||||
def send_friend_request(friend_request_id, user_id, friend_id)
|
||||
user = User.find(user_id)
|
||||
|
||||
# (1) save to database
|
||||
notification = Notification.new
|
||||
notification.description = NotificationTypes::FRIEND_REQUEST
|
||||
notification.source_user_id = user_id
|
||||
notification.target_user_id = friend_id
|
||||
notification.friend_request_id = friend_request_id
|
||||
notification.save
|
||||
|
||||
# (2) create notification
|
||||
notification_msg = format_msg(NotificationTypes::FRIEND_REQUEST, user)
|
||||
msg = @@message_factory.friend_request(friend_request_id, user_id, user.name, user.photo_url, friend_id, notification_msg, notification.id, notification.created_at.to_s)
|
||||
|
||||
# (3) send notification
|
||||
@@mq_router.publish_to_user(friend_id, msg)
|
||||
end
|
||||
|
||||
############### FRIEND REQUEST ACCEPTED ###############
|
||||
def send_friend_request_accepted(user_id, friend_id)
|
||||
friend = User.find(friend_id)
|
||||
|
||||
# (1) save to database
|
||||
notification = Notification.new
|
||||
notification.description = NotificationTypes::FRIEND_REQUEST_ACCEPTED
|
||||
notification.source_user_id = friend_id
|
||||
notification.target_user_id = user_id
|
||||
notification.save
|
||||
|
||||
# (2) create notification
|
||||
notification_msg = format_msg(NotificationTypes::FRIEND_REQUEST_ACCEPTED, friend)
|
||||
msg = @@message_factory.friend_request_accepted(friend_id, friend.name, friend.photo_url, user_id, notification_msg, notification.id, notification.created_at.to_s)
|
||||
|
||||
# (3) send notification
|
||||
@@mq_router.publish_to_user(user_id, msg)
|
||||
end
|
||||
|
||||
################## SESSION INVITATION ##################
|
||||
def send_session_invitation(receiver_id, invitation_id)
|
||||
|
||||
# (1) save to database
|
||||
notification = Notification.new
|
||||
notification.description = NotificationTypes::SESSION_INVITATION
|
||||
notification.target_user_id = receiver_id
|
||||
notification.save
|
||||
|
||||
# (2) create notification
|
||||
msg = @@message_factory.session_invitation(receiver_id, invitation_id)
|
||||
|
||||
# (3) send notification
|
||||
@@mq_router.publish_to_user(receiver_id, msg)
|
||||
end
|
||||
|
||||
def send_musician_session_join(music_session, connection, user)
|
||||
|
||||
# (1) create notification
|
||||
msg = @@message_factory.musician_session_join(music_session.id, user.id, user.name, user.photo_url)
|
||||
|
||||
# (2) send notification
|
||||
@@mq_router.server_publish_to_session(music_session, msg, sender = {:client_id => connection.client_id})
|
||||
end
|
||||
|
||||
def send_musician_session_depart(music_session, client_id, user)
|
||||
|
||||
# (1) create notification
|
||||
msg = @@message_factory.musician_session_depart(music_session.id, user.id, user.name, user.photo_url)
|
||||
|
||||
# (2) send notification
|
||||
@@mq_router.server_publish_to_session(music_session, msg, sender = {:client_id => client_id})
|
||||
end
|
||||
|
||||
def send_musician_session_fresh(music_session, client_id, user)
|
||||
|
||||
# (1) create notification
|
||||
msg = @@message_factory.musician_session_fresh(music_session.id, user.id, user.name, user.photo_url)
|
||||
|
||||
# (2) send notification
|
||||
@@mq_router.server_publish_to_session(music_session, msg, sender = {:client_id => client_id})
|
||||
end
|
||||
|
||||
def send_musician_session_stale(music_session, client_id, user)
|
||||
|
||||
# (1) create notification
|
||||
msg = @@message_factory.musician_session_stale(music_session.id, user.id, user.name, user.photo_url)
|
||||
|
||||
# (2) send notification
|
||||
@@mq_router.server_publish_to_session(music_session, msg, sender = {:client_id => client_id})
|
||||
end
|
||||
|
||||
def send_friend_session_join(db_conn, connection, user)
|
||||
ids = retrieve_friends_and_followers_not_in_session(db_conn, user.id, connection.music_session.id)
|
||||
|
||||
if ids.length > 0
|
||||
# (1) save to database
|
||||
|
||||
# (2) create notification
|
||||
msg = @@message_factory.friend_session_join(connection.music_session.id, user.id, user.name, user.photo_url)
|
||||
|
||||
# (3) send notification
|
||||
@@mq_router.publish_to_friends(ids, msg, sender = {:client_id => connection.client_id})
|
||||
end
|
||||
end
|
||||
|
||||
def send_join_request(music_session, join_request, sender, text)
|
||||
|
||||
# (1) save to database
|
||||
|
||||
# (2) create notification
|
||||
msg = @@message_factory.join_request(music_session.id, join_request.id, sender.name, text)
|
||||
|
||||
# (3) send notification
|
||||
@@mq_router.server_publish_to_session(music_session, msg)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
module JamRuby
|
||||
class RecordedTrack < ActiveRecord::Base
|
||||
|
||||
self.table_name = "recorded_tracks"
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
SOUND = %w(mono stereo)
|
||||
|
||||
belongs_to :user, :class_name => "JamRuby::User", :inverse_of => :recorded_tracks
|
||||
belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :recorded_tracks
|
||||
belongs_to :instrument, :class_name => "JamRuby::Instrument"
|
||||
|
||||
validates :sound, :inclusion => {:in => SOUND}
|
||||
|
||||
before_destroy :delete_s3_files
|
||||
|
||||
# Copy an ephemeral track to create a saved one. Some fields are ok with defaults
|
||||
def self.create_from_track(track, recording)
|
||||
recorded_track = self.new
|
||||
recorded_track.recording = recording
|
||||
recorded_track.user = track.connection.user
|
||||
recorded_track.instrument = track.instrument
|
||||
recorded_track.sound = track.sound
|
||||
recorded_track.save
|
||||
recorded_track
|
||||
end
|
||||
|
||||
def upload_start(length, md5)
|
||||
self.upload_id = S3Manager.multipart_upload_start(hashed_filename)
|
||||
self.length = length
|
||||
self.md5 = md5
|
||||
save
|
||||
end
|
||||
|
||||
def upload_sign(content_md5)
|
||||
S3Manager.upload_sign(hashed_filename, content_md5, upload_id)
|
||||
end
|
||||
|
||||
def upload_part_complete(part)
|
||||
raise JamRuby::JamArgumentError unless part == next_part_to_upload
|
||||
self.next_part_to_upload = part + 1
|
||||
save
|
||||
end
|
||||
|
||||
def upload_complete
|
||||
S3Manager.multipart_upload_complete(upload_id)
|
||||
self.fully_uploaded = true
|
||||
save
|
||||
recording.upload_complete
|
||||
end
|
||||
|
||||
def url
|
||||
S3Manager.url(hashed_filename)
|
||||
end
|
||||
|
||||
def filename
|
||||
hashed_filename
|
||||
end
|
||||
|
||||
|
||||
# Format: "recording_#{recorded_track_id}"
|
||||
# File extension is irrelevant actually.
|
||||
def self.find_by_upload_filename(filename)
|
||||
matches = /^recording_([\w-]+)$/.match(filename)
|
||||
return nil unless matches && matches.length > 1
|
||||
RecordedTrack.find(matches[1])
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def delete_s3_files
|
||||
S3Manager.delete(hashed_filename)
|
||||
end
|
||||
|
||||
def hashed_filename
|
||||
S3Manager.hashed_filename('recorded_track', id)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,281 @@
|
|||
module JamRuby
|
||||
class Recording < ActiveRecord::Base
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :recording
|
||||
has_many :users, :through => :claimed_recordings, :class_name => "JamRuby::User"
|
||||
belongs_to :owner, :class_name => "JamRuby::User", :inverse_of => :owned_recordings
|
||||
belongs_to :band, :class_name => "JamRuby::Band", :inverse_of => :recordings
|
||||
belongs_to :music_session, :class_name => "JamRuby::MusicSession", :inverse_of => :recording
|
||||
has_many :mixes, :class_name => "JamRuby::Mix", :inverse_of => :recording
|
||||
has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :foreign_key => :recording_id
|
||||
|
||||
|
||||
# Start recording a session.
|
||||
def self.start(music_session_id, owner)
|
||||
|
||||
recording = nil
|
||||
|
||||
# Use a transaction and lock to avoid races.
|
||||
ActiveRecord::Base.transaction do
|
||||
music_session = MusicSession.find(music_session_id, :lock => true)
|
||||
|
||||
if music_session.nil?
|
||||
raise PermissionError, "the session has ended"
|
||||
end
|
||||
|
||||
unless music_session.recording.nil?
|
||||
raise PermissionError, "the session is already being recorded"
|
||||
end
|
||||
|
||||
recording = Recording.new
|
||||
recording.music_session = music_session
|
||||
recording.owner = owner
|
||||
|
||||
music_session.connections.each do |connection|
|
||||
# Note that we do NOT connect the recording to any users at this point.
|
||||
# That ONLY happens if a user clicks 'save'
|
||||
# recording.users << connection.user
|
||||
connection.tracks.each do |track|
|
||||
RecordedTrack.create_from_track(track, recording)
|
||||
end
|
||||
end
|
||||
|
||||
# Note that I believe this can be nil.
|
||||
recording.band = music_session.band
|
||||
recording.save
|
||||
|
||||
music_session.recording = recording
|
||||
music_session.save
|
||||
end
|
||||
|
||||
|
||||
# FIXME:
|
||||
# NEED TO SEND NOTIFICATION TO ALL USERS IN THE SESSION THAT RECORDING HAS STARTED HERE.
|
||||
# I'LL STUB IT A BIT. NOTE THAT I REDO THE FIND HERE BECAUSE I DON'T WANT TO SEND THESE
|
||||
# NOTIFICATIONS WHILE THE DB ROW IS LOCKED
|
||||
music_session = MusicSession.find(music_session_id)
|
||||
music_session.connections.each do |connection|
|
||||
# connection.notify_recording_has_started
|
||||
end
|
||||
|
||||
recording
|
||||
end
|
||||
|
||||
# Stop recording a session
|
||||
def stop
|
||||
# Use a transaction and lock to avoid races.
|
||||
ActiveRecord::Base.transaction do
|
||||
music_session = MusicSession.find(self.music_session_id, :lock => true)
|
||||
if music_session.nil?
|
||||
raise PermissionError, "the session has ended"
|
||||
end
|
||||
unless music_session.recording
|
||||
raise PermissionError, "the session is not currently being recorded"
|
||||
end
|
||||
music_session.recording = nil
|
||||
music_session.save
|
||||
end
|
||||
|
||||
self.duration = Time.now - created_at
|
||||
save
|
||||
end
|
||||
|
||||
|
||||
# Called when a user wants to "claim" a recording. To do this, the user must have been one of the tracks in the recording.
|
||||
def claim(user, name, genre, is_public, is_downloadable)
|
||||
if self.users.include?(user)
|
||||
raise PermissionError, "user already claimed this recording"
|
||||
end
|
||||
|
||||
unless self.recorded_tracks.find { |recorded_track| recorded_track.user == user }
|
||||
raise PermissionError, "user was not in this session"
|
||||
end
|
||||
|
||||
unless self.music_session.nil?
|
||||
raise PermissionError, "recording cannot be claimed while it is being recorded"
|
||||
end
|
||||
|
||||
if name.nil? || genre.nil? || is_public.nil? || is_downloadable.nil?
|
||||
raise PermissionError, "recording must have name, genre and flags"
|
||||
end
|
||||
|
||||
claimed_recording = ClaimedRecording.new
|
||||
claimed_recording.user = user
|
||||
claimed_recording.recording = self
|
||||
claimed_recording.name = name
|
||||
claimed_recording.genre = genre
|
||||
claimed_recording.is_public = is_public
|
||||
claimed_recording.is_downloadable = is_downloadable
|
||||
self.claimed_recordings << claimed_recording
|
||||
save
|
||||
|
||||
claimed_recording
|
||||
end
|
||||
|
||||
# Find out if all the tracks for this recording have been uploaded
|
||||
def uploaded?
|
||||
self.recorded_tracks.each do |recorded_track|
|
||||
return false unless recorded_track.fully_uploaded
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
# Discards this recording and schedules deletion of all files associated with it.
|
||||
def discard
|
||||
self.destroy
|
||||
end
|
||||
|
||||
# Returns the list of files the user needs to upload. This will only ever be recordings
|
||||
def self.upload_file_list(user)
|
||||
files = []
|
||||
User.joins(:recordings).joins(:recordings => :recorded_tracks)
|
||||
.where(%Q{ recordings.duration IS NOT NULL })
|
||||
.where("recorded_tracks.user_id = '#{user.id}'")
|
||||
.where(%Q{ recorded_tracks.fully_uploaded = FALSE }).each do |user|
|
||||
user.recordings.each.do |recording|
|
||||
recording.recorded_tracks.each do |recorded_track|
|
||||
files.push(
|
||||
{
|
||||
:type => "recorded_track",
|
||||
:id => recorded_track.id,
|
||||
:url => recorded_track.url # FIXME IS THIS RIGHT?
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
files
|
||||
end
|
||||
|
||||
# Returns the list of files this user should have synced to his computer, along with md5s and lengths
|
||||
def self.list(user)
|
||||
downloads = []
|
||||
|
||||
# That second join is important. It's saying join off of recordings, NOT user. If you take out the
|
||||
# ":recordings =>" part, you'll just get the recorded_tracks that I played. Very different!
|
||||
User.joins(:recordings).joins(:recordings => :recorded_tracks)
|
||||
.order(%Q{ recordings.created_at DESC })
|
||||
.where(%Q{ recorded_tracks.fully_uploaded = TRUE })
|
||||
.where(:id => user.id).each do |theuser|
|
||||
theuser.recordings.each do |recording|
|
||||
recording.recorded_tracks.each do |recorded_track|
|
||||
recorded_track = user.claimed_recordings.first.recording.recorded_tracks.first
|
||||
downloads.push(
|
||||
{
|
||||
:type => "recorded_track",
|
||||
:id => recorded_track.id,
|
||||
:length => recorded_track.length,
|
||||
:md5 => recorded_track.md5,
|
||||
:url => recorded_track.url
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
User.joins(:recordings).joins(:recordings => :mixes)
|
||||
.order(%Q{ recordings.created_at DESC })
|
||||
.where(%Q{ mixes.completed_at IS NOT NULL }).each do |theuser|
|
||||
theuser.recordings.each do |recording|
|
||||
recording.mixes.each do |mix|
|
||||
downloads.push(
|
||||
{
|
||||
:type => "mix",
|
||||
:id => mix.id,
|
||||
:length => mix.length,
|
||||
:md5 => mix.md5,
|
||||
:url => mix.url
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
uploads = []
|
||||
RecordedTrack
|
||||
.joins(:recording)
|
||||
.where(:user_id => user.id)
|
||||
.where(:fully_uploaded => false)
|
||||
.where("duration IS NOT NULL").each do |recorded_track|
|
||||
uploads.push(recorded_track.filename)
|
||||
end
|
||||
|
||||
{
|
||||
"downloads" => downloads,
|
||||
"uploads" => uploads
|
||||
}
|
||||
end
|
||||
|
||||
# Check to see if all files have been uploaded. If so, kick off a mix.
|
||||
def upload_complete
|
||||
# Don't allow multiple mixes for now.
|
||||
raise JamRuby::JamArgumentError unless self.mixes.length == 0
|
||||
|
||||
# FIXME: There's a possible race condition here. If two users complete
|
||||
# uploads at the same time, we'll schedule 2 mixes.
|
||||
recorded_tracks.each do |recorded_track|
|
||||
return unless recorded_track.fully_uploaded
|
||||
end
|
||||
|
||||
self.mixes << Mix.schedule(self, base_mix_manifest.to_json)
|
||||
|
||||
save
|
||||
end
|
||||
|
||||
=begin
|
||||
# This is no longer remotely right.
|
||||
def self.search(query, options = { :limit => 10 })
|
||||
|
||||
# only issue search if at least 2 characters are specified
|
||||
if query.nil? || query.length < 2
|
||||
return []
|
||||
end
|
||||
|
||||
# create 'anded' statement
|
||||
query = Search.create_tsquery(query)
|
||||
|
||||
if query.nil? || query.length == 0
|
||||
return []
|
||||
end
|
||||
|
||||
return Recording.where("description_tsv @@ to_tsquery('jamenglish', ?)", query).limit(options[:limit])
|
||||
end
|
||||
=end
|
||||
|
||||
def base_mix_manifest
|
||||
manifest = { "files" => [], "timeline" => [] }
|
||||
mix_params = []
|
||||
recorded_tracks.each do |recorded_track|
|
||||
return nil unless recorded_track.fully_uploaded
|
||||
manifest["files"] << { "url" => recorded_track.url, "codec" => "vorbis", "offset" => 0 }
|
||||
mix_params << { "level" => 100, "balance" => 0 }
|
||||
end
|
||||
|
||||
manifest["timeline"] << { "timestamp" => 0, "mix" => mix_params }
|
||||
manifest["timeline"] << { "timestamp" => duration, "end" => true }
|
||||
manifest
|
||||
end
|
||||
|
||||
private
|
||||
def self.validate_user_is_band_member(user, band)
|
||||
unless band.users.exists? user
|
||||
raise PermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR
|
||||
end
|
||||
end
|
||||
|
||||
def self.validate_user_is_creator(user, creator)
|
||||
unless user.id == creator.id
|
||||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
|
||||
end
|
||||
end
|
||||
|
||||
def self.validate_user_is_musician(user)
|
||||
unless user.musician?
|
||||
raise PermissionError, ValidationMessages::USER_NOT_MUSICIAN_VALIDATION_ERROR
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
module JamRuby
|
||||
# not a active_record model; just a search result
|
||||
class Search
|
||||
attr_accessor :bands, :musicians, :fans, :recordings, :friends
|
||||
|
||||
LIMIT = 10
|
||||
|
||||
# performs a site-white search
|
||||
def self.search(query, user_id = nil)
|
||||
|
||||
users = User.search(query, :limit => LIMIT)
|
||||
bands = Band.search(query, :limit => LIMIT)
|
||||
# NOTE: I removed recordings from search here. This is because we switched
|
||||
# to "claimed_recordings" so it's not clear what should be searched.
|
||||
|
||||
friends = Friendship.search(query, user_id, :limit => LIMIT)
|
||||
|
||||
return Search.new(users + bands + friends)
|
||||
end
|
||||
|
||||
# performs a friend search scoped to a specific user
|
||||
# def self.search_by_user(query, user_id)
|
||||
# friends = Friendship.search(query, user_id, :limit => LIMIT)
|
||||
# return Search.new(friends)
|
||||
# end
|
||||
|
||||
# search_results - results from a Tire search across band/user/recording
|
||||
def initialize(search_results)
|
||||
@bands = []
|
||||
@musicians = []
|
||||
@fans = []
|
||||
@recordings = []
|
||||
@friends = []
|
||||
|
||||
if search_results.nil?
|
||||
return
|
||||
end
|
||||
|
||||
search_results.take(LIMIT).each do |result|
|
||||
if result.class == User
|
||||
if result.musician
|
||||
@musicians.push(result)
|
||||
else
|
||||
@fans.push(result)
|
||||
end
|
||||
elsif result.class == Band
|
||||
@bands.push(result)
|
||||
elsif result.class == Recording
|
||||
@recordings.push(result)
|
||||
elsif result.class == Friendship
|
||||
@friends.push(result.friend)
|
||||
else
|
||||
raise Exception, "unknown class #{result.class} returned in search results"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.create_tsquery(query)
|
||||
# empty queries don't hit back to elasticsearch
|
||||
if query.nil? || query.length == 0
|
||||
return nil
|
||||
end
|
||||
|
||||
search_terms = query.split
|
||||
|
||||
if search_terms.length == 0
|
||||
return nil
|
||||
end
|
||||
|
||||
args = nil
|
||||
search_terms.each do |search_term|
|
||||
if args == nil
|
||||
args = search_term
|
||||
else
|
||||
args = args + " & " + search_term
|
||||
end
|
||||
|
||||
end
|
||||
args = args + ":*"
|
||||
|
||||
return args
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
module JamRuby
|
||||
class Track < ActiveRecord::Base
|
||||
|
||||
self.table_name = "tracks"
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
default_scope order('created_at ASC')
|
||||
|
||||
SOUND = %w(mono stereo)
|
||||
|
||||
belongs_to :connection, :class_name => "JamRuby::Connection", :inverse_of => :tracks
|
||||
belongs_to :instrument, :class_name => "JamRuby::Instrument", :inverse_of => :tracks
|
||||
|
||||
validates :sound, :inclusion => {:in => SOUND}
|
||||
|
||||
def self.index(current_user, music_session_id)
|
||||
query = Track
|
||||
.joins(
|
||||
%Q{
|
||||
INNER JOIN
|
||||
connections
|
||||
ON
|
||||
connections.music_session_id = '#{music_session_id}'
|
||||
AND
|
||||
connections.id = connection_id
|
||||
AND
|
||||
connections.user_id = '#{current_user.id}'
|
||||
INNER JOIN
|
||||
music_sessions
|
||||
ON
|
||||
music_sessions.id = '#{music_session_id}'
|
||||
AND
|
||||
music_sessions.user_id = '#{current_user.id}'
|
||||
}
|
||||
)
|
||||
|
||||
return query
|
||||
end
|
||||
|
||||
def self.save(id, connection_id, instrument_id, sound)
|
||||
if id.nil?
|
||||
track = Track.new()
|
||||
track.connection_id = connection_id
|
||||
else
|
||||
track = Track.find(id)
|
||||
end
|
||||
|
||||
unless instrument_id.nil?
|
||||
track.instrument_id = instrument_id
|
||||
end
|
||||
|
||||
unless sound.nil?
|
||||
track.sound = sound
|
||||
end
|
||||
|
||||
track.updated_at = Time.now.getutc
|
||||
track.save
|
||||
return track
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,931 @@
|
|||
include Devise::Models
|
||||
|
||||
module JamRuby
|
||||
class User < ActiveRecord::Base
|
||||
|
||||
#devise: for later: :trackable
|
||||
|
||||
devise :database_authenticatable,
|
||||
:recoverable, :rememberable
|
||||
|
||||
|
||||
attr_accessible :first_name, :last_name, :email, :city, :password, :password_confirmation, :state, :country, :birth_date, :subscribe_email, :terms_of_service, :original_fpfile, :cropped_fpfile, :cropped_s3_path, :photo_url, :crop_selection
|
||||
|
||||
# updating_password corresponds to a lost_password
|
||||
attr_accessor :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar
|
||||
|
||||
# authorizations (for facebook, etc -- omniauth)
|
||||
has_many :user_authorizations, :class_name => "JamRuby::UserAuthorization"
|
||||
|
||||
# connections (websocket-gateway)
|
||||
has_many :connections, :class_name => "JamRuby::Connection"
|
||||
|
||||
# friend requests
|
||||
has_many :friend_requests, :class_name => "JamRuby::FriendRequest"
|
||||
|
||||
# instruments
|
||||
has_many :musician_instruments, :class_name => "JamRuby::MusicianInstrument"
|
||||
has_many :instruments, :through => :musician_instruments, :class_name => "JamRuby::Instrument"
|
||||
|
||||
# bands
|
||||
has_many :band_musicians, :class_name => "JamRuby::BandMusician"
|
||||
has_many :bands, :through => :band_musicians, :class_name => "JamRuby::Band"
|
||||
|
||||
# recordings
|
||||
has_many :owned_recordings, :class_name => "JamRuby::Recording"
|
||||
has_many :recordings, :through => :claimed_recordings, :class_name => "JamRuby::Recording"
|
||||
has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :user
|
||||
|
||||
# user likers (a musician has likers and may have likes too; fans do not have likers)
|
||||
has_many :likers, :class_name => "JamRuby::UserLiker", :foreign_key => "user_id", :inverse_of => :user
|
||||
has_many :inverse_likers, :through => :likers, :class_name => "JamRuby::User", :foreign_key => "liker_id"
|
||||
|
||||
# user likes (fans and musicians have likes)
|
||||
has_many :likes, :class_name => "JamRuby::UserLike", :foreign_key => "liker_id", :inverse_of => :user
|
||||
has_many :inverse_likes, :through => :followings, :class_name => "JamRuby::User", :foreign_key => "user_id"
|
||||
|
||||
# band likes
|
||||
has_many :band_likes, :class_name => "JamRuby::BandLiker", :foreign_key => "liker_id", :inverse_of => :user
|
||||
has_many :inverse_band_likes, :through => :band_likes, :class_name => "JamRuby::Band", :foreign_key => "band_id"
|
||||
|
||||
# followers
|
||||
has_many :user_followers, :class_name => "JamRuby::UserFollower", :foreign_key => "user_id"
|
||||
has_many :followers, :through => :user_followers, :class_name => "JamRuby::User"
|
||||
has_many :inverse_user_followers, :through => :followers, :class_name => "JamRuby::UserFollower", :foreign_key => "follower_id"
|
||||
has_many :inverse_followers, :through => :inverse_user_followers, :source => :user, :class_name => "JamRuby::User"
|
||||
|
||||
# user followings
|
||||
has_many :user_followings, :class_name => "JamRuby::UserFollowing", :foreign_key => "follower_id"
|
||||
has_many :followings, :through => :user_followings, :class_name => "JamRuby::User"
|
||||
has_many :inverse_user_followings, :through => :followings, :class_name => "JamRuby::UserFollowing", :foreign_key => "user_id"
|
||||
has_many :inverse_followings, :through => :inverse_user_followings, :source => :user, :class_name => "JamRuby::User"
|
||||
|
||||
# band followings
|
||||
has_many :b_followings, :class_name => "JamRuby::BandFollowing", :foreign_key => "follower_id"
|
||||
has_many :band_followings, :through => :b_followings, :class_name => "JamRuby::Band"
|
||||
has_many :inverse_b_followings, :through => :band_followings, :class_name => "JamRuby::BandFollowing", :foreign_key => "band_id"
|
||||
has_many :inverse_band_followings, :through => :inverse_band_followings, :source => :band, :class_name => "JamRuby::Band"
|
||||
|
||||
# notifications
|
||||
has_many :notifications, :class_name => "JamRuby::Notification", :foreign_key => "target_user_id"
|
||||
has_many :inverse_notifications, :through => :notifications, :class_name => "JamRuby::User"
|
||||
|
||||
# friends
|
||||
has_many :friendships, :class_name => "JamRuby::Friendship", :foreign_key => "user_id"
|
||||
has_many :friends, :through => :friendships, :class_name => "JamRuby::User"
|
||||
has_many :inverse_friendships, :class_name => "JamRuby::Friendship", :foreign_key => "friend_id"
|
||||
has_many :inverse_friends, :through => :inverse_friendships, :source => :user, :class_name => "JamRuby::User"
|
||||
|
||||
# connections / music sessions
|
||||
has_many :created_music_sessions, :foreign_key => "user_id", :inverse_of => :user, :class_name => "JamRuby::MusicSession" # sessions *created* by the user
|
||||
has_many :music_sessions, :through => :connections, :class_name => "JamRuby::MusicSession"
|
||||
|
||||
# invitations
|
||||
has_many :received_invitations, :foreign_key => "receiver_id", :inverse_of => :receiver, :class_name => "JamRuby::Invitation"
|
||||
has_many :sent_invitations, :foreign_key => "sender_id", :inverse_of => :sender, :class_name => "JamRuby::Invitation"
|
||||
|
||||
# fan invitations
|
||||
has_many :received_fan_invitations, :foreign_key => "receiver_id", :inverse_of => :receiver, :class_name => "JamRuby::FanInvitation"
|
||||
has_many :sent_fan_invitations, :foreign_key => "sender_id", :inverse_of => :sender, :class_name => "JamRuby::FanInvitation"
|
||||
|
||||
# band invitations
|
||||
has_many :received_band_invitations, :inverse_of => :receiver, :foreign_key => "user_id", :class_name => "JamRuby::BandInvitation"
|
||||
has_many :sent_band_invitations, :inverse_of => :sender, :foreign_key => "creator_id", :class_name => "JamRuby::BandInvitation"
|
||||
|
||||
# session history
|
||||
has_many :music_session_histories, :foreign_key => "user_id", :class_name => "JamRuby::MusicSessionHistory", :inverse_of => :user
|
||||
has_many :music_session_user_histories, :foreign_key => "user_id", :class_name => "JamRuby::MusicSessionUserHistory", :inverse_of => :user
|
||||
|
||||
# saved tracks
|
||||
has_many :recorded_tracks, :foreign_key => "user_id", :class_name => "JamRuby::RecordedTrack", :inverse_of => :user
|
||||
|
||||
# invited users
|
||||
has_many :invited_users, :foreign_key => "sender_id", :class_name => "JamRuby::InvitedUser"
|
||||
|
||||
# crash dumps
|
||||
has_many :crash_dumps, :foreign_key => "user_id", :class_name => "JamRuby::CrashDump"
|
||||
|
||||
# This causes the authenticate method to be generated (among other stuff)
|
||||
#has_secure_password
|
||||
|
||||
before_save :create_remember_token, :if => :should_validate_password?
|
||||
before_save :stringify_avatar_info , :if => :updating_avatar
|
||||
|
||||
validates :first_name, presence: true, length: {maximum: 50}, no_profanity: true
|
||||
validates :last_name, presence: true, length: {maximum: 50}, no_profanity: true
|
||||
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
|
||||
validates :email, presence: true, format: {with: VALID_EMAIL_REGEX}
|
||||
validates :update_email, presence: true, format: {with: VALID_EMAIL_REGEX}, :if => :updating_email
|
||||
|
||||
validates_length_of :password, minimum: 6, maximum: 100, :if => :should_validate_password?
|
||||
validates_presence_of :password_confirmation, :if => :should_validate_password?
|
||||
validates_confirmation_of :password, :if => :should_validate_password?
|
||||
|
||||
validates :terms_of_service, :acceptance => {:accept => true, :on => :create, :allow_nil => false }
|
||||
validates :subscribe_email, :inclusion => {:in => [nil, true, false]}
|
||||
validates :musician, :inclusion => {:in => [true, false]}
|
||||
|
||||
# custom validators
|
||||
validate :validate_musician_instruments
|
||||
validate :validate_current_password
|
||||
validate :validate_update_email
|
||||
validate :validate_avatar_info
|
||||
validate :email_case_insensitive_uniqueness
|
||||
validate :update_email_case_insensitive_uniqueness, :if => :updating_email
|
||||
|
||||
def validate_musician_instruments
|
||||
errors.add(:musician_instruments, ValidationMessages::INSTRUMENT_MINIMUM_NOT_MET) if !administratively_created && musician && musician_instruments.length == 0
|
||||
errors.add(:musician_instruments, ValidationMessages::INSTRUMENT_LIMIT_EXCEEDED) if !administratively_created && musician && musician_instruments.length > 5
|
||||
end
|
||||
|
||||
def validate_current_password
|
||||
# checks if the user put in their current password (used when changing your email, for instance)
|
||||
errors.add(:current_password, ValidationMessages::NOT_YOUR_PASSWORD) if should_confirm_existing_password? && !valid_password?(self.current_password)
|
||||
end
|
||||
|
||||
def validate_update_email
|
||||
if updating_email && self.update_email == self.email
|
||||
errors.add(:update_email, ValidationMessages::EMAIL_MATCHES_CURRENT)
|
||||
elsif updating_email && User.where("email ILIKE ?", self.update_email).first != nil
|
||||
errors.add(:update_email, ValidationMessages::EMAIL_ALREADY_TAKEN)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_avatar_info
|
||||
if updating_avatar
|
||||
# we want to mak sure that original_fpfile and cropped_fpfile seems like real fpfile info objects (i.e, json objects from filepicker.io)
|
||||
errors.add(:original_fpfile, ValidationMessages::INVALID_FPFILE) if self.original_fpfile.nil? || self.original_fpfile["key"].nil? || self.original_fpfile["url"].nil?
|
||||
errors.add(:cropped_fpfile, ValidationMessages::INVALID_FPFILE) if self.cropped_fpfile.nil? || self.cropped_fpfile["key"].nil? || self.cropped_fpfile["url"].nil?
|
||||
end
|
||||
end
|
||||
|
||||
def email_case_insensitive_uniqueness
|
||||
# using the case insensitive unique check of active record will downcase the field, which is not what we want--we want to preserve original casing
|
||||
search = User.where("email ILIKE ?", self.email).first
|
||||
if search != nil && search != self
|
||||
errors.add(:email, ValidationMessages::EMAIL_ALREADY_TAKEN)
|
||||
end
|
||||
end
|
||||
|
||||
def update_email_case_insensitive_uniqueness
|
||||
# using the case insensitive unique check of active record will downcase the field, which is not what we want--we want to preserve original casing
|
||||
search = User.where("update_email ILIKE ?", self.update_email).first
|
||||
if search != nil && search != self
|
||||
errors.add(:update_email, ValidationMessages::EMAIL_ALREADY_TAKEN)
|
||||
end
|
||||
end
|
||||
|
||||
def online
|
||||
@online ||= !self.connections.nil? && self.connections.size > 0
|
||||
end
|
||||
|
||||
def name
|
||||
return "#{first_name} #{last_name}"
|
||||
end
|
||||
|
||||
def location
|
||||
loc = self.city.blank? ? '' : self.city
|
||||
loc = loc.blank? ? self.state : "#{loc}, #{self.state}" unless self.state.blank?
|
||||
#loc = loc.blank? ? self.country : "#{loc}, #{self.country}" unless self.country.blank?
|
||||
loc
|
||||
end
|
||||
|
||||
def location= location_hash
|
||||
unless location_hash.blank?
|
||||
self.city = location_hash[:city]
|
||||
self.state = location_hash[:state]
|
||||
self.country = location_hash[:country]
|
||||
end if self.city.blank?
|
||||
end
|
||||
|
||||
def musician?
|
||||
return musician
|
||||
end
|
||||
|
||||
def should_validate_password?
|
||||
(updating_password || new_record?)
|
||||
end
|
||||
|
||||
def should_confirm_existing_password?
|
||||
confirm_current_password
|
||||
end
|
||||
|
||||
def end_user_created?
|
||||
return !administratively_created
|
||||
end
|
||||
|
||||
def friends?(user)
|
||||
return self.friends.exists?(user)
|
||||
end
|
||||
|
||||
def friend_count
|
||||
return self.friends.size
|
||||
end
|
||||
|
||||
def liker_count
|
||||
return self.likers.size
|
||||
end
|
||||
|
||||
def like_count
|
||||
return self.likes.size
|
||||
end
|
||||
|
||||
def band_like_count
|
||||
return self.band_likes.size
|
||||
end
|
||||
|
||||
def follower_count
|
||||
return self.followers.size
|
||||
end
|
||||
|
||||
def following_count
|
||||
return self.followings.size
|
||||
end
|
||||
|
||||
def band_following_count
|
||||
return self.band_followings.size
|
||||
end
|
||||
|
||||
def recording_count
|
||||
return self.recordings.size
|
||||
end
|
||||
|
||||
def session_count
|
||||
return self.music_sessions.size
|
||||
end
|
||||
|
||||
def confirm_email!
|
||||
self.email_confirmed = true
|
||||
end
|
||||
|
||||
def my_session_settings
|
||||
unless self.session_settings.nil?
|
||||
return JSON.parse(self.session_settings)
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
|
||||
def session_history(user_id, band_id = nil, genre = nil)
|
||||
return MusicSessionHistory.index(self, user_id, band_id, genre)
|
||||
end
|
||||
|
||||
def session_user_history(user_id, session_id)
|
||||
return MusicSessionUserHistory.where("music_session_id='#{session_id}'")
|
||||
end
|
||||
|
||||
# always returns a non-null value for photo-url,
|
||||
# using the generic avatar if no user photo available
|
||||
def resolved_photo_url
|
||||
if self.photo_url == nil || self.photo_url == ''
|
||||
# lame that this isn't environment, but boy this is hard to pass all the way down from jam-web!
|
||||
"http://www.jamkazam.com/assets/shared/avatar_generic.png"
|
||||
else
|
||||
return self.photo_url
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
return email unless email.nil?
|
||||
|
||||
if !first_name.nil? && !last_name.nil?
|
||||
return first_name + ' ' + last_name
|
||||
end
|
||||
|
||||
return id
|
||||
end
|
||||
|
||||
def set_password(old_password, new_password, new_password_confirmation)
|
||||
|
||||
# so that UserObserver knows to send a confirmation email on success
|
||||
self.setting_password = true
|
||||
# so that should_validate_password? fires
|
||||
self.updating_password = true
|
||||
|
||||
attributes = { :password => new_password, :password_confirmation => new_password_confirmation }
|
||||
|
||||
# taken liberally from Devise::DatabaseAuthenticatable.update_with_password
|
||||
|
||||
if valid_password?(old_password)
|
||||
update_attributes(attributes)
|
||||
else
|
||||
self.assign_attributes(attributes)
|
||||
self.valid?
|
||||
self.errors.add(:current_password, old_password.blank? ? :blank : :invalid)
|
||||
end
|
||||
|
||||
#clean_up_passwords
|
||||
|
||||
end
|
||||
|
||||
def self.set_password_from_token(email, token, new_password, new_password_confirmation)
|
||||
user = User.where("email ILIKE ?", email).first
|
||||
if user.nil? || user.reset_password_token != token || Time.now - user.reset_password_token_created > 3.days || new_password.length < 6 || new_password != new_password_confirmation
|
||||
raise JamRuby::JamArgumentError
|
||||
end
|
||||
user.reset_password_token = nil
|
||||
user.reset_password_token_created = nil
|
||||
user.change_password(new_password, new_password_confirmation)
|
||||
user.save
|
||||
end
|
||||
|
||||
def change_password(new_password, new_password_confirmation)
|
||||
# FIXME: Should verify that the new password meets certain quality criteria. Really, maybe that should be a
|
||||
# verification step.
|
||||
self.updating_password = true
|
||||
self.password = new_password
|
||||
self.password_confirmation = new_password_confirmation
|
||||
|
||||
UserMailer.password_changed(self).deliver
|
||||
end
|
||||
|
||||
def self.reset_password(email, base_uri)
|
||||
user = User.where("email ILIKE ?", email).first
|
||||
raise JamRuby::JamArgumentError if user.nil?
|
||||
|
||||
user.reset_password_token = SecureRandom.urlsafe_base64
|
||||
user.reset_password_token_created = Time.now
|
||||
user.save
|
||||
|
||||
reset_url = "#{base_uri}/reset_password_token?token=#{user.reset_password_token}&email=#{CGI.escape(email)}"
|
||||
UserMailer.password_reset(user, reset_url).deliver
|
||||
|
||||
user
|
||||
end
|
||||
|
||||
def self.band_index(user_id)
|
||||
bands = Band.joins(:band_musicians)
|
||||
.where(:bands_musicians => {:user_id => "#{user_id}"})
|
||||
|
||||
return bands
|
||||
end
|
||||
|
||||
def self.recording_index(current_user, user_id)
|
||||
hide_private = false
|
||||
|
||||
# hide private recordings from anyone but the current user
|
||||
if current_user.id != user_id
|
||||
hide_private = true
|
||||
end
|
||||
|
||||
if hide_private
|
||||
recordings = Recording.joins(:musician_recordings)
|
||||
.where(:musicians_recordings => {:user_id => "#{user_id}"}, :public => true)
|
||||
|
||||
else
|
||||
recordings = Recording.joins(:musician_recordings)
|
||||
.where(:musicians_recordings => {:user_id => "#{user_id}"})
|
||||
end
|
||||
|
||||
return recordings
|
||||
end
|
||||
|
||||
# given an array of instruments, update a user's instruments
|
||||
def update_instruments(instruments)
|
||||
# delete all instruments for this user first
|
||||
unless self.new_record?
|
||||
MusicianInstrument.delete_all(["user_id = ?", self.id])
|
||||
end
|
||||
|
||||
# loop through each instrument in the array and save to the db
|
||||
instruments.each do |musician_instrument_param|
|
||||
instrument = Instrument.find(musician_instrument_param[:instrument_id])
|
||||
musician_instrument = MusicianInstrument.new
|
||||
musician_instrument.user = self
|
||||
musician_instrument.instrument = instrument
|
||||
musician_instrument.proficiency_level = musician_instrument_param[:proficiency_level]
|
||||
musician_instrument.priority = musician_instrument_param[:priority]
|
||||
musician_instrument.save
|
||||
self.musician_instruments << musician_instrument
|
||||
end
|
||||
end
|
||||
|
||||
# this easy_save routine guards against nil sets, but many of these fields can be set to null.
|
||||
# I've started to use it less as I go forward
|
||||
def easy_save(first_name, last_name, email, password, password_confirmation, musician, gender,
|
||||
birth_date, internet_service_provider, city, state, country, instruments, photo_url)
|
||||
|
||||
# first name
|
||||
unless first_name.nil?
|
||||
self.first_name = first_name
|
||||
end
|
||||
|
||||
# last name
|
||||
unless last_name.nil?
|
||||
self.last_name = last_name
|
||||
end
|
||||
|
||||
# email
|
||||
# !! Email is changed in a dedicated method, 'update_email'
|
||||
#unless email.nil?
|
||||
# self.email = email
|
||||
#end
|
||||
|
||||
# password
|
||||
unless password.nil?
|
||||
self.password = password
|
||||
end
|
||||
|
||||
# password confirmation
|
||||
unless password_confirmation.nil?
|
||||
self.password_confirmation = password_confirmation
|
||||
end
|
||||
|
||||
# musician flag
|
||||
unless musician.nil?
|
||||
self.musician = musician
|
||||
end
|
||||
|
||||
# gender
|
||||
unless gender.nil?
|
||||
self.gender = gender
|
||||
end
|
||||
|
||||
# birthdate
|
||||
unless birth_date.nil?
|
||||
self.birth_date = birth_date
|
||||
end
|
||||
|
||||
# ISP
|
||||
unless internet_service_provider.nil?
|
||||
self.internet_service_provider = internet_service_provider
|
||||
end
|
||||
|
||||
# city
|
||||
unless city.nil?
|
||||
self.city = city
|
||||
end
|
||||
|
||||
# state
|
||||
unless state.nil?
|
||||
self.state = state
|
||||
end
|
||||
|
||||
# country
|
||||
unless country.nil?
|
||||
self.country = country
|
||||
end
|
||||
|
||||
# instruments
|
||||
unless instruments.nil?
|
||||
update_instruments(instruments)
|
||||
end
|
||||
|
||||
# photo url
|
||||
unless photo_url.nil?
|
||||
self.photo_url = photo_url
|
||||
end
|
||||
|
||||
self.updated_at = Time.now.getutc
|
||||
self.save
|
||||
end
|
||||
|
||||
# helper method for creating / updating a User
|
||||
def self.save(id, updater_id, first_name, last_name, email, password, password_confirmation, musician, gender,
|
||||
birth_date, internet_service_provider, city, state, country, instruments, photo_url)
|
||||
if id.nil?
|
||||
user = User.new()
|
||||
else
|
||||
user = User.find(id)
|
||||
end
|
||||
|
||||
if user.id != updater_id
|
||||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
|
||||
end
|
||||
|
||||
user.easy_save(first_name, last_name, email, password, password_confirmation, musician, gender,
|
||||
birth_date, internet_service_provider, city, state, country, instruments, photo_url)
|
||||
return user
|
||||
end
|
||||
|
||||
def begin_update_email(email, current_password, confirmation_url)
|
||||
# sets the user model in a state such that it's expecting to have it's email updated
|
||||
# two columns matter for this; 'update_email_token' and 'update_email'
|
||||
# confirmation_link is odd in the sense that it can likely only come from www.jamkazam.com (jam-web)
|
||||
|
||||
# an observer should be set up to send an email based on this activity
|
||||
self.updating_email = self.confirm_current_password = true
|
||||
self.current_password = current_password
|
||||
self.update_email = email
|
||||
self.update_email_token = SecureRandom.urlsafe_base64
|
||||
self.update_email_confirmation_url = "#{confirmation_url}#{self.update_email_token}"
|
||||
|
||||
self.save
|
||||
end
|
||||
|
||||
def self.finalize_update_email(update_email_token)
|
||||
# updates the user model to have a new email address
|
||||
user = User.find_by_update_email_token!(update_email_token)
|
||||
|
||||
user.updated_email = true
|
||||
user.email = user.update_email
|
||||
user.update_email_token = nil
|
||||
user.save
|
||||
|
||||
return user
|
||||
end
|
||||
|
||||
|
||||
def self.create_user_like(user_id, liker_id)
|
||||
liker = UserLiker.new()
|
||||
liker.user_id = user_id
|
||||
liker.liker_id = liker_id
|
||||
liker.save
|
||||
end
|
||||
|
||||
def self.delete_like(user_id, band_id, liker_id)
|
||||
if !user_id.nil?
|
||||
JamRuby::UserLiker.delete_all "(user_id = '#{user_id}' AND liker_id = '#{liker_id}')"
|
||||
|
||||
elsif !band_id.nil?
|
||||
JamRuby::BandLiker.delete_all "(band_id = '#{band_id}' AND liker_id = '#{liker_id}')"
|
||||
end
|
||||
end
|
||||
|
||||
def self.create_band_like(band_id, liker_id)
|
||||
liker = BandLiker.new()
|
||||
liker.band_id = band_id
|
||||
liker.liker_id = liker_id
|
||||
liker.save
|
||||
end
|
||||
|
||||
def self.delete_band_like(band_id, liker_id)
|
||||
JamRuby::BandLiker.delete_all "(band_id = '#{band_id}' AND liker_id = '#{liker_id}')"
|
||||
end
|
||||
|
||||
def self.create_user_following(user_id, follower_id)
|
||||
follower = UserFollower.new()
|
||||
follower.user_id = user_id
|
||||
follower.follower_id = follower_id
|
||||
follower.save
|
||||
end
|
||||
|
||||
def self.delete_following(user_id, band_id, follower_id)
|
||||
if !user_id.nil?
|
||||
JamRuby::UserFollower.delete_all "(user_id = '#{user_id}' AND follower_id = '#{follower_id}')"
|
||||
|
||||
elsif !band_id.nil?
|
||||
JamRuby::BandFollower.delete_all "(band_id = '#{band_id}' AND follower_id = '#{follower_id}')"
|
||||
end
|
||||
end
|
||||
|
||||
def self.create_band_following(band_id, follower_id)
|
||||
follower = BandFollower.new()
|
||||
follower.band_id = band_id
|
||||
follower.follower_id = follower_id
|
||||
follower.save
|
||||
end
|
||||
|
||||
def self.delete_band_following(band_id, follower_id)
|
||||
JamRuby::BandFollower.delete_all "(band_id = '#{band_id}' AND follower_id = '#{follower_id}')"
|
||||
end
|
||||
|
||||
def self.create_favorite(user_id, recording_id)
|
||||
favorite = UserFavorite.new()
|
||||
favorite.user_id = user_id
|
||||
favorite.recording_id = recording_id
|
||||
favorite.save
|
||||
end
|
||||
|
||||
def self.delete_favorite(user_id, recording_id)
|
||||
JamRuby::UserFavorite.delete_all "(user_id = '#{user_id}' AND recording_id = '#{recording_id}')"
|
||||
end
|
||||
|
||||
def self.save_session_settings(user, music_session)
|
||||
unless user.nil?
|
||||
|
||||
# only save genre id and description
|
||||
genres = []
|
||||
unless music_session.genres.nil?
|
||||
music_session.genres.each do |genre|
|
||||
g = Hash.new
|
||||
g["id"] = genre.id
|
||||
g["description"] = genre.description
|
||||
genres << g
|
||||
end
|
||||
end
|
||||
|
||||
# only save invitation receiver id and name
|
||||
invitees = []
|
||||
unless music_session.invitations.nil?
|
||||
music_session.invitations.each do |invitation|
|
||||
i = Hash.new
|
||||
i["id"] = invitation.receiver.id
|
||||
i["name"] = invitation.receiver.name
|
||||
invitees << i
|
||||
end
|
||||
end
|
||||
|
||||
session_settings = { :band_id => music_session.band_id,
|
||||
:musician_access => music_session.musician_access,
|
||||
:approval_required => music_session.approval_required,
|
||||
:fan_chat => music_session.fan_chat,
|
||||
:fan_access => music_session.fan_access,
|
||||
:description => music_session.description,
|
||||
:genres => genres,
|
||||
:invitees => invitees
|
||||
}.to_json
|
||||
|
||||
user.session_settings = session_settings
|
||||
user.save
|
||||
end
|
||||
end
|
||||
|
||||
# throws ActiveRecord::RecordNotFound if instrument is invalid
|
||||
# throws an email delivery error if unable to connect out to SMTP
|
||||
def self.signup(first_name, last_name, email, password, password_confirmation, terms_of_service, subscribe_email,
|
||||
location, instruments, birth_date, musician, photo_url, invited_user, signup_confirm_url)
|
||||
user = User.new
|
||||
|
||||
UserManager.active_record_transaction do |user_manager|
|
||||
user.first_name = first_name
|
||||
user.last_name = last_name
|
||||
user.email = email
|
||||
user.subscribe_email = subscribe_email
|
||||
user.terms_of_service = terms_of_service
|
||||
user.musician = musician
|
||||
|
||||
# FIXME: Setting random password for social network logins. This
|
||||
# is because we have validations all over the place on this.
|
||||
# The right thing would be to have this null
|
||||
|
||||
# Seth: I think we need a flag in the signature of signup to say 'social_signup=true'. If that flag is set,
|
||||
# then you can do use.updating_password = false and instead set a null password
|
||||
if password.nil?
|
||||
user.password = user.password_confirmation = SecureRandom.urlsafe_base64
|
||||
else
|
||||
user.password = password
|
||||
user.password_confirmation = password_confirmation
|
||||
end
|
||||
|
||||
user.admin = false
|
||||
user.city = location[:city]
|
||||
user.state = location[:state]
|
||||
user.country = location[:country]
|
||||
user.birth_date = birth_date
|
||||
|
||||
if user.musician # only update instruments if the user is a musician
|
||||
unless instruments.nil?
|
||||
instruments.each do |musician_instrument_param|
|
||||
instrument = Instrument.find(musician_instrument_param[:instrument_id])
|
||||
musician_instrument = MusicianInstrument.new
|
||||
musician_instrument.user = user
|
||||
musician_instrument.instrument = instrument
|
||||
musician_instrument.proficiency_level = musician_instrument_param[:proficiency_level]
|
||||
musician_instrument.priority = musician_instrument_param[:priority]
|
||||
user.musician_instruments << musician_instrument
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
user.photo_url = photo_url
|
||||
|
||||
|
||||
if invited_user.nil?
|
||||
user.can_invite = Limits::USERS_CAN_INVITE
|
||||
user.email_confirmed = false
|
||||
user.signup_token = SecureRandom.urlsafe_base64
|
||||
else
|
||||
# if you are invited by an admin, we'll say you can invite too.
|
||||
# but if not, then you can not invite
|
||||
user.can_invite = invited_user.invited_by_administrator?
|
||||
|
||||
# if you came in from an invite and used the same email to signup,
|
||||
# then we know you are a real human and that your email is valid.
|
||||
# lucky! we'll log you in immediately
|
||||
if invited_user.email.casecmp(user.email).zero?
|
||||
user.email_confirmed = true
|
||||
user.signup_token = nil
|
||||
else
|
||||
user.email_confirmed = false
|
||||
user.signup_token = SecureRandom.urlsafe_base64
|
||||
end
|
||||
|
||||
|
||||
# now that the user is saved, let's
|
||||
if invited_user.autofriend && !invited_user.sender.nil?
|
||||
# hookup this user with the sender
|
||||
Friendship.save_using_models(user, invited_user.sender)
|
||||
end
|
||||
|
||||
invited_user.accept!
|
||||
invited_user.save
|
||||
|
||||
if invited_user.errors.any?
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
||||
user.save
|
||||
|
||||
if user.errors.any?
|
||||
raise ActiveRecord::Rollback
|
||||
else
|
||||
# don't send an signup email if the user was invited already *and* they used the same email that they were invited with
|
||||
if !invited_user.nil? && invited_user.email.casecmp(user.email).zero?
|
||||
else
|
||||
|
||||
# FIXME:
|
||||
# It's not standard to require a confirmation when a user signs up with Facebook.
|
||||
# We should stop asking for it.
|
||||
#
|
||||
# any errors here should also rollback the transaction; that's OK. If emails aren't going to be delivered,
|
||||
# it's already a really bad situation; make user signup again
|
||||
UserMailer.welcome_message(user, signup_confirm_url.nil? ? nil : (signup_confirm_url + "/" + user.signup_token) ).deliver
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return user
|
||||
end
|
||||
|
||||
# this is intended to be development-mode or test-mode only; VRFS-149
|
||||
# it creates or updates one user per developer, so that we aren't in the business
|
||||
# of constantly recreating users as we create new dev environments
|
||||
|
||||
# We guard against this code running in production mode,
|
||||
# because otherwise it's a bit of uncomfortable code
|
||||
# to have sitting around
|
||||
def self.create_dev_user(first_name, last_name, email, password,
|
||||
city, state, country, instruments, photo_url)
|
||||
|
||||
if Environment.mode == "production"
|
||||
# short-circuit out
|
||||
return
|
||||
end
|
||||
|
||||
user = User.find_or_create_by_email(email)
|
||||
|
||||
User.transaction do
|
||||
user.first_name = first_name
|
||||
user.last_name = last_name
|
||||
user.email = email
|
||||
user.password = password
|
||||
user.password_confirmation = password
|
||||
user.admin = true
|
||||
user.email_confirmed = true
|
||||
user.musician = true
|
||||
user.city = city
|
||||
user.state = state
|
||||
user.country = country
|
||||
user.terms_of_service = true
|
||||
|
||||
if instruments.nil?
|
||||
instruments = [{:instrument_id => "acoustic guitar", :proficiency_level => 3, :priority => 1}]
|
||||
end
|
||||
|
||||
unless user.new_record?
|
||||
MusicianInstrument.delete_all(["user_id = ?", user.id])
|
||||
end
|
||||
|
||||
instruments.each do |musician_instrument_param|
|
||||
instrument = Instrument.find(musician_instrument_param[:instrument_id])
|
||||
musician_instrument = MusicianInstrument.new
|
||||
musician_instrument.user = user
|
||||
musician_instrument.instrument = instrument
|
||||
musician_instrument.proficiency_level = musician_instrument_param[:proficiency_level]
|
||||
musician_instrument.priority = musician_instrument_param[:priority]
|
||||
user.musician_instruments << musician_instrument
|
||||
end
|
||||
|
||||
if photo_url.nil?
|
||||
user.photo_url = photo_url
|
||||
end
|
||||
|
||||
user.signup_token = nil
|
||||
user.save
|
||||
|
||||
if user.errors.any?
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
||||
return user
|
||||
end
|
||||
|
||||
def signup_confirm
|
||||
self.signup_token = nil
|
||||
self.confirm_email!
|
||||
self.save
|
||||
end
|
||||
|
||||
def update_avatar(original_fpfile, cropped_fpfile, crop_selection, aws_bucket)
|
||||
self.updating_avatar = true
|
||||
|
||||
cropped_s3_path = cropped_fpfile["key"]
|
||||
|
||||
return self.update_attributes(
|
||||
:original_fpfile => original_fpfile,
|
||||
:cropped_fpfile => cropped_fpfile,
|
||||
:cropped_s3_path => cropped_s3_path,
|
||||
:crop_selection => crop_selection,
|
||||
:photo_url => S3Util.url(aws_bucket, cropped_s3_path, :secure => false)
|
||||
)
|
||||
end
|
||||
|
||||
def delete_avatar(aws_bucket)
|
||||
|
||||
User.transaction do
|
||||
|
||||
unless self.cropped_s3_path.nil?
|
||||
S3Util.delete(aws_bucket, File.dirname(self.cropped_s3_path) + '/cropped.jpg')
|
||||
S3Util.delete(aws_bucket, self.cropped_s3_path)
|
||||
end
|
||||
|
||||
return self.update_attributes(
|
||||
:original_fpfile => nil,
|
||||
:cropped_fpfile => nil,
|
||||
:cropped_s3_path => nil,
|
||||
:photo_url => nil,
|
||||
:crop_selection => nil
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# throws RecordNotFound if signup token is invalid; i.e., if it's nil, empty string, or not belonging to a user
|
||||
def self.signup_confirm(signup_token)
|
||||
if signup_token.nil? || signup_token.empty?
|
||||
# there are plenty of confirmed users with nil signup_tokens, so we can't look on it
|
||||
raise ActiveRecord::RecordNotFound
|
||||
else
|
||||
UserManager.active_record_transaction do |user_manager|
|
||||
# throws ActiveRecord::RecordNotFound if invalid
|
||||
user = User.find_by_signup_token!(signup_token)
|
||||
user.signup_confirm
|
||||
return user
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# if valid credentials are supplied for an 'active' user, returns the user
|
||||
# if not authenticated, returns nil
|
||||
def self.authenticate(email, password)
|
||||
# remove email_confirmed restriction due to VRFS-378
|
||||
|
||||
# we only allow users that have confirmed email to authenticate
|
||||
# user = User.where('email_confirmed=true').find_by_email(email)
|
||||
|
||||
# do a case insensitive search for email, because we store it case sensitive
|
||||
user = User.where("email ILIKE ?", email).first
|
||||
|
||||
if user && user.valid_password?(password)
|
||||
return user
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
def self.search(query, options = { :limit => 10 })
|
||||
|
||||
# only issue search if at least 2 characters are specified
|
||||
if query.nil? || query.length < 2
|
||||
return []
|
||||
end
|
||||
|
||||
# save query for use in instrument search
|
||||
search_criteria = query
|
||||
|
||||
# create 'anded' statement
|
||||
query = Search.create_tsquery(query)
|
||||
|
||||
if query.nil? || query.length == 0
|
||||
return []
|
||||
end
|
||||
|
||||
# remove email_confirmed restriction due to VRFS-378
|
||||
# .where("email_confirmed = true AND (name_tsv @@ to_tsquery('jamenglish', ?) OR users.id in (select user_id from musicians_instruments where instrument_id like '%#{search_criteria.downcase}%'))", query)
|
||||
|
||||
return query = User
|
||||
.where("(name_tsv @@ to_tsquery('jamenglish', ?) OR users.id in (select user_id from musicians_instruments where instrument_id like '%#{search_criteria.downcase}%'))", query)
|
||||
.limit(options[:limit])
|
||||
end
|
||||
|
||||
# devise compatibility
|
||||
|
||||
#def encrypted_password
|
||||
# logger.debug("password digest returned #{self.password_digest}")
|
||||
# self.password_digest
|
||||
#end
|
||||
|
||||
#def encrypted_password=(encrypted_password)
|
||||
# self.password_digest = encrypted_password
|
||||
#end
|
||||
|
||||
# end devise compatibility
|
||||
private
|
||||
def create_remember_token
|
||||
self.remember_token = SecureRandom.urlsafe_base64
|
||||
end
|
||||
|
||||
def stringify_avatar_info
|
||||
# fpfile comes in as a hash, which is a easy-to-use and validate form. However, we store it as a VARCHAR,
|
||||
# so we need t oconvert it to JSON before storing it (otherwise it gets serialized as a ruby object)
|
||||
# later, when serving this data out to the REST API, we currently just leave it as a string and make a JSON capable
|
||||
# client parse it, because it's very rare when it's needed at all
|
||||
self.original_fpfile = original_fpfile.to_json if !original_fpfile.nil?
|
||||
self.cropped_fpfile = cropped_fpfile.to_json if !cropped_fpfile.nil?
|
||||
self.crop_selection = crop_selection.to_json if !crop_selection.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
module JamRuby
|
||||
class UserAuthorization < ActiveRecord::Base
|
||||
|
||||
attr_accessible :provider, :uid, :token, :token_expiration
|
||||
|
||||
self.table_name = "user_authorizations"
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
belongs_to :user, :class_name => "JamRuby::User", :foreign_key => "user_id"
|
||||
validates :provider, :uid, :presence => true
|
||||
|
||||
# token and token_expiration can be missing
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
module JamRuby
|
||||
class UserFollower < ActiveRecord::Base
|
||||
|
||||
self.table_name = "users_followers"
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
belongs_to :user, :class_name => "JamRuby::User", :foreign_key => "user_id", :inverse_of => :inverse_followers
|
||||
belongs_to :follower, :class_name => "JamRuby::User", :foreign_key => "follower_id", :inverse_of => :followers
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
module JamRuby
|
||||
class UserFollowing < ActiveRecord::Base
|
||||
|
||||
self.table_name = "users_followers"
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
belongs_to :user, :class_name => "JamRuby::User", :foreign_key => "follower_id", :inverse_of => :inverse_followings
|
||||
belongs_to :following, :class_name => "JamRuby::User", :foreign_key => "user_id", :inverse_of => :followings
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
module JamRuby
|
||||
class UserLike < ActiveRecord::Base
|
||||
|
||||
self.table_name = "users_likers"
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
belongs_to :user, :class_name => "JamRuby::User", :foreign_key => "user_id", :inverse_of => :inverse_likes
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
module JamRuby
|
||||
class UserLiker < ActiveRecord::Base
|
||||
|
||||
self.table_name = "users_likers"
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
belongs_to :user, :class_name => "JamRuby::User", :foreign_key => "liker_id", :inverse_of => :inverse_likers
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue