Initial commit
1
.browserslistrc
Normal file
@@ -0,0 +1 @@
|
||||
defaults
|
||||
29
.env.sample
Normal file
@@ -0,0 +1,29 @@
|
||||
DOMAIN=
|
||||
WEB_PORT=
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_BUCKET=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_REGION=
|
||||
|
||||
BRAYNIAC_AI_API_ENDPOINT=https://z99cprg2eg.execute-api.us-east-1.amazonaws.com/dev/v0.0.1
|
||||
SOURCE_AUDIO_API_ENDPOINT=https://bigmedia.sourceaudio.com/api
|
||||
SOURCE_AUDIO_TOKEN=
|
||||
|
||||
# Optional
|
||||
REDIS_URL=
|
||||
|
||||
# Required for Zoom.us integration
|
||||
ZOOM_API_KEY=
|
||||
ZOOM_API_SECRET=
|
||||
ZOOM_PRO_USERS_LIMIT= # defaults to 3
|
||||
ZOOM_USER_TYPE= # 'pro' / 'basic'
|
||||
ZOOM_ENABLE_RECORDINGS=0 # 0 / 1
|
||||
# Token for webhooks authorization
|
||||
ZOOM_VERIFICATION_TOKEN=
|
||||
|
||||
# Required for creating live streams with Mux
|
||||
MUX_TOKEN_ID=
|
||||
MUX_TOKEN_SECRET=
|
||||
MUX_BROADCAST_SERVER_URL=rtmp://global-live.mux.com:5222/app
|
||||
MUX_TEST_MODE_DISABLED=
|
||||
35
.gitignore
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
||||
#
|
||||
# If you find yourself ignoring temporary files generated by your text editor
|
||||
# or operating system, you probably want to add a global ignore instead:
|
||||
# git config --global core.excludesfile '~/.gitignore_global'
|
||||
|
||||
# Ignore bundler config.
|
||||
/.bundle
|
||||
|
||||
# Ignore all logfiles and tempfiles.
|
||||
/log/*
|
||||
/tmp/*
|
||||
!/log/.keep
|
||||
!/tmp/.keep
|
||||
|
||||
# Ignore uploaded files in development
|
||||
/storage/*
|
||||
|
||||
/node_modules
|
||||
/yarn-error.log
|
||||
|
||||
/public/assets
|
||||
.byebug_history
|
||||
|
||||
# Ignore master key for decrypting credentials and more.
|
||||
/config/master.key
|
||||
.env
|
||||
.idea/*
|
||||
/public/packs
|
||||
/public/packs-test
|
||||
/node_modules
|
||||
/yarn-error.log
|
||||
yarn-debug.log*
|
||||
.yarn-integrity
|
||||
rspec.log
|
||||
9
.pairs
Normal file
@@ -0,0 +1,9 @@
|
||||
pairs:
|
||||
ac: Andrew Courter
|
||||
cr: Carlos Ramirez III
|
||||
mail:
|
||||
prefix: pair
|
||||
domain: releaseme.ai
|
||||
no_solo_prefix: true
|
||||
global: false # Set to true for git-pair to change git configuration for all your projects
|
||||
|
||||
1
.ruby-version
Normal file
@@ -0,0 +1 @@
|
||||
2.6.3
|
||||
201
Gemfile
Normal file
@@ -0,0 +1,201 @@
|
||||
source "https://rubygems.org"
|
||||
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
||||
|
||||
ruby "2.6.3"
|
||||
|
||||
# Bundle edge Rails instead: gem "rails", github: "rails/rails"
|
||||
gem "rails", "~> 6.0.0"
|
||||
# Use postgresql as the database for Active Record
|
||||
gem "pg", "~> 0.18"
|
||||
# Use Puma as the app server
|
||||
gem "puma", "~> 3.11"
|
||||
# Use SCSS for stylesheets
|
||||
gem "sass-rails", "~> 5.0"
|
||||
gem "webpacker", "~> 4.0.7"
|
||||
# Use Uglifier as compressor for JavaScript assets
|
||||
gem "uglifier", "~> 4.1.20"
|
||||
|
||||
# Use CoffeeScript for .coffee assets and views
|
||||
gem "coffee-rails", "~> 5.0"
|
||||
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
|
||||
gem "turbolinks", "~> 5"
|
||||
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
|
||||
gem "jbuilder", "~> 2.5"
|
||||
# Use Redis adapter to run Action Cable in production
|
||||
gem "redis", "~> 4.0", group: [:production, :review]
|
||||
|
||||
# Use ActiveStorage variant
|
||||
gem "mini_magick", "~> 4.8"
|
||||
gem "active_storage_base64", "~> 1.0.0"
|
||||
gem "image_processing", "~> 1.2"
|
||||
|
||||
# Use Amazon Web Services S3 for file uploads in production
|
||||
gem "aws-sdk-s3", "~> 1.31.0", require: false, group: [:production, :review]
|
||||
# Allow AWS API requests to be signed using IAM credentials
|
||||
gem "aws-sigv4", "~> 1.0.2"
|
||||
|
||||
# Reduces boot times through caching; required in config/boot.rb
|
||||
gem "bootsnap", ">= 1.1.0", require: false
|
||||
|
||||
# Background processing library
|
||||
gem "sidekiq", "~> 5.2.5"
|
||||
# Sidekiq web console requires a specific version of Rack to work
|
||||
gem "rack", github: "rack/rack", ref: "f690bb71425aa31d7b9b3113829af773950d8ab5"
|
||||
|
||||
# General-purpose JavaScript library
|
||||
gem "jquery-rails", "~> 4.3.1"
|
||||
|
||||
# Pagination framework
|
||||
gem "will_paginate", "~> 3.2.1"
|
||||
|
||||
# Front-end framework and associated helpers
|
||||
gem "bootstrap", "~> 4.4.0"
|
||||
gem "bootstrap_form", "~> 4.3.0"
|
||||
gem "country_select", "~> 3.1.1"
|
||||
gem "font-awesome-rails", "~> 4.7.0.2"
|
||||
gem "will_paginate-bootstrap4", "~> 0.2.2"
|
||||
|
||||
# Engine for static pages
|
||||
gem "high_voltage", "~> 3.0.0"
|
||||
|
||||
# Ruby wrapper for Segment analytics API
|
||||
gem "analytics-ruby", require: "segment"
|
||||
|
||||
# A PDF generation library and pre-packaged binaries for WKHTMLTOPDF dependency
|
||||
gem "pdfkit", "~> 0.8.2", require: "pdfkit"
|
||||
gem "wkhtmltopdf-binary", "~> 0.12.3.1", group: [:development, :test]
|
||||
gem "wkhtmltopdf-heroku", "~> 2.12.5.0", group: [:production, :review]
|
||||
# A PDF library for merging files together
|
||||
gem "hexapdf", "~> 0.9.0"
|
||||
|
||||
# Authentication framework
|
||||
gem "oath", "~> 1.1.0"
|
||||
gem "oath-generators", "~> 1.0.1"
|
||||
|
||||
# Authorization framework
|
||||
gem "pundit", "~> 2.0.0"
|
||||
|
||||
# Standardized data migrations
|
||||
gem "rails-data-migrations", "~> 1.2.0"
|
||||
|
||||
# Connects business objects and REST web services
|
||||
gem "activeresource", "5.1.0"
|
||||
|
||||
# Markdown parser
|
||||
gem "redcarpet", "~> 3.4.0"
|
||||
|
||||
# Additional middleware for Rack
|
||||
gem "rack-contrib", "~> 2.0.1"
|
||||
|
||||
# Tagging plugin
|
||||
gem "acts-as-taggable-on", github: "tbuehl/acts-as-taggable-on", branch: "fix/rails-6-and-failing-specs"
|
||||
|
||||
# Value class for SMPTE timecode information
|
||||
gem "timecode", "~> 2.2.2"
|
||||
|
||||
# Generate xlsx (Excel) templates
|
||||
gem "axlsx", "~> 3.0.0.pre"
|
||||
gem "axlsx_rails", "~> 0.5.2"
|
||||
gem "axlsx_styler", "~> 0.2.0"
|
||||
|
||||
# Shows errors when using SJR templates
|
||||
gem "better_sjr", "~> 1.0.0"
|
||||
|
||||
# Enable full-text searching using PostgreSQL
|
||||
gem "pg_search", "~> 2.1.2"
|
||||
|
||||
# Provides support for dealing with money and currency
|
||||
gem "money-rails", "~> 1.13.0"
|
||||
|
||||
# Ruby library that encodes QR Codes
|
||||
gem "rqrcode", "~> 0.10.1"
|
||||
|
||||
# API wrapper for Sentry exception tracking service
|
||||
gem "sentry-raven", "~> 2.11.0"
|
||||
|
||||
# Store settings as a serialized Hash for ActiveRecord models
|
||||
gem "ledermann-rails-settings", "~> 2.4.3"
|
||||
|
||||
gem "httparty", "~> 0.17.0"
|
||||
|
||||
gem "remember_me", "1.0.0", github: "exosyphon-earthvectors/remember_me", branch: "remove-mongoid-dependency"
|
||||
|
||||
# Helper for creating conditional CSS class strings
|
||||
gem "css-class-string", "~> 0.1.1"
|
||||
|
||||
# Manages and displays breadcrumb trails
|
||||
gem "loaf", "~> 0.8.1"
|
||||
|
||||
# Library for reading and writing zip files
|
||||
gem 'rubyzip', '~> 1.2.4'
|
||||
|
||||
# Mux API library for live streaming
|
||||
# TODO - Update the gem as soon as version 1.5.0 is out.
|
||||
gem 'mux_ruby', git: 'git://github.com/muxinc/mux-ruby.git', branch: 'test-mode'
|
||||
|
||||
# CORS policies
|
||||
gem 'rack-cors'
|
||||
|
||||
# Ruby wrappers for the HubSpot REST API
|
||||
gem "hubspot-ruby"
|
||||
|
||||
group :development, :test, :review do
|
||||
# Call "byebug" anywhere in the code to stop execution and get a debugger console
|
||||
gem "byebug", "~> 11.0.1", platforms: [:mri, :mingw, :x64_mingw]
|
||||
|
||||
gem "rails-controller-testing", "~> 1.0.4"
|
||||
|
||||
# Testing framework
|
||||
gem "rspec-rails", "~> 4.0.0.beta2"
|
||||
|
||||
# Fixture replacement for generating test data
|
||||
gem "factory_bot_rails", "~> 4.8.2"
|
||||
|
||||
# Acceptance test framework for web applications
|
||||
gem "capybara", "~> 3.28.0"
|
||||
|
||||
# Enable headless browser testing for Capybara using Chrome
|
||||
gem "webdrivers", "~> 4.0"
|
||||
|
||||
# RSpec matchers for common testing expectations
|
||||
gem "shoulda-matchers", "~> 4.0"
|
||||
|
||||
# Temporary table-backed ActiveRecord models
|
||||
gem "temping", "~> 3.10.0"
|
||||
|
||||
# PDF parsing
|
||||
gem "pdf-reader", "~> 2.1.0"
|
||||
|
||||
# Stubbing HTTP requests
|
||||
gem "webmock"
|
||||
end
|
||||
|
||||
group :development do
|
||||
# Access an interactive console on exception pages or by calling "console" anywhere in the code.
|
||||
gem "web-console", "~> 4.0.1"
|
||||
gem "listen", ">= 3.0.5", "< 3.2"
|
||||
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
|
||||
gem "spring", "~> 2.1.0"
|
||||
gem "spring-watcher-listen", "~> 2.0.0"
|
||||
# Rake tasks for sorting YAML files
|
||||
gem "i18n_yaml_sorter", "~> 0.2.0"
|
||||
# Loading .env files for environment variables
|
||||
gem "dotenv-rails", "~> 2.7.5"
|
||||
# Shell commands for Heroku apps
|
||||
gem "parity", "~> 3.2.0"
|
||||
# Run scheduled jobs
|
||||
gem "clockwork", "~> 2.0.4"
|
||||
end
|
||||
|
||||
gem "jsonapi-rails"
|
||||
gem "knock"
|
||||
gem "rubocop"
|
||||
|
||||
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
|
||||
|
||||
# Datepicker
|
||||
gem 'bootstrap-datepicker-rails'
|
||||
|
||||
# Zoom.us integration
|
||||
gem 'zoom_rb', github: 'blazejkotowski/zoom_rb', branch: 'roles-api'
|
||||
599
Gemfile.lock
Normal file
@@ -0,0 +1,599 @@
|
||||
GIT
|
||||
remote: git://github.com/muxinc/mux-ruby.git
|
||||
revision: 443238cc7195eb28c724e128378da0bfc25180a5
|
||||
branch: test-mode
|
||||
specs:
|
||||
mux_ruby (1.4.0)
|
||||
json (~> 2.1, >= 2.1.0)
|
||||
typhoeus (~> 1.0, >= 1.0.1)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/blazejkotowski/zoom_rb.git
|
||||
revision: 9d32753f0daa1936d1452a427cb69fd0d75f5be6
|
||||
branch: roles-api
|
||||
specs:
|
||||
zoom_rb (0.10.0)
|
||||
httparty (~> 0.13)
|
||||
json (>= 1.8)
|
||||
jwt
|
||||
|
||||
GIT
|
||||
remote: https://github.com/exosyphon-earthvectors/remember_me.git
|
||||
revision: 1582f2daa900949e6c3ebb74617eb9068f9dea17
|
||||
branch: remove-mongoid-dependency
|
||||
specs:
|
||||
remember_me (1.0.0)
|
||||
rails (>= 3.2)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/rack/rack.git
|
||||
revision: f690bb71425aa31d7b9b3113829af773950d8ab5
|
||||
ref: f690bb71425aa31d7b9b3113829af773950d8ab5
|
||||
specs:
|
||||
rack (2.2.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/tbuehl/acts-as-taggable-on.git
|
||||
revision: 47b192b8d09079ac32f932a849f9d87630035515
|
||||
branch: fix/rails-6-and-failing-specs
|
||||
specs:
|
||||
acts-as-taggable-on (6.0.1)
|
||||
activerecord (>= 5.0, < 6.1)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
Ascii85 (1.0.3)
|
||||
actioncable (6.0.0)
|
||||
actionpack (= 6.0.0)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.0.0)
|
||||
actionpack (= 6.0.0)
|
||||
activejob (= 6.0.0)
|
||||
activerecord (= 6.0.0)
|
||||
activestorage (= 6.0.0)
|
||||
activesupport (= 6.0.0)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.0.0)
|
||||
actionpack (= 6.0.0)
|
||||
actionview (= 6.0.0)
|
||||
activejob (= 6.0.0)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.0.0)
|
||||
actionview (= 6.0.0)
|
||||
activesupport (= 6.0.0)
|
||||
rack (~> 2.0)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.0.0)
|
||||
actionpack (= 6.0.0)
|
||||
activerecord (= 6.0.0)
|
||||
activestorage (= 6.0.0)
|
||||
activesupport (= 6.0.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.0.0)
|
||||
activesupport (= 6.0.0)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
active_storage_base64 (1.0.0)
|
||||
rails (~> 6.0)
|
||||
activejob (6.0.0)
|
||||
activesupport (= 6.0.0)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.0.0)
|
||||
activesupport (= 6.0.0)
|
||||
activemodel-serializers-xml (1.0.2)
|
||||
activemodel (> 5.x)
|
||||
activesupport (> 5.x)
|
||||
builder (~> 3.1)
|
||||
activerecord (6.0.0)
|
||||
activemodel (= 6.0.0)
|
||||
activesupport (= 6.0.0)
|
||||
activeresource (5.1.0)
|
||||
activemodel (>= 5.0, < 7)
|
||||
activemodel-serializers-xml (~> 1.0)
|
||||
activesupport (>= 5.0, < 7)
|
||||
activestorage (6.0.0)
|
||||
actionpack (= 6.0.0)
|
||||
activejob (= 6.0.0)
|
||||
activerecord (= 6.0.0)
|
||||
marcel (~> 0.3.1)
|
||||
activesupport (6.0.0)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
zeitwerk (~> 2.1, >= 2.1.8)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
afm (0.2.2)
|
||||
analytics-ruby (2.2.7)
|
||||
approximately (1.1.0)
|
||||
ast (2.4.0)
|
||||
autoprefixer-rails (9.7.3)
|
||||
execjs
|
||||
aws-eventstream (1.0.3)
|
||||
aws-partitions (1.210.0)
|
||||
aws-sdk-core (3.46.2)
|
||||
aws-eventstream (~> 1.0)
|
||||
aws-partitions (~> 1.0)
|
||||
aws-sigv4 (~> 1.0)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.13.0)
|
||||
aws-sdk-core (~> 3, >= 3.39.0)
|
||||
aws-sigv4 (~> 1.0)
|
||||
aws-sdk-s3 (1.31.0)
|
||||
aws-sdk-core (~> 3, >= 3.39.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.0)
|
||||
aws-sigv4 (1.0.3)
|
||||
axlsx (3.0.0.pre)
|
||||
htmlentities (~> 4.3, >= 4.3.4)
|
||||
mimemagic (~> 0.3)
|
||||
nokogiri (~> 1.8, >= 1.8.2)
|
||||
rubyzip (~> 1.2, >= 1.2.1)
|
||||
axlsx_rails (0.5.2)
|
||||
actionpack (>= 3.1)
|
||||
axlsx (>= 2.0.1)
|
||||
axlsx_styler (0.2.0)
|
||||
activesupport (>= 3.1)
|
||||
axlsx (>= 2.0, < 4)
|
||||
bcrypt (3.1.13)
|
||||
better_sjr (1.0.0)
|
||||
bindex (0.8.1)
|
||||
bootsnap (1.4.5)
|
||||
msgpack (~> 1.0)
|
||||
bootstrap (4.4.1)
|
||||
autoprefixer-rails (>= 9.1.0)
|
||||
popper_js (>= 1.14.3, < 2)
|
||||
sassc-rails (>= 2.0.0)
|
||||
bootstrap-datepicker-rails (1.9.0.1)
|
||||
railties (>= 3.0)
|
||||
bootstrap_form (4.3.0)
|
||||
actionpack (>= 5.0)
|
||||
activemodel (>= 5.0)
|
||||
builder (3.2.4)
|
||||
byebug (11.0.1)
|
||||
capybara (3.28.0)
|
||||
addressable
|
||||
mini_mime (>= 0.1.3)
|
||||
nokogiri (~> 1.8)
|
||||
rack (>= 1.6.0)
|
||||
rack-test (>= 0.6.3)
|
||||
regexp_parser (~> 1.5)
|
||||
xpath (~> 3.2)
|
||||
childprocess (2.0.0)
|
||||
rake (< 13.0)
|
||||
chunky_png (1.3.11)
|
||||
clockwork (2.0.4)
|
||||
activesupport
|
||||
tzinfo
|
||||
cmdparse (3.0.4)
|
||||
coffee-rails (5.0.0)
|
||||
coffee-script (>= 2.2.0)
|
||||
railties (>= 5.2.0)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.12.2)
|
||||
concurrent-ruby (1.1.5)
|
||||
connection_pool (2.2.2)
|
||||
countries (2.1.4)
|
||||
i18n_data (~> 0.8.0)
|
||||
money (~> 6.9)
|
||||
sixarm_ruby_unaccent (~> 1.1)
|
||||
unicode_utils (~> 1.4)
|
||||
country_select (3.1.1)
|
||||
countries (~> 2.0)
|
||||
sort_alphabetical (~> 1.0)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
crass (1.0.6)
|
||||
css-class-string (0.1.1)
|
||||
diff-lcs (1.3)
|
||||
dotenv (2.7.5)
|
||||
dotenv-rails (2.7.5)
|
||||
dotenv (= 2.7.5)
|
||||
railties (>= 3.2, < 6.1)
|
||||
erubi (1.9.0)
|
||||
ethon (0.12.0)
|
||||
ffi (>= 1.3.0)
|
||||
execjs (2.7.0)
|
||||
factory_bot (4.8.2)
|
||||
activesupport (>= 3.0.0)
|
||||
factory_bot_rails (4.8.2)
|
||||
factory_bot (~> 4.8.2)
|
||||
railties (>= 3.0.0)
|
||||
faraday (0.15.4)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ffi (1.11.3)
|
||||
font-awesome-rails (4.7.0.5)
|
||||
railties (>= 3.2, < 6.1)
|
||||
geom2d (0.2.0)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
hashdiff (1.0.1)
|
||||
hashery (2.1.2)
|
||||
hexapdf (0.9.3)
|
||||
cmdparse (~> 3.0, >= 3.0.3)
|
||||
geom2d (~> 0.2)
|
||||
high_voltage (3.0.0)
|
||||
htmlentities (4.3.4)
|
||||
httparty (0.17.0)
|
||||
mime-types (~> 3.0)
|
||||
multi_xml (>= 0.5.2)
|
||||
hubspot-ruby (0.9.0)
|
||||
activesupport (>= 3.0.0)
|
||||
httparty (>= 0.10.0)
|
||||
i18n (1.8.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
i18n_data (0.8.0)
|
||||
i18n_yaml_sorter (0.2.0)
|
||||
image_processing (1.9.3)
|
||||
mini_magick (>= 4.9.5, < 5)
|
||||
ruby-vips (>= 2.0.13, < 3)
|
||||
jaro_winkler (1.5.4)
|
||||
jbuilder (2.9.1)
|
||||
activesupport (>= 4.2.0)
|
||||
jmespath (1.4.0)
|
||||
jquery-rails (4.3.5)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (2.3.0)
|
||||
jsonapi-deserializable (0.2.0)
|
||||
jsonapi-parser (0.1.1)
|
||||
jsonapi-rails (0.4.0)
|
||||
jsonapi-parser (~> 0.1.0)
|
||||
jsonapi-rb (~> 0.5.0)
|
||||
jsonapi-rb (0.5.0)
|
||||
jsonapi-deserializable (~> 0.2.0)
|
||||
jsonapi-serializable (~> 0.3.0)
|
||||
jsonapi-renderer (0.2.2)
|
||||
jsonapi-serializable (0.3.1)
|
||||
jsonapi-renderer (~> 0.2.0)
|
||||
jwt (1.5.6)
|
||||
knock (2.1.1)
|
||||
bcrypt (~> 3.1)
|
||||
jwt (~> 1.5)
|
||||
rails (>= 4.2)
|
||||
ledermann-rails-settings (2.4.3)
|
||||
activerecord (>= 3.1)
|
||||
listen (3.1.5)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
ruby_dep (~> 1.2)
|
||||
loaf (0.8.1)
|
||||
rails (>= 3.2)
|
||||
loofah (2.4.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
marcel (0.3.3)
|
||||
mimemagic (~> 0.3.2)
|
||||
method_source (0.9.2)
|
||||
mime-types (3.3)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2019.0904)
|
||||
mimemagic (0.3.3)
|
||||
mini_magick (4.9.5)
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.4.0)
|
||||
minitest (5.14.0)
|
||||
monetize (1.9.2)
|
||||
money (~> 6.12)
|
||||
money (6.13.4)
|
||||
i18n (>= 0.6.4, <= 2)
|
||||
money-rails (1.13.2)
|
||||
activesupport (>= 3.0)
|
||||
monetize (~> 1.9.0)
|
||||
money (~> 6.13.2)
|
||||
railties (>= 3.0)
|
||||
msgpack (1.3.1)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.1.1)
|
||||
nio4r (2.5.1)
|
||||
nokogiri (1.10.7)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
oath (1.1.0)
|
||||
bcrypt
|
||||
rails
|
||||
warden
|
||||
oath-generators (1.0.1)
|
||||
oath (>= 0.0.12)
|
||||
parallel (1.19.1)
|
||||
parity (3.2.0)
|
||||
parser (2.6.5.0)
|
||||
ast (~> 2.4.0)
|
||||
pdf-reader (2.1.0)
|
||||
Ascii85 (~> 1.0.0)
|
||||
afm (~> 0.2.1)
|
||||
hashery (~> 2.0)
|
||||
ruby-rc4
|
||||
ttfunk
|
||||
pdfkit (0.8.4.1)
|
||||
pg (0.21.0)
|
||||
pg_search (2.1.7)
|
||||
activerecord (>= 4.2)
|
||||
activesupport (>= 4.2)
|
||||
popper_js (1.14.5)
|
||||
public_suffix (4.0.1)
|
||||
puma (3.12.1)
|
||||
pundit (2.0.1)
|
||||
activesupport (>= 3.0.0)
|
||||
rack-contrib (2.0.1)
|
||||
rack (~> 2.0)
|
||||
rack-cors (1.1.1)
|
||||
rack (>= 2.0.0)
|
||||
rack-protection (2.0.7)
|
||||
rack
|
||||
rack-proxy (0.6.5)
|
||||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (6.0.0)
|
||||
actioncable (= 6.0.0)
|
||||
actionmailbox (= 6.0.0)
|
||||
actionmailer (= 6.0.0)
|
||||
actionpack (= 6.0.0)
|
||||
actiontext (= 6.0.0)
|
||||
actionview (= 6.0.0)
|
||||
activejob (= 6.0.0)
|
||||
activemodel (= 6.0.0)
|
||||
activerecord (= 6.0.0)
|
||||
activestorage (= 6.0.0)
|
||||
activesupport (= 6.0.0)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 6.0.0)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.4)
|
||||
actionpack (>= 5.0.1.x)
|
||||
actionview (>= 5.0.1.x)
|
||||
activesupport (>= 5.0.1.x)
|
||||
rails-data-migrations (1.2.0)
|
||||
rails (>= 4.0.0)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.3.0)
|
||||
loofah (~> 2.3)
|
||||
railties (6.0.0)
|
||||
actionpack (= 6.0.0)
|
||||
activesupport (= 6.0.0)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.20.3, < 2.0)
|
||||
rainbow (3.0.0)
|
||||
rake (12.3.3)
|
||||
rb-fsevent (0.10.3)
|
||||
rb-inotify (0.10.0)
|
||||
ffi (~> 1.0)
|
||||
redcarpet (3.4.0)
|
||||
redis (4.1.2)
|
||||
regexp_parser (1.6.0)
|
||||
rqrcode (0.10.1)
|
||||
chunky_png (~> 1.0)
|
||||
rspec-core (3.9.1)
|
||||
rspec-support (~> 3.9.1)
|
||||
rspec-expectations (3.9.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-mocks (3.9.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-rails (4.0.0.beta4)
|
||||
actionpack (>= 4.2)
|
||||
activesupport (>= 4.2)
|
||||
railties (>= 4.2)
|
||||
rspec-core (~> 3.9)
|
||||
rspec-expectations (~> 3.9)
|
||||
rspec-mocks (~> 3.9)
|
||||
rspec-support (~> 3.9)
|
||||
rspec-support (3.9.2)
|
||||
rubocop (0.76.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.6)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 1.7)
|
||||
ruby-progressbar (1.10.1)
|
||||
ruby-rc4 (0.1.5)
|
||||
ruby-vips (2.0.14)
|
||||
ffi (~> 1.9)
|
||||
ruby_dep (1.5.0)
|
||||
rubyzip (1.2.4)
|
||||
safe_yaml (1.0.5)
|
||||
sass (3.7.4)
|
||||
sass-listen (~> 4.0.0)
|
||||
sass-listen (4.0.0)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
sass-rails (5.1.0)
|
||||
railties (>= 5.2.0)
|
||||
sass (~> 3.1)
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
sprockets-rails (>= 2.0, < 4.0)
|
||||
tilt (>= 1.1, < 3)
|
||||
sassc (2.2.1)
|
||||
ffi (~> 1.9)
|
||||
sassc-rails (2.1.2)
|
||||
railties (>= 4.0.0)
|
||||
sassc (>= 2.0)
|
||||
sprockets (> 3.0)
|
||||
sprockets-rails
|
||||
tilt
|
||||
selenium-webdriver (3.142.4)
|
||||
childprocess (>= 0.5, < 3.0)
|
||||
rubyzip (~> 1.2, >= 1.2.2)
|
||||
sentry-raven (2.11.0)
|
||||
faraday (>= 0.7.6, < 1.0)
|
||||
shoulda-matchers (4.2.0)
|
||||
activesupport (>= 4.2.0)
|
||||
sidekiq (5.2.7)
|
||||
connection_pool (~> 2.2, >= 2.2.2)
|
||||
rack (>= 1.5.0)
|
||||
rack-protection (>= 1.5.0)
|
||||
redis (>= 3.3.5, < 5)
|
||||
sixarm_ruby_unaccent (1.2.0)
|
||||
sort_alphabetical (1.1.0)
|
||||
unicode_utils (>= 1.2.2)
|
||||
spring (2.1.0)
|
||||
spring-watcher-listen (2.0.1)
|
||||
listen (>= 2.7, < 4.0)
|
||||
spring (>= 1.2, < 3.0)
|
||||
sprockets (3.7.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.2.1)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
temping (3.10.0)
|
||||
activerecord (>= 4.2)
|
||||
activesupport (>= 4.2)
|
||||
thor (1.0.1)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.10)
|
||||
timecode (2.2.2)
|
||||
approximately (~> 1.1)
|
||||
ttfunk (1.5.1)
|
||||
turbolinks (5.2.0)
|
||||
turbolinks-source (~> 5.2)
|
||||
turbolinks-source (5.2.0)
|
||||
typhoeus (1.3.1)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (1.2.6)
|
||||
thread_safe (~> 0.1)
|
||||
uglifier (4.1.20)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unicode-display_width (1.6.0)
|
||||
unicode_utils (1.4.0)
|
||||
warden (1.2.8)
|
||||
rack (>= 2.0.6)
|
||||
web-console (4.0.1)
|
||||
actionview (>= 6.0.0)
|
||||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 6.0.0)
|
||||
webdrivers (4.1.2)
|
||||
nokogiri (~> 1.6)
|
||||
rubyzip (~> 1.0)
|
||||
selenium-webdriver (>= 3.0, < 4.0)
|
||||
webmock (3.8.3)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webpacker (4.0.7)
|
||||
activesupport (>= 4.2)
|
||||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 4.2)
|
||||
websocket-driver (0.7.1)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.4)
|
||||
will_paginate (3.2.1)
|
||||
will_paginate-bootstrap4 (0.2.2)
|
||||
will_paginate (~> 3.0, >= 3.0.0)
|
||||
wkhtmltopdf-binary (0.12.3.1)
|
||||
wkhtmltopdf-heroku (2.12.5.0)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.2.2)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
active_storage_base64 (~> 1.0.0)
|
||||
activeresource (= 5.1.0)
|
||||
acts-as-taggable-on!
|
||||
analytics-ruby
|
||||
aws-sdk-s3 (~> 1.31.0)
|
||||
aws-sigv4 (~> 1.0.2)
|
||||
axlsx (~> 3.0.0.pre)
|
||||
axlsx_rails (~> 0.5.2)
|
||||
axlsx_styler (~> 0.2.0)
|
||||
better_sjr (~> 1.0.0)
|
||||
bootsnap (>= 1.1.0)
|
||||
bootstrap (~> 4.4.0)
|
||||
bootstrap-datepicker-rails
|
||||
bootstrap_form (~> 4.3.0)
|
||||
byebug (~> 11.0.1)
|
||||
capybara (~> 3.28.0)
|
||||
clockwork (~> 2.0.4)
|
||||
coffee-rails (~> 5.0)
|
||||
country_select (~> 3.1.1)
|
||||
css-class-string (~> 0.1.1)
|
||||
dotenv-rails (~> 2.7.5)
|
||||
factory_bot_rails (~> 4.8.2)
|
||||
font-awesome-rails (~> 4.7.0.2)
|
||||
hexapdf (~> 0.9.0)
|
||||
high_voltage (~> 3.0.0)
|
||||
httparty (~> 0.17.0)
|
||||
hubspot-ruby
|
||||
i18n_yaml_sorter (~> 0.2.0)
|
||||
image_processing (~> 1.2)
|
||||
jbuilder (~> 2.5)
|
||||
jquery-rails (~> 4.3.1)
|
||||
jsonapi-rails
|
||||
knock
|
||||
ledermann-rails-settings (~> 2.4.3)
|
||||
listen (>= 3.0.5, < 3.2)
|
||||
loaf (~> 0.8.1)
|
||||
mini_magick (~> 4.8)
|
||||
money-rails (~> 1.13.0)
|
||||
mux_ruby!
|
||||
oath (~> 1.1.0)
|
||||
oath-generators (~> 1.0.1)
|
||||
parity (~> 3.2.0)
|
||||
pdf-reader (~> 2.1.0)
|
||||
pdfkit (~> 0.8.2)
|
||||
pg (~> 0.18)
|
||||
pg_search (~> 2.1.2)
|
||||
puma (~> 3.11)
|
||||
pundit (~> 2.0.0)
|
||||
rack!
|
||||
rack-contrib (~> 2.0.1)
|
||||
rack-cors
|
||||
rails (~> 6.0.0)
|
||||
rails-controller-testing (~> 1.0.4)
|
||||
rails-data-migrations (~> 1.2.0)
|
||||
redcarpet (~> 3.4.0)
|
||||
redis (~> 4.0)
|
||||
remember_me (= 1.0.0)!
|
||||
rqrcode (~> 0.10.1)
|
||||
rspec-rails (~> 4.0.0.beta2)
|
||||
rubocop
|
||||
rubyzip (~> 1.2.4)
|
||||
sass-rails (~> 5.0)
|
||||
sentry-raven (~> 2.11.0)
|
||||
shoulda-matchers (~> 4.0)
|
||||
sidekiq (~> 5.2.5)
|
||||
spring (~> 2.1.0)
|
||||
spring-watcher-listen (~> 2.0.0)
|
||||
temping (~> 3.10.0)
|
||||
timecode (~> 2.2.2)
|
||||
turbolinks (~> 5)
|
||||
tzinfo-data
|
||||
uglifier (~> 4.1.20)
|
||||
web-console (~> 4.0.1)
|
||||
webdrivers (~> 4.0)
|
||||
webmock
|
||||
webpacker (~> 4.0.7)
|
||||
will_paginate (~> 3.2.1)
|
||||
will_paginate-bootstrap4 (~> 0.2.2)
|
||||
wkhtmltopdf-binary (~> 0.12.3.1)
|
||||
wkhtmltopdf-heroku (~> 2.12.5.0)
|
||||
zoom_rb!
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.6.3p62
|
||||
|
||||
BUNDLED WITH
|
||||
1.17.3
|
||||
3
Procfile
Normal file
@@ -0,0 +1,3 @@
|
||||
web: bundle exec rails s
|
||||
worker: bundle exec sidekiq -q default -q active_storage_analysis -q active_storage_purge -q mailers -c 5
|
||||
release: ./scripts/release_tasks.sh
|
||||
167
README.md
Normal file
@@ -0,0 +1,167 @@
|
||||
### Setup
|
||||
|
||||
|
||||
|
||||
Install RVM to manage ruby version:
|
||||
```bash
|
||||
\curl -sSL https://get.rvm.io | bash
|
||||
rvm install 2.6.3
|
||||
```
|
||||
|
||||
Install external dependencies:
|
||||
|
||||
#### For MacOS:
|
||||
```bash
|
||||
brew tap heroku/brew && brew install heroku
|
||||
brew install postgres
|
||||
brew install redis
|
||||
brew install ffmpeg
|
||||
brew install yarn
|
||||
```
|
||||
|
||||
#### For Linux (Ubuntu):
|
||||
```bash
|
||||
curl https://cli-assets.heroku.com/install.sh | sh
|
||||
sudo apt-get install postgresql postgresql-contrib
|
||||
sudo apt-get install redis-server
|
||||
sudo add-apt-repository ppa:mc3man/trusty-media
|
||||
sudo apt-get install ffmpeg
|
||||
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
|
||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install yarn
|
||||
```
|
||||
|
||||
Then run:
|
||||
```bash
|
||||
bin/setup
|
||||
```
|
||||
to create your databases
|
||||
|
||||
You will need to:
|
||||
- Get environment variables from fellow teammates to populate into `.env` (See env.sample for examples)
|
||||
- AWS account to have access to S3 buckets
|
||||
- Heroku account to deploy
|
||||
- Set DOMAIN and WEB_PORT variables - otherwise application won't work properly. Use DOMAIN=localhost and WEB_PORT=3000 for the default values
|
||||
- Set ZOOM_API_KEY and ZOOM_API_SECRET to integrate with Zoom.us for automatic meeting creation.
|
||||
|
||||
After filling up the .env with correct values
|
||||
Run setup again:
|
||||
```bash
|
||||
bin/setup
|
||||
```
|
||||
|
||||
|
||||
### Running tests
|
||||
```bash
|
||||
rspec
|
||||
yarn test
|
||||
```
|
||||
|
||||
### Running Data migrations
|
||||
```bash
|
||||
rails data:migrate
|
||||
```
|
||||
|
||||
### Current deployment pipeline on Heroku
|
||||
- Review -> Staging |-> Production
|
||||
|-> Demo
|
||||
|
||||
#### Working with environments using Parity
|
||||
|
||||
[Parity](https://github.com/thoughtbot/parity) provides Heroku command wrappers for each environment under the `bin` folder.
|
||||
|
||||
### Useful Heroku commands
|
||||
##### Manually Deploying to Review
|
||||
|
||||
The Review environment is auto-deployed from GitHub `master`. But if you need to manually deploy:
|
||||
```bash
|
||||
bin/review deploy
|
||||
```
|
||||
|
||||
##### Running migrations manually (should be done automatically by Heroku via Procfile)
|
||||
```bash
|
||||
bin/review run db:migrate
|
||||
```
|
||||
|
||||
##### Rails console into Review environment
|
||||
```bash
|
||||
bin/review console
|
||||
```
|
||||
|
||||
##### We keep locale files sorted in alphabetical order. This can be done programmatically
|
||||
```
|
||||
rake i18n:sort
|
||||
```
|
||||
|
||||
## Zoom.us integration
|
||||
DirectMe app offers live broadcasting. Users are offered to paralelly connect to the Zoom meeting to have a video conference while the streaming happens. In order to use the Zoom functionality, the app needs to have the API keys provided. You need Zoom PRO account for this feature.
|
||||
|
||||
#### Zoom.us api keys
|
||||
1. Log in to you zoom.us account
|
||||
2. Go to https://marketplace.zoom.us/develop/create
|
||||
3. Choose JWT application
|
||||
4. Copy API Key and API Secret
|
||||
|
||||
|
||||
## Working Locally
|
||||
|
||||
#### Polling for analysis status updates
|
||||
|
||||
When videos are uploaded, a video/audio analysis process is initiated. Typically the analysis process sends an SNS notification when it is completed. Those SNS notifications hit a webhook URL within the app to update the status accordingly. This requires a public URL, which you likely won't have in development.
|
||||
|
||||
As an alternative, we can poll for status updates using the following Rake task.
|
||||
```bash
|
||||
rake dev:poll_for_analysis_updates
|
||||
```
|
||||
|
||||
To run this continuously, there is a [Clockwork](https://github.com/adamwiggins/clockwork) scheduler included.
|
||||
```bash
|
||||
bin/clockwork lib/dev_clockwork.rb
|
||||
```
|
||||
|
||||
#### Running a real async background queue locally
|
||||
1. Verify redis has been installed
|
||||
1. Set REDIS_URL in your `.env` file
|
||||
```bash
|
||||
REDIS_URL=redis://127.0.0.1:6379
|
||||
```
|
||||
1. Start Sidekiq
|
||||
```bash
|
||||
bundle exec sidekiq -q default -q mailers -c 5
|
||||
```
|
||||
|
||||
### Setup pairing script
|
||||
[Check out the repo here](https://github.com/pivotal-legacy/git_scripts)
|
||||
- Tool to add two or more authors on a commit message quickly
|
||||
- Make sure to add yourself or new devs to the `.pairs` file in the repo
|
||||
|
||||
`cd /usr/local/bin && curl -L http://github.com/pivotal/git_scripts/tarball/master | gunzip | tar xvf - --strip=2`
|
||||
|
||||
then `git pair XX` where XX are your initials from the pairs file
|
||||
|
||||
Enjoy!
|
||||
|
||||
## Problems with setting up environment
|
||||
|
||||
|
||||
### Database setup
|
||||
|
||||
If the setup fails in the point after at least one of the databases
|
||||
have been set up.
|
||||
1. Drop the databases
|
||||
```bash
|
||||
rails db:drop
|
||||
```
|
||||
1. Run the setup again.
|
||||
|
||||
## Optional helpers
|
||||
|
||||
# Run rubocop fix script as a pre-commit git hook
|
||||
|
||||
Add following as .git/hooks/pre-commit file
|
||||
|
||||
```
|
||||
#!/bin/sh
|
||||
exec ./bin/rubocopfix.sh
|
||||
```
|
||||
6
Rakefile
Normal file
@@ -0,0 +1,6 @@
|
||||
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
||||
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
||||
|
||||
require_relative 'config/application'
|
||||
|
||||
Rails.application.load_tasks
|
||||
31
app.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"environments": {
|
||||
"test": {
|
||||
"env": {
|
||||
"WD_CHROME_PATH": "/app/.apt/usr/bin/google-chrome-stable"
|
||||
},
|
||||
"addons": [
|
||||
"heroku-redis:in-dyno",
|
||||
"heroku-postgresql:in-dyno"
|
||||
],
|
||||
"buildpacks": [
|
||||
{
|
||||
"url": "heroku/ruby"
|
||||
},
|
||||
{
|
||||
"url": "heroku/nodejs"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/heroku/heroku-buildpack-google-chrome"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/heroku/heroku-buildpack-activestorage-preview.git"
|
||||
}
|
||||
],
|
||||
"scripts": {
|
||||
"test-setup": "bin/rails assets:precompile",
|
||||
"test": "rspec && rspec --tag integration"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
app/assets/config/manifest.js
Normal file
@@ -0,0 +1,3 @@
|
||||
//= link_tree ../images
|
||||
//= link_directory ../javascripts .js
|
||||
//= link_directory ../stylesheets .css
|
||||
0
app/assets/images/.keep
Normal file
43
app/assets/images/BiG-logo.svg
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 395 100" style="enable-background:new 0 0 395 100;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#171314;}
|
||||
.st1{fill:#EC1E24;}
|
||||
.st2{fill:#EE363B;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M71.708,90.105h30.994c12.19,0,19.217-4.753,19.217-13.224v-0.414c0-7.851-6.199-13.017-19.629-13.017
|
||||
H71.708V90.105z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M115.926,22.746v-0.414c0-7.851-6.199-12.397-18.183-12.397H71.708V35.35h25.208
|
||||
C109.108,35.35,115.926,31.217,115.926,22.746z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<circle class="st1" cx="191.732" cy="22.976" r="19.801"/>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<rect x="0.188" y="-0.438" class="st0" width="32.469" height="100.915"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M220.974,50.02c0-19.586,7.145-37.202,19.286-50.457h-87.75c1.966,4.185,3.088,8.981,3.088,14.505v0.414
|
||||
c0,16.736-9.505,26.654-22.316,32.233c17.564,5.786,28.722,15.704,28.722,35.333v0.414c0,6.768-1.519,12.806-4.415,18.017
|
||||
h14.342v-46.12h39.602v46.12h28.215c-11.807-12.907-18.774-30.146-18.774-50.044V50.02z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M354.491-0.438l-20.494,24.629c-10.331-8.678-20.662-13.637-34.919-13.637
|
||||
c-20.868,0-36.985,17.563-36.985,39.672v0.413c0,23.349,16.323,40.085,39.259,40.085c9.71,0,17.149-2.067,23.142-5.993V67.169
|
||||
h-28.515v-29.34h66.532v62.648H395V-0.438H354.491z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<circle class="st2" cx="191.732" cy="22.976" r="19.801"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/assets/images/anonymous_person.png
Normal file
|
After Width: | Height: | Size: 910 B |
BIN
app/assets/images/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
7
app/assets/images/folder.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48" version="1.1" width="48px" height="48px">
|
||||
<g id="surface1">
|
||||
<path style=" fill:#FFA000;" d="M 40 12 L 22 12 L 18 8 L 8 8 C 5.800781 8 4 9.800781 4 12 L 4 20 L 44 20 L 44 16 C 44 13.800781 42.199219 12 40 12 Z "/>
|
||||
<path style=" fill:#FFCA28;" d="M 40 12 L 8 12 C 5.800781 12 4 13.800781 4 16 L 4 36 C 4 38.199219 5.800781 40 8 40 L 40 40 C 42.199219 40 44 38.199219 44 36 L 44 16 C 44 13.800781 42.199219 12 40 12 Z "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 573 B |
BIN
app/assets/images/logo-deliverME-white.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
app/assets/images/logo-releaseME-black.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
app/assets/images/logo_castme.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
app/assets/images/logo_deliverme.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
app/assets/images/logo_directme.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
app/assets/images/logo_editme.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
app/assets/images/logo_expenseme.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
app/assets/images/logo_releaseme.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
155
app/assets/images/releaseme-logo.svg
Normal file
@@ -0,0 +1,155 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="1003.765px" height="581.324px" viewBox="0 0 1003.765 581.324" enable-background="new 0 0 1003.765 581.324"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="355.3584" y1="337.6338" x2="667.3279" y2="21.1429">
|
||||
<stop offset="0" style="stop-color:#D20000"/>
|
||||
<stop offset="0.3602" style="stop-color:#020101"/>
|
||||
<stop offset="0.7097" style="stop-color:#D20000"/>
|
||||
<stop offset="1" style="stop-color:#020101"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_1_)" d="M644.937,312.306c4.518,2.543,8.047,6.601,8.795,11.832c2.063,14.346,3.597,28.706,4.766,43.094
|
||||
V10.74H345.266v326.469c2.965-7.496,6.708-14.713,11.483-21.525c0.657-0.93,1.354-1.755,2.08-2.528V24.308h286.108V312.306z"/>
|
||||
</g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="313.311" y1="337.875" x2="663.7488" y2="337.875">
|
||||
<stop offset="0" style="stop-color:#020101"/>
|
||||
<stop offset="1" style="stop-color:#D20000"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_2_)" d="M660.762,367.118v-28.663c0-28.471-12.821-46.185-38.115-52.676
|
||||
c-19.553-5.01-56.349-26.795-67.248-33.399c-11.131-4.921-13.731,8.246-8.204,10.883c9.652,5.906,49.704,29.923,72.083,35.659
|
||||
c19.309,4.944,27.916,17.144,27.916,39.533v28.729c-0.006,0.533-0.526,13.157-24.29,22.262l-3.214,1.231
|
||||
c-26.231,10.081-53.206,20.568-114.142,20.205l-7.334-0.085v0.085c-60.854,0.349-87.893-10.124-114.14-20.205l-3.203-1.231
|
||||
c-23.766-9.104-24.295-21.729-24.304-22.328v-28.663c0-22.39,8.611-34.589,27.924-39.533
|
||||
c22.347-5.727,62.296-29.678,72.032-35.636c5.171-3.533-1.755-15.134-8.293-10.826c-11.066,6.708-47.62,28.323-67.103,33.319
|
||||
C355.829,292.271,343,309.99,343,338.455l0.009,28.428c-0.035,0.905-0.409,22.427,33.014,35.23l3.188,1.222
|
||||
c27.042,10.397,54.949,21.125,116.447,21.12c2.029,0,4.112-0.01,6.224-0.033c65.956,0.793,94.745-10.35,122.676-21.087
|
||||
l3.194-1.222C661.178,389.31,660.79,367.783,660.762,367.118z"/>
|
||||
</g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="410.1826" y1="98.9678" x2="410.1826" y2="116.1672">
|
||||
<stop offset="0" style="stop-color:#D20000"/>
|
||||
<stop offset="1" style="stop-color:#020101"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_3_)" d="M439.963,102.392c-1.394,4.345-2.333,8.878-2.762,13.572h-56.799v-13.572H439.963z"/>
|
||||
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="593.5845" y1="98.9678" x2="593.5845" y2="116.1672">
|
||||
<stop offset="0" style="stop-color:#D20000"/>
|
||||
<stop offset="1" style="stop-color:#020101"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_4_)" d="M623.365,102.392v13.572h-56.797c-0.43-4.694-1.369-9.228-2.763-13.572H623.365z"/>
|
||||
</g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="408.6396" y1="138.3242" x2="408.6396" y2="152.4173">
|
||||
<stop offset="0" style="stop-color:#D20000"/>
|
||||
<stop offset="1" style="stop-color:#020101"/>
|
||||
</linearGradient>
|
||||
<rect x="380.402" y="139.777" fill="url(#SVGID_5_)" width="56.476" height="13.568"/>
|
||||
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="595.127" y1="138.3242" x2="595.127" y2="152.4173">
|
||||
<stop offset="0" style="stop-color:#D20000"/>
|
||||
<stop offset="1" style="stop-color:#020101"/>
|
||||
</linearGradient>
|
||||
<rect x="566.889" y="139.777" fill="url(#SVGID_6_)" width="56.476" height="13.568"/>
|
||||
</g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="410.2896" y1="176.3193" x2="410.2896" y2="193.0743">
|
||||
<stop offset="0" style="stop-color:#D20000"/>
|
||||
<stop offset="1" style="stop-color:#020101"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_7_)" d="M437.254,177.159c0.483,4.694,1.475,9.223,2.923,13.572h-59.775v-13.572H437.254z"/>
|
||||
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="593.4771" y1="176.3193" x2="593.4771" y2="193.0743">
|
||||
<stop offset="0" style="stop-color:#D20000"/>
|
||||
<stop offset="1" style="stop-color:#020101"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_8_)" d="M623.365,177.159v13.572h-59.775c1.448-4.35,2.441-8.878,2.924-13.572H623.365z"/>
|
||||
</g>
|
||||
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="423.3101" y1="146.2383" x2="580.458" y2="146.2383">
|
||||
<stop offset="0" style="stop-color:#D20000"/>
|
||||
<stop offset="1" style="stop-color:#020101"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_9_)" d="M580.19,115.964c-0.349-4.671-1.127-9.199-2.254-13.572
|
||||
c-8.794-33.763-39.555-58.784-76.053-58.784s-67.257,25.021-76.053,58.784c-1.126,4.373-1.904,8.901-2.254,13.572
|
||||
c-0.188,2.038-0.267,4.133-0.267,6.223v48.108c0,2.307,0.107,4.614,0.321,6.864c0.375,4.642,1.179,9.171,2.387,13.572
|
||||
c8.983,33.438,39.582,58.139,75.865,58.139c36.284,0,66.882-24.701,75.866-58.139c1.208-4.401,2.012-8.931,2.388-13.572
|
||||
c0.213-2.25,0.321-4.558,0.321-6.864v-48.108C580.458,120.097,580.378,118.002,580.19,115.964z M566.889,170.295
|
||||
c0,2.307-0.134,4.614-0.376,6.864c-0.482,4.694-1.476,9.223-2.924,13.572c-8.555,25.876-32.984,44.595-61.706,44.595
|
||||
c-28.721,0-53.15-18.719-61.706-44.595c-1.448-4.35-2.44-8.878-2.923-13.572c-0.241-2.25-0.376-4.558-0.376-6.864v-48.108
|
||||
c0-2.09,0.107-4.185,0.323-6.223c0.429-4.694,1.368-9.228,2.762-13.572c8.394-26.201,32.958-45.212,61.92-45.212
|
||||
s53.527,19.011,61.921,45.212c1.394,4.345,2.333,8.878,2.763,13.572c0.214,2.038,0.322,4.133,0.322,6.223V170.295z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#1D1D2C" d="M10.784,569.702V469.555H73.22c16.924,0.193,27.735,0.613,32.43,1.245
|
||||
c8.265,1.075,13.988,4.793,17.168,11.151c2.103,4.256,3.155,10.199,3.155,17.828c0,5.773-0.318,10.161-0.954,13.171
|
||||
c-0.637,3.01-1.761,5.416-3.375,7.228c-2.594,2.887-6.652,4.916-12.18,6.09c4.303,0.736,7.728,2.396,10.272,4.991
|
||||
c1.908,2.052,3.13,4.496,3.669,7.336c0.537,2.835,0.807,8.463,0.807,16.874v14.233h-13.354v-9.614
|
||||
c0-8.166-0.172-13.813-0.513-16.945c-0.979-6.359-4.574-10.077-10.787-11.152c-2.446-0.34-4.634-0.552-6.564-0.623
|
||||
c-1.934-0.075-8.524-0.137-19.774-0.184H24.136v38.519H10.784z M24.136,519.441H73.22c13.011,0,21.35-0.179,25.019-0.547
|
||||
c3.669-0.368,6.652-1.283,8.952-2.75c3.422-2.251,5.135-7.779,5.135-16.582c0-7.581-1.588-12.497-4.768-14.747
|
||||
c-2.054-1.468-5.272-2.425-9.648-2.864c-4.378-0.438-12.607-0.66-24.689-0.66H24.136V519.441z"/>
|
||||
<path fill="#1D1D2C" d="M221.71,548.204h11.52c-0.197,8.317-3.131,14.124-8.805,17.427c-5.674,3.302-15.552,4.953-29.64,4.953
|
||||
c-11.642,0-20.482-0.453-26.523-1.358c-6.043-0.906-10.601-2.435-13.683-4.586c-7.044-4.991-10.566-14.846-10.566-29.569
|
||||
c0-10.316,1.492-18.072,4.475-23.257c2.836-4.939,6.935-8.279,12.289-10.016c5.358-1.735,14.295-2.604,26.816-2.604
|
||||
c11.984,0,20.642,0.637,25.975,1.905c5.33,1.274,9.585,3.718,12.765,7.341c2.643,3.033,4.452,6.553,5.431,10.563
|
||||
c0.977,4.01,1.516,10.128,1.613,18.342h-77.551c0.294,8.661,2.005,14.601,5.136,17.832c2.102,2.198,5.466,3.703,10.087,4.51
|
||||
c4.622,0.807,12.143,1.212,22.563,1.212c7.875,0,13.646-0.367,17.314-1.104c3.669-0.73,6.406-2.075,8.217-4.033
|
||||
C220.512,554.247,221.37,551.729,221.71,548.204z M221.197,527.957c-0.048-6.803-1.859-11.544-5.429-14.238
|
||||
c-2.348-1.807-5.639-3.066-9.868-3.778c-4.233-0.708-10.433-1.062-18.6-1.062c-7.142,0-12.583,0.33-16.325,0.991
|
||||
c-3.741,0.66-6.665,1.82-8.768,3.485c-3.473,2.689-5.479,7.553-6.016,14.602H221.197z"/>
|
||||
<path fill="#1D1D2C" d="M252.958,469.555h11.006v100.147h-11.006V469.555z"/>
|
||||
<path fill="#1D1D2C" d="M361.682,548.204h11.52c-0.197,8.317-3.131,14.124-8.805,17.427c-5.674,3.302-15.554,4.953-29.64,4.953
|
||||
c-11.642,0-20.483-0.453-26.523-1.358c-6.043-0.906-10.603-2.435-13.683-4.586c-7.045-4.991-10.566-14.846-10.566-29.569
|
||||
c0-10.316,1.492-18.072,4.475-23.257c2.836-4.939,6.934-8.279,12.289-10.016c5.358-1.735,14.295-2.604,26.816-2.604
|
||||
c11.983,0,20.642,0.637,25.974,1.905c5.331,1.274,9.586,3.718,12.766,7.341c2.643,3.033,4.452,6.553,5.431,10.563
|
||||
c0.977,4.01,1.516,10.128,1.613,18.342h-77.551c0.294,8.661,2.004,14.601,5.135,17.832c2.103,2.198,5.466,3.703,10.088,4.51
|
||||
c4.622,0.807,12.143,1.212,22.561,1.212c7.875,0,13.646-0.367,17.316-1.104c3.667-0.73,6.406-2.075,8.216-4.033
|
||||
C360.484,554.247,361.34,551.729,361.682,548.204z M361.169,527.957c-0.048-6.803-1.86-11.544-5.43-14.238
|
||||
c-2.347-1.807-5.637-3.066-9.868-3.778c-4.231-0.708-10.431-1.062-18.6-1.062c-7.141,0-12.583,0.33-16.324,0.991
|
||||
c-3.741,0.66-6.665,1.82-8.768,3.485c-3.473,2.689-5.479,7.553-6.016,14.602H361.169z"/>
|
||||
<path fill="#1D1D2C" d="M404.447,520.984h-11.445c-0.099-1.27-0.147-2.273-0.147-3.01c0-7.284,2.739-12.252,8.217-14.894
|
||||
c5.477-2.642,15.75-3.962,30.816-3.962c11.836,0,20.347,0.542,25.532,1.613c5.183,1.08,9.245,3.132,12.179,6.165
|
||||
c2.445,2.448,4.035,5.331,4.769,8.657c0.733,3.325,1.1,9.146,1.1,17.464v36.684h-10.93l0.22-8.143h-0.589
|
||||
c-2.739,3.816-6.028,6.27-9.868,7.373c-3.84,1.1-11.089,1.651-21.752,1.651c-12.181,0-20.642-0.297-25.388-0.882
|
||||
c-4.743-0.585-8.339-1.808-10.784-3.671c-2.446-1.76-4.146-3.858-5.1-6.307c-0.953-2.444-1.432-5.869-1.432-10.271
|
||||
c0-5.382,0.722-9.515,2.166-12.402c1.442-2.882,3.875-5.038,7.3-6.453c3.716-1.618,12.4-2.425,26.045-2.425
|
||||
c14.282,0,23.626,0.43,28.028,1.283c4.402,0.858,7.801,2.925,10.198,6.203h0.956v-7.486c0-6.114-1.103-10.516-3.304-13.204
|
||||
c-1.955-2.396-4.927-4.024-8.914-4.883c-3.987-0.854-10.503-1.283-19.552-1.283c-11.887,0-19.579,0.698-23.076,2.095
|
||||
c-3.498,1.392-5.246,4.439-5.246,9.133C404.447,520.276,404.447,520.593,404.447,520.984z M432.621,537.934
|
||||
c-9.05,0-15.385,0.156-19.002,0.477c-3.622,0.316-6.189,0.916-7.705,1.798c-2.887,1.712-4.329,4.868-4.329,9.464
|
||||
c0,4.547,1.712,7.633,5.135,9.246c2.789,1.32,12.179,1.98,28.174,1.98c11.69,0,19.418-0.759,23.186-2.278
|
||||
c3.766-1.514,5.648-4.595,5.648-9.241c0-4.5-2.102-7.529-6.309-9.1C453.213,538.717,444.946,537.934,432.621,537.934z"/>
|
||||
<path fill="#1D1D2C" d="M494.095,545.416h11.74c0,0.981,0,1.642,0,1.981c0,3.77,0.404,6.458,1.212,8.071
|
||||
c0.807,1.613,2.408,2.859,4.803,3.741c3.18,1.128,12.327,1.688,27.442,1.688c9.438,0,16.005-0.207,19.7-0.622
|
||||
c3.691-0.415,6.345-1.236,7.958-2.463c2.203-1.66,3.304-4.227,3.304-7.698c0-4.209-1.321-7.082-3.964-8.624
|
||||
c-2.641-1.543-7.46-2.288-14.452-2.236c-2.201,0.048-12.792-0.075-31.769-0.368c-4.94-0.099-8.891-0.49-11.849-1.174
|
||||
c-2.961-0.685-5.418-1.783-7.374-3.303c-3.718-2.887-5.576-7.751-5.576-14.601c0-7.236,2.322-12.497,6.969-15.775
|
||||
c2.446-1.708,6.456-2.943,12.033-3.703c5.576-0.76,13.598-1.137,24.065-1.137c11.69,0,20.31,0.637,25.863,1.905
|
||||
c5.55,1.274,9.624,3.524,12.216,6.751c1.27,1.566,2.092,3.218,2.459,4.954c0.367,1.735,0.548,4.66,0.548,8.765h-11.593
|
||||
c-0.047-4.255-0.758-7.044-2.126-8.359c-1.516-1.52-4.45-2.618-8.804-3.303c-4.353-0.688-10.664-1.028-18.929-1.028
|
||||
c-15.066,0-24.211,1.053-27.442,3.156c-2.347,1.566-3.52,4.203-3.52,7.921c0,3.816,1.368,6.335,4.107,7.558
|
||||
c2.74,1.222,8.268,1.764,16.582,1.617c8.218-0.15,18.856,0.048,31.916,0.586c7.923,0.292,13.635,2.028,17.131,5.207
|
||||
c3.498,3.18,5.247,8.219,5.247,15.115c0,4.156-0.612,7.534-1.835,10.124c-1.222,2.595-3.204,4.722-5.942,6.383
|
||||
c-2.397,1.467-6.311,2.51-11.739,3.118c-5.43,0.613-13.281,0.92-23.552,0.92c-11.494,0-20.019-0.345-25.569-1.028
|
||||
c-5.552-0.685-9.625-1.906-12.216-3.671c-2.641-1.759-4.487-3.887-5.54-6.382c-1.052-2.496-1.578-5.968-1.578-10.417
|
||||
C494.022,548.496,494.046,547.275,494.095,545.416z"/>
|
||||
<path fill="#1D1D2C" d="M677.144,548.204h11.518c-0.195,8.317-3.13,14.124-8.803,17.427c-5.676,3.302-15.555,4.953-29.642,4.953
|
||||
c-11.642,0-20.482-0.453-26.523-1.358c-6.041-0.906-10.602-2.435-13.683-4.586c-7.043-4.991-10.566-14.846-10.566-29.569
|
||||
c0-10.316,1.493-18.072,4.477-23.257c2.837-4.939,6.934-8.279,12.289-10.016c5.356-1.735,14.295-2.604,26.817-2.604
|
||||
c11.982,0,20.64,0.637,25.972,1.905c5.331,1.274,9.587,3.718,12.767,7.341c2.641,3.033,4.45,6.553,5.429,10.563
|
||||
s1.516,10.128,1.613,18.342h-77.551c0.294,8.661,2.006,14.601,5.136,17.832c2.103,2.198,5.466,3.703,10.088,4.51
|
||||
c4.623,0.807,12.144,1.212,22.562,1.212c7.875,0,13.646-0.367,17.314-1.104c3.669-0.73,6.409-2.075,8.219-4.033
|
||||
C675.946,554.247,676.801,551.729,677.144,548.204z M676.63,527.957c-0.049-6.803-1.858-11.544-5.43-14.238
|
||||
c-2.348-1.807-5.636-3.066-9.868-3.778c-4.23-0.708-10.431-1.062-18.599-1.062c-7.144,0-12.583,0.33-16.326,0.991
|
||||
c-3.741,0.66-6.664,1.82-8.768,3.485c-3.472,2.689-5.477,7.553-6.015,14.602H676.63z"/>
|
||||
<path fill="#E40C2B" d="M709.343,469.555h26.707l54.367,84.886l53.486-84.886h26.854v100.147h-13.354v-87.751h-5.576
|
||||
l-55.907,87.751h-10.86l-56.493-87.751h-5.87v87.751h-13.353V469.555z"/>
|
||||
<path fill="#E40C2B" d="M894.74,569.702V469.555h98.167v11.736h-84.816v30.598h81.221v11.737h-81.221v34.334h84.889v11.742H894.74
|
||||
z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
67
app/assets/javascripts/appearance_release_import.js
Normal file
@@ -0,0 +1,67 @@
|
||||
function beforeUnloadHandler(e){
|
||||
e.preventDefault();
|
||||
e.returnValue = '';
|
||||
}
|
||||
|
||||
function turbolinksClickHandler(){
|
||||
return false;
|
||||
}
|
||||
|
||||
let eventListenersAdded = false;
|
||||
|
||||
function addEventListenersForUploadProgresBar(){
|
||||
if (eventListenersAdded) return;
|
||||
eventListenersAdded = true;
|
||||
addEventListener("direct-upload:initialize", event => {
|
||||
const { detail } = event;
|
||||
const { id, file } = detail;
|
||||
const progressBar = App.FileUploadProgress.createProgressBar(id);
|
||||
|
||||
$('#upload-progress-container').append(progressBar);
|
||||
});
|
||||
|
||||
addEventListener("direct-upload:start", event => {
|
||||
const { id } = event.detail;
|
||||
App.FileUploadProgress.showProgressBar(id);
|
||||
})
|
||||
|
||||
addEventListener("direct-upload:progress", event => {
|
||||
const { id, progress } = event.detail;
|
||||
if (App.FileUploadProgress._findProgressBar(id)) {
|
||||
App.FileUploadProgress.updateProgressBar(id, progress);
|
||||
}
|
||||
})
|
||||
|
||||
addEventListener("direct-upload:error", event => {
|
||||
event.preventDefault();
|
||||
const { id, error } = event.detail;
|
||||
App.FileUploadProgress.showError(id, error);
|
||||
})
|
||||
|
||||
addEventListener("direct-upload:end", event => {
|
||||
const { id } = event.detail;
|
||||
App.FileUploadProgress.removeProgressBar(id)
|
||||
})
|
||||
}
|
||||
|
||||
$(document).on("turbolinks:load", function() {
|
||||
addEventListenersForUploadProgresBar();
|
||||
window.removeEventListener('beforeunload', beforeUnloadHandler, true);
|
||||
$(document).off('turbolinks:click', turbolinksClickHandler);
|
||||
|
||||
$("#import_appearance_releases").click(function(e) {
|
||||
$('#appearance_release_file_upload').click();
|
||||
});
|
||||
|
||||
$('#appearance_release_file_upload').change(function(e) {
|
||||
$('#import_appearance_releases')
|
||||
.html('Importing...')
|
||||
.attr('disabled', 'disabled');
|
||||
|
||||
window.addEventListener('beforeunload', beforeUnloadHandler, true);
|
||||
$(document).on('turbolinks:click', turbolinksClickHandler);
|
||||
|
||||
const fileUploadForm = document.getElementById('appearance_releases_import');
|
||||
Rails.fire(fileUploadForm, 'submit');
|
||||
});
|
||||
});
|
||||
21
app/assets/javascripts/application.js
Normal file
@@ -0,0 +1,21 @@
|
||||
//= require rails-ujs
|
||||
//= require jquery3
|
||||
//= require activestorage
|
||||
//= require turbolinks
|
||||
//= require turbolinks-csp
|
||||
//= require popper
|
||||
//= require bootstrap-sprockets
|
||||
//= require bootstrap-datepicker
|
||||
//= require bs-custom-file-input/dist/bs-custom-file-input
|
||||
//= require clappr/dist/clappr
|
||||
//= require clappr-markers-plugin/dist/clappr-markers-plugin
|
||||
//= require clappr-playback-rate-plugin/dist/clappr-playback-rate-plugin
|
||||
//= require spark-md5/spark-md5
|
||||
//= require js-sha256/src/sha256
|
||||
//= require evaporate/evaporate
|
||||
//= require exif-js
|
||||
//= require signature_pad/dist/signature_pad
|
||||
//= require signature_pad-extensions
|
||||
//= require smpte-timecode/smpte-timecode
|
||||
//= require trix/dist/trix
|
||||
//= require_tree .
|
||||
13
app/assets/javascripts/cable.js
Normal file
@@ -0,0 +1,13 @@
|
||||
// Action Cable provides the framework to deal with WebSockets in Rails.
|
||||
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
|
||||
//
|
||||
//= require action_cable
|
||||
//= require_self
|
||||
//= require_tree ./channels
|
||||
|
||||
(function() {
|
||||
this.App || (this.App = {});
|
||||
|
||||
App.cable = ActionCable.createConsumer();
|
||||
|
||||
}).call(this);
|
||||
0
app/assets/javascripts/channels/.keep
Normal file
45
app/assets/javascripts/channels/broadcasts.coffee
Normal file
@@ -0,0 +1,45 @@
|
||||
$(document).on "turbolinks:load", ->
|
||||
# Only connect if a broadcast-token meta tag is present
|
||||
broadcast_meta = document.querySelector("meta[name=broadcast-token]")
|
||||
return unless broadcast_meta
|
||||
|
||||
broadcastToken = broadcast_meta.getAttribute("content")
|
||||
|
||||
App.public = App.cable.subscriptions.create { channel: "BroadcastsChannel", token: broadcastToken },
|
||||
connected: ->
|
||||
# Called when the subscription is ready for use on the server
|
||||
|
||||
disconnected: ->
|
||||
# Called when the subscription has been terminated by the server
|
||||
|
||||
received: (data) ->
|
||||
return unless document.querySelector("meta[name=broadcast-token][content='#{broadcastToken}']")
|
||||
switch data.event
|
||||
when "broadcast_stream_update" then @refreshBroadcastVideo(data)
|
||||
when "stream_recording_ready" then @showBroadcastRecordings(data)
|
||||
when "file_upload_update" then @refreshFilesTab(data)
|
||||
|
||||
refreshBroadcastVideo: (data) ->
|
||||
$("#broadcast_updates").html data.status_content
|
||||
if data.streamer_status == 'recording' && data.status == 'active'
|
||||
$("#broadcast_video").html data.video_content
|
||||
new (Clappr.Player)(
|
||||
parentId: '#broadcast_video'
|
||||
source: data.playback_url
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
mute: true,
|
||||
autoPlay: true,
|
||||
hlsMinimumDvrSize: 1)
|
||||
if data.streamer_status == "idle" && data.status == "idle"
|
||||
$("#broadcast_video").html data.video_content
|
||||
|
||||
showBroadcastRecordings: (data) ->
|
||||
$(".flash-message").html data.flash_content
|
||||
$("#broadcast_recordings").html data.recordings_content
|
||||
$("#broadcast_recordings_nav").html data.recordings_nav_content
|
||||
|
||||
refreshFilesTab: (data) ->
|
||||
$("#broadcast_file_list").html data.files_content
|
||||
$("#broadcast_files_pagination").html data.pagination_content
|
||||
|
||||
25
app/assets/javascripts/channels/projects.coffee
Normal file
@@ -0,0 +1,25 @@
|
||||
# TODO: Need to unsubscribe as project changes
|
||||
$(document).on "turbolinks:load", ->
|
||||
# Only connect if a project-id meta tag is present
|
||||
return unless meta = document.querySelector("meta[name=project-id]")
|
||||
# Get the id of the project to subscribe to
|
||||
projectId = meta.getAttribute("content")
|
||||
|
||||
App.projects = App.cable.subscriptions.create { channel: "ProjectsChannel", id: projectId },
|
||||
connected: ->
|
||||
# Called when the subscription is ready for use on the server
|
||||
|
||||
disconnected: ->
|
||||
# Called when the subscription has been terminated by the server
|
||||
|
||||
received: (data) ->
|
||||
switch data.event
|
||||
when "video_status_update" then @showVideoStatusUpdate(data.content)
|
||||
when "download_status_update" then @showDownloadStatusUpdate(data.content)
|
||||
when "conference_recording_ready" then @showDownloadStatusUpdate(data.content)
|
||||
|
||||
showVideoStatusUpdate: (content) ->
|
||||
$("[data-ujs-target='video-analysis-msg']").replaceWith content
|
||||
|
||||
showDownloadStatusUpdate: (content) ->
|
||||
$(".flash-message").html content
|
||||
18
app/assets/javascripts/clipboard.js
Normal file
@@ -0,0 +1,18 @@
|
||||
$(document).on("click", "[data-behavior=clipboard]", function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Copy the value of the link's `href` attribute
|
||||
var value = $(this).attr("href");
|
||||
|
||||
// Creates a temporary input field to copy from
|
||||
var copyToClipboard = function(value) {
|
||||
var $temp = $("<input>");
|
||||
$("body").append($temp);
|
||||
$temp.val(value).select();
|
||||
document.execCommand("copy");
|
||||
$temp.remove();
|
||||
}
|
||||
|
||||
// Perform the copying
|
||||
copyToClipboard(value);
|
||||
});
|
||||
11
app/assets/javascripts/collapse_select.js
Normal file
@@ -0,0 +1,11 @@
|
||||
$(document).on("change", "[data-toggle=collapse-select]", function(event) {
|
||||
const select = event.target;
|
||||
const target = select.dataset.target;
|
||||
const showValues = JSON.parse(select.dataset.showValues);
|
||||
|
||||
if (showValues.indexOf(select.value) > -1) {
|
||||
$(target).show("fast");
|
||||
} else {
|
||||
$(target).hide("fast");
|
||||
}
|
||||
});
|
||||
64
app/assets/javascripts/collapse_state.js
Normal file
@@ -0,0 +1,64 @@
|
||||
if (typeof(Storage) !== "undefined") {
|
||||
|
||||
class CollapseState {
|
||||
static get _KEY() { return "collapseState"; }
|
||||
|
||||
set(id, value) {
|
||||
const state = this._state;
|
||||
state[id] = value;
|
||||
window.localStorage.setItem(CollapseState._KEY, JSON.stringify(state));
|
||||
}
|
||||
|
||||
get(id) {
|
||||
const state = this._state;
|
||||
return state[id];
|
||||
}
|
||||
|
||||
get _state() {
|
||||
if (window.localStorage.getItem(CollapseState._KEY) === null) {
|
||||
window.localStorage.setItem(CollapseState._KEY, JSON.stringify({}));
|
||||
}
|
||||
|
||||
const data = window.localStorage.getItem(CollapseState._KEY);
|
||||
return JSON.parse(data);
|
||||
}
|
||||
}
|
||||
|
||||
// EVENT HANDLER - Store state when element is hidden
|
||||
$(document).on("hidden.bs.collapse", (event) => {
|
||||
const state = new CollapseState();
|
||||
state.set(event.target.id, "hide");
|
||||
});
|
||||
|
||||
// EVENT HANDLER - Store state when element is shown
|
||||
$(document).on("shown.bs.collapse", (event) => {
|
||||
const state = new CollapseState();
|
||||
state.set(event.target.id, "show");
|
||||
});
|
||||
|
||||
// Set initial state of elements from storage
|
||||
$(document).on("turbolinks:load", function() {
|
||||
const state = new CollapseState();
|
||||
const collapsibles = document.querySelectorAll("[data-toggle=collapse][data-behavior=stateful]");
|
||||
|
||||
collapsibles.forEach( (collapsible) => {
|
||||
const targetSelector = collapsible.dataset.target.toString().replace("#", "");
|
||||
const target = document.getElementById(targetSelector);
|
||||
const value = state.get(target.id);
|
||||
|
||||
switch (value) {
|
||||
case "show":
|
||||
collapsible.classList.remove("collapsed");
|
||||
target.classList.add("show");
|
||||
break;
|
||||
case "hide":
|
||||
collapsible.classList.add("collapsed");
|
||||
target.classList.remove("show");
|
||||
break;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
} else {
|
||||
console.log("No Web Storage support..");
|
||||
}
|
||||
3
app/assets/javascripts/custom_file_input.js
Normal file
@@ -0,0 +1,3 @@
|
||||
$(document).on("turbolinks:load", function() {
|
||||
bsCustomFileInput.init()
|
||||
})
|
||||
55
app/assets/javascripts/digital_signature.js
Normal file
@@ -0,0 +1,55 @@
|
||||
App.DigitalSignature = {
|
||||
init: function(canvas) {
|
||||
var signatureInput = $(canvas).data("signature-input");
|
||||
var $clearButton = $("[data-behavior=clear-digital-signature]")
|
||||
|
||||
var opts = {
|
||||
onEnd: function() {
|
||||
// Save the signature to an input field
|
||||
if ($(signatureInput).length) {
|
||||
$(signatureInput).val(signaturePad.crop().toDataURL());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize using SignaturePad plugin
|
||||
var signaturePad = new SignaturePad(canvas, opts);
|
||||
|
||||
// Handler for when the browser window is resized
|
||||
// Stores the current signature and restores it after the resizing
|
||||
function resizeCanvas() {
|
||||
var cachedSignature = null;
|
||||
|
||||
if (!signaturePad.isEmpty()) {
|
||||
var cachedSignature = signaturePad.toDataURL();
|
||||
}
|
||||
|
||||
var ratio = Math.max(window.devicePixelRatio || 1, 1);
|
||||
canvas.width = canvas.offsetWidth * ratio;
|
||||
canvas.height = canvas.offsetHeight * ratio;
|
||||
canvas.getContext("2d").scale(ratio, ratio);
|
||||
signaturePad.clear(); // otherwise isEmpty() might return incorrect value
|
||||
|
||||
if (cachedSignature !== null) {
|
||||
signaturePad.fromDataURL(cachedSignature);
|
||||
}
|
||||
}
|
||||
|
||||
// Attach resize handler and trigger it once for an initial sizing
|
||||
window.addEventListener("resize", resizeCanvas);
|
||||
resizeCanvas();
|
||||
|
||||
// Attach clear button handler
|
||||
$clearButton.on("click", function(e) {
|
||||
e.preventDefault();
|
||||
signaturePad.clear();
|
||||
$(signatureInput).val("");
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
$(document).on("turbolinks:load", function() {
|
||||
$("[data-behavior=digital-signature]").each(function(index, element) {
|
||||
App.DigitalSignature.init(element);
|
||||
});
|
||||
});
|
||||
31
app/assets/javascripts/evaporate.js
Normal file
@@ -0,0 +1,31 @@
|
||||
$(document).on("click", "#video_submit_button", (event) => {
|
||||
const input = $("#video_file");
|
||||
|
||||
if (input[0].files.length > 0) {
|
||||
event.preventDefault();
|
||||
|
||||
const bucket = input.data("aws-bucket");
|
||||
const awsAccessKeyId = input.data("aws-access-key-id");
|
||||
const signerUrl = input.data("signer-url");
|
||||
|
||||
const uploader = Evaporate.create({
|
||||
logging: false,
|
||||
signerUrl: signerUrl,
|
||||
aws_key: awsAccessKeyId,
|
||||
bucket: bucket,
|
||||
cloudfront: false,
|
||||
computeContentMd5: true,
|
||||
cryptoMd5Method: (d) => btoa(SparkMD5.ArrayBuffer.hash(d, true)),
|
||||
cryptoHexEncodedHash256: sha256,
|
||||
});
|
||||
|
||||
const submitButton = $(event.target);
|
||||
submitButton.prop('disabled', true);
|
||||
|
||||
const allFilesCompleted = () => {
|
||||
input.value = '';
|
||||
$("#video_form").submit();
|
||||
}
|
||||
uploadFile(input[0].files[0], uploader, input, allFilesCompleted)
|
||||
}
|
||||
});
|
||||
59
app/assets/javascripts/file_upload_progress.js
Normal file
@@ -0,0 +1,59 @@
|
||||
App.FileUploadProgress = {
|
||||
// Creates a new progress bar element
|
||||
createProgressBar: id => {
|
||||
// <div class="progress">
|
||||
const progress = document.createElement("div")
|
||||
progress.setAttribute("id", `progress_bar_${id}`)
|
||||
progress.setAttribute("hidden", "hidden")
|
||||
progress.classList.add("mt-2")
|
||||
progress.classList.add("progress")
|
||||
|
||||
// <div class="progress-bar">
|
||||
const progressBar = document.createElement("div")
|
||||
progressBar.setAttribute("role", "progressbar")
|
||||
progressBar.setAttribute("aria-valuenow", "0")
|
||||
progressBar.setAttribute("aria-valuemin", "0")
|
||||
progressBar.setAttribute("aria-valuemax", "100")
|
||||
progressBar.classList.add("progress-bar")
|
||||
|
||||
progress.appendChild(progressBar)
|
||||
|
||||
return progress
|
||||
},
|
||||
|
||||
// Unhides an existing progress bar
|
||||
showProgressBar: id => {
|
||||
const progressBar = App.FileUploadProgress._findProgressBar(id)
|
||||
progressBar.removeAttribute("hidden")
|
||||
progressBar.firstChild.setAttribute("style", "width: 0%")
|
||||
},
|
||||
|
||||
// Updates the progress of an existing progress bar
|
||||
updateProgressBar: (id, progress) => {
|
||||
const progressBar = App.FileUploadProgress._findProgressBar(id)
|
||||
progressBar.firstChild.setAttribute("style", `width: ${progress}%`)
|
||||
progressBar.firstChild.setAttribute("aria-valuenow", progress)
|
||||
},
|
||||
|
||||
// Shows an error message
|
||||
showError: (id, error) => {
|
||||
// <div class="text-danger>...</div>
|
||||
const errorMsg = document.createElement("div")
|
||||
errorMsg.classList.add("text-danger")
|
||||
errorMsg.appendChild( document.createTextNode(error) )
|
||||
|
||||
const progressBar = App.FileUploadProgress._findProgressBar(id)
|
||||
progressBar.parentNode.replaceChild(errorMsg, progressBar)
|
||||
},
|
||||
|
||||
// Removes a progress bar element from the document
|
||||
removeProgressBar: id => {
|
||||
const progressBar = App.FileUploadProgress._findProgressBar(id)
|
||||
progressBar.parentNode.removeChild(progressBar)
|
||||
},
|
||||
|
||||
// Finds a progress bar element by the given ID
|
||||
_findProgressBar: id => {
|
||||
return document.getElementById(`progress_bar_${id}`)
|
||||
}
|
||||
}
|
||||
50
app/assets/javascripts/multi_view_broadcasts.js
Normal file
@@ -0,0 +1,50 @@
|
||||
$(document).on("click", "[data-behavior=select_broadcast]", function() {
|
||||
var _this = this;
|
||||
var checkbox = $(this).children("input:checkbox");
|
||||
|
||||
selectBroadcast(_this, checkbox);
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-behavior=select_broadcast] input[type='checkbox']", function(e) {
|
||||
e.stopPropagation();
|
||||
|
||||
var _this = this;
|
||||
var checkbox = $(this);
|
||||
|
||||
selectBroadcast(_this, checkbox);
|
||||
});
|
||||
|
||||
function selectBroadcast(clicked_element, checkbox) {
|
||||
if (clicked_element.hasChildNodes()) {
|
||||
if(checkbox.prop("checked")) {
|
||||
checkbox.prop('checked', false);
|
||||
} else {
|
||||
checkbox.prop('checked', true);
|
||||
}
|
||||
}
|
||||
|
||||
var checked = checkbox.prop("checked");
|
||||
var project_id = JSON.parse($('#multi_view_broadcasts').parent().attr('data-project-id'));
|
||||
var broadcast_ids = JSON.parse($('#multi_view_broadcasts').parent().attr('data-broadcast-ids'));
|
||||
var selected_broadcast_id = checkbox.val();
|
||||
|
||||
if (checked && !broadcast_ids.includes(selected_broadcast_id)) {
|
||||
broadcast_ids.push(selected_broadcast_id);
|
||||
} else if(!checked && broadcast_ids.includes(selected_broadcast_id)) {
|
||||
broadcast_ids.splice( $.inArray(selected_broadcast_id, broadcast_ids), 1 );
|
||||
}
|
||||
|
||||
$('#multi_view_broadcasts').parent().attr('data-broadcast-ids', JSON.stringify(broadcast_ids));
|
||||
|
||||
if (broadcast_ids.length >= 2) {
|
||||
multi_view_ids = $.param({multi_view_ids: broadcast_ids});
|
||||
broadcast_url = "/en/projects/" + project_id + "/broadcasts/" + broadcast_ids[0] + "?" + multi_view_ids
|
||||
|
||||
$("#multi_view_broadcasts").attr("href", broadcast_url);
|
||||
$("#multi_view_broadcasts").attr("target", "_blank");
|
||||
$("#multi_view_broadcasts").removeClass('disabled');
|
||||
} else if (broadcast_ids.length < 2) {
|
||||
$("#multi_view_broadcasts").attr("href", "javascript:void(0);");
|
||||
$("#multi_view_broadcasts").addClass('disabled');
|
||||
}
|
||||
}
|
||||
60
app/assets/javascripts/photo_preview.js
Normal file
@@ -0,0 +1,60 @@
|
||||
App.PhotoPreview = {
|
||||
init: function(element) {
|
||||
var fileInput = $(element).data("file-input");
|
||||
|
||||
// Listen for changes on the file input field
|
||||
$(fileInput).on("change", function(e) {
|
||||
var input = e.target;
|
||||
|
||||
if (input.files && input.files[0]) {
|
||||
var reader = new FileReader();
|
||||
|
||||
// Load a preview of the image and append to the container
|
||||
reader.onload = function (e) {
|
||||
App.PhotoPreview.set(element, e.target.result);
|
||||
}
|
||||
|
||||
reader.readAsDataURL(input.files[0]);
|
||||
}
|
||||
});
|
||||
},
|
||||
set: function(element, imgSrc) {
|
||||
var img = $("<img>").attr('src', imgSrc).addClass("img-thumbnail photo-preview");
|
||||
$(element).html(img);
|
||||
App.PhotoPreview.autoOrient(img.get(0));
|
||||
},
|
||||
autoOrient: function(image) {
|
||||
// Get the EXIF data from the image
|
||||
EXIF.getData(image, function() {
|
||||
var orientation = EXIF.getTag(this, "Orientation");
|
||||
|
||||
// Rotate the image to ensure it appears right side up
|
||||
switch (orientation) {
|
||||
case 6:
|
||||
$(image).css('transform', 'rotate(90deg)');
|
||||
break;
|
||||
case 8:
|
||||
$(image).css('transform', 'rotate(270deg)');
|
||||
break;
|
||||
case 3:
|
||||
$(image).css('transform', 'rotate(180deg)');
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$(document).on("turbolinks:load", function() {
|
||||
$("[data-behavior=person-photo-preview]").each(function(index, element) {
|
||||
App.PhotoPreview.init(element);
|
||||
});
|
||||
$("[data-behavior=guardian-photo-preview]").each(function(index, element) {
|
||||
App.PhotoPreview.init(element);
|
||||
});
|
||||
$("[data-behavior=take-person-photo]").click(function(e) {
|
||||
$("[data-ujs-target=person-photo-input]").trigger("click");
|
||||
});
|
||||
$("[data-behavior=take-guardian-photo]").click(function(e) {
|
||||
$("[data-ujs-target=guardian-photo-input]").trigger("click");
|
||||
});
|
||||
});
|
||||
24
app/assets/javascripts/play_previous_recordings.js
Normal file
@@ -0,0 +1,24 @@
|
||||
$(document).on("click", "[data-behavior=play_recording]", function() {
|
||||
if ($(this).hasClass('active')){
|
||||
return false;
|
||||
}
|
||||
|
||||
var playback_url = $(this).attr("data-playback-url")
|
||||
$("#broadcast_video").empty();
|
||||
|
||||
new Clappr.Player({
|
||||
parentId: '#broadcast_video',
|
||||
source: playback_url,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
autoPlay: true
|
||||
});
|
||||
|
||||
$(".dropdown-menu").children().removeClass('active');
|
||||
$(".dropdown-menu").children().children('i').remove();
|
||||
$(this).siblings().removeClass('active');
|
||||
$(this).siblings().children("i").remove();
|
||||
$(this).addClass('active');
|
||||
$(this).prepend('<i class="fa fa-check"> </i>');
|
||||
});
|
||||
|
||||
9
app/assets/javascripts/popover.js
Normal file
@@ -0,0 +1,9 @@
|
||||
// $(document).on("turbolinks:load", function() {
|
||||
// $('body').popover({
|
||||
// selector: '[data-toggle=popover]'
|
||||
// });
|
||||
// });
|
||||
|
||||
$(document).on("turbolinks:load", function() {
|
||||
$('[data-toggle=popover]').popover();
|
||||
});
|
||||
81
app/assets/javascripts/select_releasables.js
Normal file
@@ -0,0 +1,81 @@
|
||||
// Makes releasables selectable and taggable.
|
||||
|
||||
$(document).on("click", "[data-behavior=all-selectable]", function() {
|
||||
var checkbox = $(this).children("input:checkbox");
|
||||
var checked = checkbox.prop("checked");
|
||||
|
||||
if (checked) {
|
||||
checkbox.prop("checked", false);
|
||||
} else {
|
||||
checkbox.prop("checked", true);
|
||||
}
|
||||
|
||||
var checkedNewValue = checkbox.prop("checked");
|
||||
|
||||
if(checkedNewValue) {
|
||||
$("[data-behavior=select]").children('input:checkbox').each(function() {
|
||||
if (!this.checked) {
|
||||
$(this).parent().trigger('click')
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$("[data-behavior=select]").children('input:checkbox').each(function() {
|
||||
if (this.checked) {
|
||||
$(this).parent().trigger('click')
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-behavior=select]", function() {
|
||||
var checkbox = $(this).children("input:checkbox");
|
||||
var checked = checkbox.prop("checked");
|
||||
|
||||
var releasable_ids = JSON.parse($('#tag_all').parent().attr('data-releasable-ids'));
|
||||
var selected_releasable_id = $(this).children('input').val();
|
||||
|
||||
if (checked) {
|
||||
checkbox.prop("checked", false);
|
||||
} else {
|
||||
checkbox.prop("checked", true);
|
||||
}
|
||||
|
||||
var checkedNewValue = checkbox.prop("checked");
|
||||
|
||||
if (checkedNewValue && !releasable_ids.includes(selected_releasable_id)) {
|
||||
releasable_ids.push(selected_releasable_id);
|
||||
} else if(!checkedNewValue && releasable_ids.includes(selected_releasable_id)) {
|
||||
releasable_ids.splice( $.inArray(selected_releasable_id, releasable_ids), 1 );
|
||||
}
|
||||
|
||||
if (releasable_ids.length >= 1) {
|
||||
$("#tag_all").prop('disabled', false);
|
||||
} else if (releasable_ids.length < 1) {
|
||||
$("#tag_all").prop('disabled', true);
|
||||
}
|
||||
|
||||
$('#tag_all').parent().attr('data-releasable-ids', JSON.stringify(releasable_ids));
|
||||
});
|
||||
|
||||
$(document).on("click", "#tag_all", function(event) {
|
||||
event.preventDefault();
|
||||
var releasable_ids = JSON.parse($(this).parent().attr('data-releasable-ids'));
|
||||
|
||||
if (releasable_ids.length >= 1) {
|
||||
var input_ids = $('<input>').attr({ type: 'hidden', name: 'releasable_ids', value: JSON.stringify(releasable_ids) });
|
||||
var input_name = $('<input>').attr({ type: 'hidden', name: 'releasable_name', value: $("tbody").attr("id") });
|
||||
|
||||
$(this).parent().append(input_ids);
|
||||
$(this).parent().append(input_name);
|
||||
|
||||
Rails.fire($(this).parent()[0], 'submit');
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-behavior=select] input:checkbox", function() {
|
||||
$(this).parent().trigger('click');
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-behavior=all-selectable] input:checkbox", function() {
|
||||
$(this).parent().trigger('click');
|
||||
});
|
||||
5
app/assets/javascripts/tooltip.js
Normal file
@@ -0,0 +1,5 @@
|
||||
$(document).on("turbolinks:load", function() {
|
||||
$('body').tooltip({
|
||||
selector: '[data-toggle=tooltip]'
|
||||
});
|
||||
});
|
||||
4
app/assets/javascripts/trix.js
Normal file
@@ -0,0 +1,4 @@
|
||||
// Do not allow file attachments in rich text content
|
||||
addEventListener("trix-file-accept", function(event) {
|
||||
event.preventDefault();
|
||||
})
|
||||
12
app/assets/javascripts/turbolinks-csp.js
Normal file
@@ -0,0 +1,12 @@
|
||||
// Include CSP nonce for every Turbolinks request (see: content_security_policy.rb)
|
||||
document.addEventListener("turbolinks:request-start", function(event) {
|
||||
var xhr = event.data.xhr;
|
||||
xhr.setRequestHeader("X-Turbolinks-Nonce", $("meta[name='csp-nonce']").prop('content'));
|
||||
});
|
||||
|
||||
// Ensure all <script> tags on Turbolinks cached pages include a nonce
|
||||
document.addEventListener("turbolinks:before-cache", function() {
|
||||
$('script[nonce]').each(function(index, element) {
|
||||
$(element).attr('nonce', element.nonce);
|
||||
})
|
||||
})
|
||||
135
app/assets/javascripts/video_analysis.js
Normal file
@@ -0,0 +1,135 @@
|
||||
// TODO: Do we need to detach events when the page is changed?
|
||||
App.VideoAnalysis = {
|
||||
initPlayerWithAnalysis: function(videoElement, bookmarkData) {
|
||||
// Initialize the video player
|
||||
var player = App.VideoPlayer.init(videoElement);
|
||||
|
||||
// Attach custom keyboard shortcuts
|
||||
var oldBindKeyEvents = player.core.mediaControl.bindKeyEvents.bind(player.core.mediaControl);
|
||||
var oldUnbindKeyEvents = player.core.mediaControl.unbindKeyEvents.bind(player.core.mediaControl);
|
||||
player.core.mediaControl.bindKeyEvents = function() {
|
||||
oldBindKeyEvents();
|
||||
player.core.mediaControl.bindKeyAndShow('d', function() {
|
||||
var markersPlugin = player.getPlugin("markers-plugin");
|
||||
markersPlugin.getAll().forEach(function(marker, index) {
|
||||
if ( Math.abs(marker.getTime() - player.getCurrentTime()) < 2 ) {
|
||||
markersPlugin.removeMarker(marker);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
player.core.mediaControl.unbindKeyEvents = function() {
|
||||
console.log("Unbinding key events");
|
||||
oldUnbindKeyEvents();
|
||||
if (player.core.mediaControl.kibo) {
|
||||
player.core.mediaControl.kibo.off('d')
|
||||
}
|
||||
}
|
||||
|
||||
function ClassyMarker(time, tooltipText, cssClass) {
|
||||
this._cssClass = cssClass || null;
|
||||
ClapprMarkersPlugin.StandardMarker.call(this, time, tooltipText);
|
||||
}
|
||||
ClassyMarker.prototype = Object.create(ClapprMarkersPlugin.StandardMarker.prototype);
|
||||
ClassyMarker.prototype.constructor = ClassyMarker;
|
||||
ClassyMarker.prototype._buildMarkerEl = function() {
|
||||
var $marker = Clappr.$('<div />').addClass('standard-marker' + ' ' + this._cssClass);
|
||||
$marker.append(Clappr.$('<div />').addClass('standard-marker-inner'));
|
||||
return $marker;
|
||||
}
|
||||
|
||||
function RemovableMarker(time, tooltipText, cssClass, url) {
|
||||
this._url = url || null;
|
||||
ClassyMarker.call(this, time, tooltipText, cssClass);
|
||||
}
|
||||
RemovableMarker.prototype = Object.create(ClassyMarker.prototype);
|
||||
RemovableMarker.prototype.constructor = RemovableMarker;
|
||||
RemovableMarker.prototype.onDestroy = function() {
|
||||
$.ajax({ url: this.source._url, type: "DELETE", headers: { 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content') } });
|
||||
};
|
||||
|
||||
// Add existing bookmarks to the timeline and set up handlers for adding new bookmarks
|
||||
var addBookmark = function(bookmark) {
|
||||
var markers = player.getPlugin("markers-plugin");
|
||||
// TODO: Move the remove URL into a serializer?
|
||||
var marker = new RemovableMarker(Number(bookmark.time_elapsed), bookmark.notes + " (Press 'D' to delete)", "bookmark-marker", "/bookmarks/" + bookmark.id);
|
||||
markers.addMarker(marker);
|
||||
}
|
||||
bookmarkData.forEach(function(bookmark, index) {
|
||||
addBookmark(bookmark)
|
||||
});
|
||||
$(document).on("addBookmark-" + $(videoElement).data("video-id"), function(event, bookmark) {
|
||||
addBookmark(bookmark);
|
||||
});
|
||||
|
||||
// TODO: Where to put this??
|
||||
player.on("timeupdate", function(event) {
|
||||
$("[data-ujs-target=bookmark-form] input[name='bookmark[time_elapsed]']").val(event.current);
|
||||
$("[data-ujs-target=unreleased-appearance-form] input[name='unreleased_appearance[time_elapsed]']").val(event.current);
|
||||
$("[data-ujs-target=video-release-confirmation-form] input[name='video_release_confirmation[time_elapsed]']").val(event.current);
|
||||
$("[data-ujs-target=audio-confirmation-form] input[name='audio_confirmation[time_elapsed]']").val(event.current);
|
||||
$("[data-ujs-target=graphics-element-form] input[name='graphics_element[time_elapsed]']").val(event.current);
|
||||
$("[data-ujs-target=edl-event-form] input[name='edl_event[time_elapsed]']").val(event.current);
|
||||
});
|
||||
|
||||
// // Add event handler to show recommendations as the video plays
|
||||
player.on("timeupdate", function(event) {
|
||||
$("#suggested_matches [data-time]").each(function(index, item) {
|
||||
var time = $(item).data("time");
|
||||
if (event.current > time) {
|
||||
$(item).show();
|
||||
} else {
|
||||
$(item).hide();
|
||||
}
|
||||
});
|
||||
$("#graphics_elements_matches [data-time]").each(function(index, item) {
|
||||
var time = $(item).data("time");
|
||||
if (event.current > time) {
|
||||
$(item).show();
|
||||
} else {
|
||||
$(item).hide();
|
||||
}
|
||||
});
|
||||
$("#audio_matches [data-time]").each(function(index, item) {
|
||||
var time = $(item).data("time");
|
||||
if (event.current > time) {
|
||||
$(item).show();
|
||||
} else {
|
||||
$(item).hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on("click", "#seek_button", () => {
|
||||
try {
|
||||
const timecode = new Timecode($("#seek_input").val(), 30);
|
||||
player.seek(timecode.frameCount / timecode.frameRate);
|
||||
} catch(e) {
|
||||
$("#seek_input").val("")
|
||||
}
|
||||
});
|
||||
$(document).on('click',"[data-behavior~=seekable-timecode]", function(e){
|
||||
e.preventDefault();
|
||||
const timeCodeText = e.target.innerText;
|
||||
let extractedTime = timeCodeText;
|
||||
const startOfTimeCode = 8; // MOV TC: 00:00....
|
||||
if (timeCodeText.startsWith('MOV')){
|
||||
extractedTime = timeCodeText.slice(startOfTimeCode);
|
||||
}
|
||||
const timeCode = new Timecode(extractedTime, 30);
|
||||
player.seek(timeCode.frameCount / timeCode.frameRate);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$(document).on("click", "#go_button", function () {
|
||||
const timecode = $("#go_to").val()
|
||||
const $edlEvent = $(`[data-timecode-in='${timecode}']`).first()
|
||||
const edlEventContainer = $("#edl_events_list").parent();
|
||||
|
||||
if ($edlEvent.length > 0) {
|
||||
edlEventContainer.scrollTop($edlEvent.offset().top - edlEventContainer.offset().top + edlEventContainer.scrollTop())
|
||||
} else {
|
||||
$("#go_to").val("")
|
||||
}
|
||||
});
|
||||
97
app/assets/javascripts/video_player.js.erb
Normal file
@@ -0,0 +1,97 @@
|
||||
App.VideoPlayer = {
|
||||
init: function(videoElement) {
|
||||
// The source path for the video
|
||||
var videoUrl = $(videoElement).attr("src");
|
||||
|
||||
// Replace the standard <video> element with the Clappr player
|
||||
$(videoElement).replaceWith($("<div>", { id: "player" }));
|
||||
var player = new Clappr.Player({
|
||||
<%= "baseUrl: 'http://cdn.clappr.io/latest'," if Rails.env.test? %>
|
||||
source: videoUrl, parentId: "#player", width: "100%",
|
||||
focusElement: NotTypingSelectiveListenerElement(document), // Allows keyboard shortcuts to work even if the player itself isn't in focus
|
||||
plugins: {
|
||||
core: [ClapprMarkersPlugin, PlaybackRatePlugin]
|
||||
},
|
||||
markersPlugin: { markers: [] }
|
||||
});
|
||||
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
||||
$(document).on("turbolinks:load", function() {
|
||||
$("[data-behavior=video-player]").each(function(index, element) {
|
||||
App.VideoPlayer.init(element);
|
||||
});
|
||||
});
|
||||
|
||||
// An implementation of SelectiveListenerElement which only listens when text areas are not in focus
|
||||
var NotTypingSelectiveListenerElement = function(element) {
|
||||
var inputInFocus = function() { return $('input:focus, textarea:focus, video:focus').length > 0; }
|
||||
var noInputInFocus = function() { return !inputInFocus(); }
|
||||
|
||||
return new SelectiveListenerElement(element, noInputInFocus);
|
||||
}
|
||||
|
||||
// A helper class that maintains a collection of handlers for a given event and callback
|
||||
var HandlerCollection = function() {
|
||||
this.handlers = [];
|
||||
|
||||
// Stores a handler for a given event and callback
|
||||
this.store = function(eventName, originalCallback, handler) {
|
||||
var item = {
|
||||
eventName: eventName,
|
||||
callback: originalCallback,
|
||||
handler: handler,
|
||||
}
|
||||
this.handlers.push(item);
|
||||
}
|
||||
|
||||
// Finds a handler for a given event and callback
|
||||
this.find = function(eventName, callback) {
|
||||
var handler = null;
|
||||
for (var i = 0; i < this.handlers.length; i++) {
|
||||
var item = this.handlers[i];
|
||||
|
||||
if (item.eventName == eventName && item.callback == callback) {
|
||||
handler = item.handler;
|
||||
}
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
|
||||
// A helper class to allow a DOM element to be wrapped and selectively ignore events that are triggered on it
|
||||
var SelectiveListenerElement = function(element, shouldListenFn) {
|
||||
// The element we are proxying
|
||||
this.element = element;
|
||||
// A function that determines if we should run any event callbacks or not
|
||||
this.shouldRunCallbacks = shouldListenFn;
|
||||
// Stores the functions to be called when an event listener needs to be removed
|
||||
this.removeHandlers = new HandlerCollection();
|
||||
|
||||
this.addEventListener = function(eventName, callback, useCapture) {
|
||||
// Create a new callback function that conditionally calls the original
|
||||
var conditionalCallback = function() {
|
||||
if (this.shouldRunCallbacks()) {
|
||||
callback();
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
// Store the function to be called when removing the event listener
|
||||
this.removeHandlers.store(eventName, callback, function() {
|
||||
this.element.removeEventListener(eventName, conditionalCallback, useCapture);
|
||||
}.bind(this));
|
||||
|
||||
// Attach an event listener to the same event with the conditional callback function
|
||||
this.element.addEventListener(eventName, conditionalCallback, useCapture);
|
||||
}
|
||||
|
||||
this.removeEventListener = function(eventName, callback, useCapture) {
|
||||
var removeHandler = this.removeHandlers.find(eventName, callback);
|
||||
|
||||
if (removeHandler != null) {
|
||||
removeHandler();
|
||||
}
|
||||
}
|
||||
}
|
||||
54
app/assets/stylesheets/_bootstrap_overrides.scss
Normal file
@@ -0,0 +1,54 @@
|
||||
// Bootstrap theme customization
|
||||
$white: #FFFFFF;
|
||||
$gray-100: #F7F8F9;
|
||||
$gray-300: #DEE5EB;
|
||||
$gray-400: #B2B6C2;
|
||||
$gray-500: #A6ACBE;
|
||||
$gray-800: #393C46;
|
||||
$gray-900: #2A2D36;
|
||||
$body-bg: $gray-100;
|
||||
$body-color: #4A4A4A;
|
||||
$primary: #6F89FF;
|
||||
$blue: #0092ff;
|
||||
$red: #F9002B;
|
||||
$green: #51B61B;
|
||||
$teal: #32C498;
|
||||
$purple: #5139EE;
|
||||
$dark: $gray-900;
|
||||
$success: $teal;
|
||||
$link-color: $body-color;
|
||||
$input-border-color: $gray-300;
|
||||
$dropdown-bg: $dark;
|
||||
$dropdown-color: $gray-500;
|
||||
$dropdown-link-color: $gray-500;
|
||||
$dropdown-link-hover-color: lighten($gray-500, 5%);
|
||||
$dropdown-link-hover-bg: $gray-800;
|
||||
$dropdown-link-active-bg: $gray-800;
|
||||
$font-size-base: 1rem;
|
||||
$font-weight-normal: 400;
|
||||
$font-weight-bold: 700;
|
||||
$h1-font-size: $font-size-base * 2.25;
|
||||
$h2-font-size: $font-size-base * 1.85;
|
||||
$h3-font-size: $font-size-base * 1.5;
|
||||
$h4-font-size: $font-size-base * 1.25;
|
||||
$h5-font-size: $font-size-base * 1.15;
|
||||
$h6-font-size: $font-size-base;
|
||||
$headings-font-weight: $font-weight-bold;
|
||||
$table-border-width: 0;
|
||||
$table-head-bg: $white;
|
||||
$table-head-color: $gray-400;
|
||||
$breadcrumb-bg: transparent;
|
||||
$breadcrumb-padding-x: 0;
|
||||
$breadcrumb-padding-y: 0;
|
||||
$breadcrumb-divider: "\f054";
|
||||
$breadcrumb-divider-color: $gray-400;
|
||||
$breadcrumb-active-color: $body-color;
|
||||
$nav-tabs-link-active-bg: $white;
|
||||
|
||||
.thead-light th {
|
||||
font-weight: $font-weight-normal;
|
||||
}
|
||||
|
||||
.breadcrumb-item + .breadcrumb-item::before {
|
||||
font-family: "FontAwesome";
|
||||
}
|
||||
36
app/assets/stylesheets/actiontext.scss
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and
|
||||
// the trix-editor content (whether displayed or under editing). Feel free to incorporate this
|
||||
// inclusion directly in any other asset bundle and remove this file.
|
||||
//
|
||||
//= require trix/dist/trix
|
||||
|
||||
// We need to override trix.css’s image gallery styles to accommodate the
|
||||
// <action-text-attachment> element we wrap around attachments. Otherwise,
|
||||
// images in galleries will be squished by the max-width: 33%; rule.
|
||||
.trix-content {
|
||||
.attachment-gallery {
|
||||
> action-text-attachment,
|
||||
> .attachment {
|
||||
flex: 1 0 33%;
|
||||
padding: 0 0.5em;
|
||||
max-width: 33%;
|
||||
}
|
||||
|
||||
&.attachment-gallery--2,
|
||||
&.attachment-gallery--4 {
|
||||
> action-text-attachment,
|
||||
> .attachment {
|
||||
flex-basis: 50%;
|
||||
max-width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
action-text-attachment {
|
||||
.attachment {
|
||||
padding: 0 !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
417
app/assets/stylesheets/application.scss
Normal file
@@ -0,0 +1,417 @@
|
||||
@import "bootstrap_overrides";
|
||||
@import "bootstrap";
|
||||
@import "font-awesome";
|
||||
@import "rails_bootstrap_forms";
|
||||
@import 'trix/dist/trix';
|
||||
@import "dropzone/dist/dropzone";
|
||||
@import 'bootstrap-datepicker';
|
||||
|
||||
// Wordmarks
|
||||
.suite-wordmark {
|
||||
font-weight: normal;
|
||||
span:first-child {
|
||||
background-color: $red;
|
||||
color: white;
|
||||
padding: 5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
span:last-child {
|
||||
color: $body-color;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.product-wordmark {
|
||||
span:last-child {
|
||||
font-weight: normal;
|
||||
margin-left: 5px;
|
||||
padding: 0 2px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&.release-me {
|
||||
span:last-child {
|
||||
background-color: $teal;
|
||||
color: $body-color;
|
||||
}
|
||||
}
|
||||
|
||||
&.direct-me {
|
||||
span:last-child {
|
||||
background-color: $green;
|
||||
color: $body-color;
|
||||
}
|
||||
}
|
||||
|
||||
&.edit-me {
|
||||
span:last-child {
|
||||
background-color: $blue;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
&.deliver-me {
|
||||
span:last-child {
|
||||
background-color: $purple;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
span:last-child {
|
||||
background-color: $gray-500 !important;
|
||||
color: $body-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Vertically align all direct descendant elements to the middle
|
||||
.align-all-middle tr td {
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
// Create horizontal padding on table rows by targeting table cells
|
||||
@each $name, $value in $spacers {
|
||||
.tr-px-#{$name} {
|
||||
&, tr {
|
||||
th, td {
|
||||
&:first-child {
|
||||
padding-left: $value;
|
||||
}
|
||||
&:last-child {
|
||||
padding-right: $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show the state of a collapse panel using up and down chevron circle icons
|
||||
.collapse-indicator {
|
||||
cursor: pointer;
|
||||
font-family: "FontAwesome";
|
||||
|
||||
&:after {
|
||||
content: "\f13a";
|
||||
}
|
||||
}
|
||||
.collapsed .collapse-indicator:after {
|
||||
content: "\f139";
|
||||
}
|
||||
|
||||
// Used for the person photo image upload preview
|
||||
.photo-preview {
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
// Fix for Trix rich text editor element + Bootstrap
|
||||
// Allows the text area to scroll as content is added
|
||||
trix-editor.form-control {
|
||||
height: 15rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
// Provide some styling for required form fields
|
||||
.required small:after, label.required:after {
|
||||
color: $red;
|
||||
content: "Required";
|
||||
margin-left: 5px;
|
||||
font-size: 0.5rem;
|
||||
}
|
||||
html[lang="es"] .required:after {
|
||||
content: "Necesario";
|
||||
}
|
||||
|
||||
// Used when a release has been confirmed for a given video
|
||||
.releasable[data-confirmed="true"] {
|
||||
button {
|
||||
border-width: 5px !important;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
// Used when a suggested_match has been confirmed for a given video
|
||||
.releasable-match[data-confirmed="true"] {
|
||||
button {
|
||||
border-width: 5px !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Overrode the above style for audio matches
|
||||
#audio_matches .releasable-match[data-confirmed="true"] {
|
||||
button {
|
||||
border-width: 1px !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Used when hiding checkmark next to releasable-match
|
||||
.releasable-match[data-confirmed="false"] [data-ujs-target=match-confirmed-check] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Used when hiding checkmark next to graphics-match
|
||||
.graphics-match[data-confirmed="false"] [data-ujs-target=graphics-confirmed-check] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Colored borders that correspond to the type of release
|
||||
.border-appearance_release {
|
||||
border-color: rgb(0,255,0) !important;
|
||||
}
|
||||
.border-talent_release {
|
||||
border-color: rgb(0,0,255) !important;
|
||||
}
|
||||
|
||||
// Used to hide confirmed releases
|
||||
.hidden-when-confirmed[data-confirmed="true"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Markers
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.bookmark-marker .standard-marker-inner {
|
||||
background-color: map-get($theme-colors, 'info');
|
||||
}
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.bookmark-marker:hover .standard-marker-inner,
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.bookmark-marker.hover .standard-marker-inner {
|
||||
background-color: map-get($theme-colors, 'info');
|
||||
box-shadow: 0 0 0 8px rgba(map-get($theme-colors, 'info'), 0.2);
|
||||
}
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.unreleased-appearance-marker .standard-marker-inner {
|
||||
background-color: map-get($theme-colors, 'danger');
|
||||
}
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.unreleased-appearancebookmark-marker:hover .standard-marker-inner,
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.unreleased-appearancebookmark-marker.hover .standard-marker-inner {
|
||||
background-color: map-get($theme-colors, 'danger');
|
||||
box-shadow: 0 0 0 8px rgba(map-get($theme-colors, 'danger'), 0.2);
|
||||
}
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.unreleased-warning-marker .standard-marker-inner {
|
||||
background-color: map-get($theme-colors, 'warning');
|
||||
}
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.unreleased-warning-marker .standard-marker-inner {
|
||||
background-color: map-get($theme-colors, 'warning');
|
||||
}
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.unreleased-warning-marker:hover .standard-marker-inner,
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.unreleased-warning-marker.hover .standard-marker-inner {
|
||||
background-color: map-get($theme-colors, 'warning');
|
||||
box-shadow: 0 0 0 8px rgba(map-get($theme-colors, 'warning'), 0.2);
|
||||
}
|
||||
|
||||
// Uses a dashed border style
|
||||
.border-dashed {
|
||||
border-style: dashed !important;
|
||||
}
|
||||
|
||||
// Increases the width of the border
|
||||
.border-thick {
|
||||
border-width: 2px !important;
|
||||
}
|
||||
.border-thicker {
|
||||
border-width: 5px !important;
|
||||
}
|
||||
|
||||
// Style as a keyboard shortcut key
|
||||
.keyboard-key {
|
||||
background-color: map-get($grays, "100");
|
||||
background-image: linear-gradient($white, map-get($grays, "100"));
|
||||
border-radius: 3px;
|
||||
border: 1px solid map-get($grays, "500");
|
||||
color: $black;
|
||||
font-family: $font-family-monospace;
|
||||
font-size: $font-size-sm;
|
||||
padding: map-get($spacers, 1);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
// Remove default button styles
|
||||
.btn-no-style {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// Cursors
|
||||
.cursor-copy {
|
||||
cursor: copy;
|
||||
}
|
||||
.cursor-not-allowed {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
// These borders only appear when an element is hovered
|
||||
@each $color, $value in $theme-colors {
|
||||
.border-hover-#{$color}:hover {
|
||||
border-color: $value !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Customized tooltip colors
|
||||
@each $color, $value in $theme-colors {
|
||||
.tooltip-#{$color} {
|
||||
& > .tooltip-inner {
|
||||
background-color: $value;
|
||||
}
|
||||
&.bs-tooltip-top > .arrow::before {
|
||||
border-top-color: $value;
|
||||
}
|
||||
&.bs-tooltip-right > .arrow::before {
|
||||
border-right-color: $value;
|
||||
}
|
||||
&.bs-tooltip-bottom > .arrow::before {
|
||||
border-bottom-color: $value;
|
||||
}
|
||||
&.bs-tooltip-left > .arrow::before {
|
||||
border-left-color: $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fix for custom file input label in an inline form
|
||||
.form-inline .custom-file-label {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
// Customized carousel colors
|
||||
.carousel-bg-black {
|
||||
& > .carousel-inner {
|
||||
background-color: $black;
|
||||
}
|
||||
}
|
||||
|
||||
// Text size
|
||||
.x-small {
|
||||
font-size: 60%;
|
||||
}
|
||||
|
||||
// Fix long name wrapping in file inputs
|
||||
.custom-file {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hidden-file-input {
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-behavior=fillable-fields] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
[data-behavior=seekable-timecode] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// Fixes known issue with collapsible elements which don't normally receive click events on iOS
|
||||
// https://github.com/twbs/bootstrap/issues/16213#issuecomment-88995656
|
||||
[data-toggle=collapse][data-target] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// Pins a modal to the right side of the screen on large devices
|
||||
.modal-right {
|
||||
@include media-breakpoint-up(lg) {
|
||||
left: 25%;
|
||||
right: 0%;
|
||||
}
|
||||
}
|
||||
|
||||
// Provides extra padding to main content area when there is a fixed bottom alert
|
||||
.alert.fixed-bottom + main {
|
||||
padding-bottom: $alert-padding-y * 5 !important;
|
||||
}
|
||||
|
||||
// Allows <hr> divider to be used on a dark background
|
||||
.divider-light {
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
// Sets line height to 0
|
||||
.no-line-height {
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
// Even lighter text color
|
||||
.text-more-muted {
|
||||
color: $gray-500 !important;
|
||||
}
|
||||
|
||||
// A dark version of the pills nav
|
||||
.nav-pills-dark {
|
||||
a {
|
||||
color: $gray-500;
|
||||
|
||||
&:hover {
|
||||
background-color: $gray-800;
|
||||
color: lighten($gray-500, 15%);
|
||||
}
|
||||
|
||||
&.active {
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A comma separated list
|
||||
ul.list-comma-separated {
|
||||
li:not(:last-child):after {
|
||||
content: ",";
|
||||
}
|
||||
}
|
||||
|
||||
a[data-behavior=seekable-timecode] {
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.embeded-contract-preview {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
// Border radius
|
||||
.rounded-pill-left {
|
||||
border-top-left-radius: $rounded-pill;
|
||||
border-bottom-left-radius: $rounded-pill;
|
||||
}
|
||||
.rounded-pill-right {
|
||||
border-top-right-radius: $rounded-pill;
|
||||
border-bottom-right-radius: $rounded-pill;
|
||||
}
|
||||
|
||||
// A nav-pills component that uses white color for the active element
|
||||
.nav-pills-white {
|
||||
.nav-link.active,
|
||||
.show > .nav-link {
|
||||
color: $body-color;
|
||||
background-color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
// A custom background color that is semi-transparent
|
||||
.bg-semi-transparent {
|
||||
background-color: rgba($black, 0.05);
|
||||
}
|
||||
|
||||
// Custom width
|
||||
.w-65 {
|
||||
width: 65% !important;
|
||||
}
|
||||
|
||||
// Maximum height
|
||||
.mh-30 {
|
||||
max-height: 30rem;
|
||||
}
|
||||
|
||||
// Fix height and width
|
||||
.fix-h-and-w {
|
||||
width: 308px;
|
||||
height:308px;
|
||||
}
|
||||
54
app/assets/stylesheets/contract_pdf.scss
Normal file
@@ -0,0 +1,54 @@
|
||||
dd {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: bold;
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
dt, dd {
|
||||
display: inline-block;
|
||||
padding: 10px 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
ol li, ul li {
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
u {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.heading {
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.page {
|
||||
page-break-before: always;
|
||||
}
|
||||
|
||||
.logo {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.heading-table {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.heading-table td {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.serial-number {
|
||||
text-transform: uppercase;
|
||||
padding-right: 15px;
|
||||
}
|
||||
4
app/channels/application_cable/channel.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
module ApplicationCable
|
||||
class Channel < ActionCable::Channel::Base
|
||||
end
|
||||
end
|
||||
17
app/channels/application_cable/connection.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
module ApplicationCable
|
||||
class Connection < ActionCable::Connection::Base
|
||||
identified_by :current_user
|
||||
|
||||
def connect
|
||||
self.current_user = find_verified_user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_verified_user
|
||||
if verified_user = env['warden'].user
|
||||
verified_user
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
47
app/channels/broadcasts_channel.rb
Normal file
@@ -0,0 +1,47 @@
|
||||
class BroadcastsChannel < ApplicationCable::Channel
|
||||
def subscribed
|
||||
broadcast = Broadcast.find_by_token!(params[:token])
|
||||
stream_for broadcast
|
||||
end
|
||||
|
||||
def unsubscribed
|
||||
# Any cleanup needed when channel is unsubscribed
|
||||
end
|
||||
|
||||
def self.broadcast_stream_updates(broadcast)
|
||||
status_content = ApplicationController.render partial: "broadcasts/broadcast_status", locals: { broadcast: broadcast }
|
||||
video_content = ApplicationController.render partial: "broadcasts/video", locals: { broadcast: broadcast }
|
||||
|
||||
broadcast_to broadcast, {
|
||||
event: :broadcast_stream_update,
|
||||
status: broadcast.status,
|
||||
playback_url: broadcast.stream_playback_url,
|
||||
video_content: video_content,
|
||||
status_content: status_content,
|
||||
streamer_status: broadcast.streamer_status
|
||||
}
|
||||
end
|
||||
|
||||
def self.stream_recording_ready(broadcast, recordings, message)
|
||||
flash_message = OpenStruct.new(notice: message, alert: nil)
|
||||
flash_content = ApplicationController.render partial: "application/flash", locals: { flash: flash_message }
|
||||
recordings_content = ApplicationController.render partial: "broadcasts/broadcast_recordings", locals: { recordings: recordings, broadcast: broadcast }
|
||||
recordings_nav_content = ApplicationController.render partial: "broadcasts/broadcast_recording_nav", collection: recordings, as: :broadcast_recording
|
||||
|
||||
broadcast_to broadcast,
|
||||
event: :stream_recording_ready,
|
||||
flash_content: flash_content,
|
||||
recordings_content: recordings_content,
|
||||
recordings_nav_content: recordings_nav_content
|
||||
end
|
||||
|
||||
def self.file_upload_updates(broadcast, files, pagination_content)
|
||||
files_content = ApplicationController.render partial: "broadcasts/file", collection: files
|
||||
|
||||
broadcast_to broadcast, {
|
||||
event: :file_upload_update,
|
||||
files_content: files_content,
|
||||
pagination_content: pagination_content
|
||||
}
|
||||
end
|
||||
end
|
||||
36
app/channels/projects_channel.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
class ProjectsChannel < ApplicationCable::Channel
|
||||
|
||||
def subscribed
|
||||
# TODO: How can we get the current account in this context
|
||||
project = current_user.accessible_projects_for(current_user.primary_account).find(params[:id])
|
||||
stream_for project
|
||||
end
|
||||
|
||||
def unsubscribed
|
||||
# Any cleanup needed when channel is unsubscribed
|
||||
end
|
||||
|
||||
def self.broadcast_video_analysis_update(video)
|
||||
content = ApplicationController.render partial: "video_analyses/video_status_updated", locals: { video: video }
|
||||
broadcast_to video.project, event: :video_status_update, content: content
|
||||
end
|
||||
|
||||
def self.broadcast_download_generation_update(download, notification)
|
||||
if download.failure?
|
||||
flash = OpenStruct.new(notice: nil, alert: notification)
|
||||
else
|
||||
flash = OpenStruct.new(notice: notification, alert: nil)
|
||||
end
|
||||
|
||||
content = ApplicationController.render partial: "application/flash", locals: { flash: flash }
|
||||
broadcast_to download.project, event: :download_status_update, content: content
|
||||
end
|
||||
|
||||
def self.conference_recording_ready(project, recording)
|
||||
link = ApplicationController.helpers.link_to('Download here.', recording.service_url, target: '_blank', class: 'alert-link')
|
||||
notification = "A recording of your video conference is now available. #{link}"
|
||||
flash = OpenStruct.new(notice: notification)
|
||||
content = ApplicationController.render partial: 'application/flash', locals: { flash: flash }
|
||||
broadcast_to project, event: :conference_recording_ready, content: content
|
||||
end
|
||||
end
|
||||
83
app/controllers/account_auths_controller.rb
Normal file
@@ -0,0 +1,83 @@
|
||||
class AccountAuthsController < ApplicationController
|
||||
before_action :set_account_auth, only: [:update, :destroy]
|
||||
|
||||
def index
|
||||
@account = Current.account
|
||||
if params[:account_id]
|
||||
@account = accounts.find(params[:account_id])
|
||||
end
|
||||
|
||||
@members = account_auths.joins(:user).where(account: @account).order('role DESC, users.email ASC')
|
||||
@account_auth = account_auths.new(user: current_user, account: @account, role: :account_manager)
|
||||
end
|
||||
|
||||
def create
|
||||
email = account_auths_create_params[:user_email]
|
||||
account_id = account_auths_create_params[:account_id]
|
||||
user = User.find_by(email: email)
|
||||
if user.nil?
|
||||
user = sign_up({ email: email, password: SecureRandom.alphanumeric })
|
||||
@account_auth = build_account_auth({ user: user, account_id: account_id, role: :account_manager })
|
||||
@account_auth.save!
|
||||
UserMailer.welcome(@account_auth.user, @account_auth.account).deliver_later
|
||||
else
|
||||
@account_auth = build_account_auth({ user: user, account_id: account_id, role: :account_manager })
|
||||
@account_auth.save!
|
||||
UserMailer.existing_account(@account_auth.user, @account_auth.account).deliver_later
|
||||
end
|
||||
redirect_to account_auths_path({ account_id: @account_auth.account_id }), notice: t(".notice")
|
||||
end
|
||||
|
||||
def update
|
||||
AccountAuth.transaction do
|
||||
if @account_auth.update(account_auth_update_params)
|
||||
if @account_auth.account_manager?
|
||||
@account_auth.user.project_memberships.where(project: @account_auth.account.projects).destroy_all
|
||||
end
|
||||
flash.notice = t(".notice")
|
||||
else
|
||||
flash.alert = t(".alert")
|
||||
end
|
||||
end
|
||||
|
||||
redirect_to account_auths_path
|
||||
end
|
||||
|
||||
def destroy
|
||||
ActiveRecord::Base.transaction do
|
||||
ProjectMembership.where(user: @account_auth.user, project: @account_auth.account.projects).destroy_all
|
||||
@account_auth.destroy
|
||||
end
|
||||
redirect_to account_auths_path, alert: t(".alert")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_account_auth(auth_params)
|
||||
@account_auth = authorize account_auths.build(auth_params)
|
||||
end
|
||||
|
||||
def account_auths_create_params
|
||||
params.require(:account_auth).permit(:user_email, :account_id)
|
||||
end
|
||||
|
||||
def account_auth_update_params
|
||||
params.require(:account_auth).permit(:role)
|
||||
end
|
||||
|
||||
def account_auths
|
||||
policy_scope(AccountAuth)
|
||||
end
|
||||
|
||||
def find_account_auth
|
||||
authorize account_auths.find(params[:id])
|
||||
end
|
||||
|
||||
def accounts
|
||||
policy_scope(Account)
|
||||
end
|
||||
|
||||
def set_account_auth
|
||||
@account_auth = find_account_auth
|
||||
end
|
||||
end
|
||||
13
app/controllers/account_sessions_controller.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
class AccountSessionsController < ApplicationController
|
||||
def update
|
||||
authorize :account_session, :update?
|
||||
session[:active_account] = account_session_params[:account_id]
|
||||
redirect_to signed_in_root_path
|
||||
end
|
||||
|
||||
private
|
||||
def account_session_params
|
||||
params.require(:account_session).permit(:account_id)
|
||||
end
|
||||
end
|
||||
|
||||
56
app/controllers/accounts_controller.rb
Normal file
@@ -0,0 +1,56 @@
|
||||
class AccountsController < ApplicationController
|
||||
before_action :set_account, only: [:update]
|
||||
skip_before_action :require_login, only: [:new, :create]
|
||||
skip_after_action :verify_authorized, only: [:new, :create]
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
def create
|
||||
account = Account.create(account_params_for_create)
|
||||
|
||||
if account.valid?
|
||||
user = sign_up(user_params)
|
||||
|
||||
if user.valid?
|
||||
user.account_auths << AccountAuth.create(user: user, account: account, role: "account_manager")
|
||||
|
||||
if sign_in(user)
|
||||
TrackAnalyticsJob.perform_later(user, user.primary_account, :track_guest_sign_up, user_agent: request.user_agent, user_ip: request.remote_ip)
|
||||
SubmitHubspotFormJob.perform_later(user.email, account.name, i_m_interested_in: user.interested_product_name)
|
||||
redirect_to signed_in_root_path
|
||||
else
|
||||
redirect_to new_session_path, alert: t(".notice")
|
||||
end
|
||||
else
|
||||
redirect_to new_account_path, alert: t(".error")
|
||||
end
|
||||
else
|
||||
redirect_to new_account_path, alert: t(".error")
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @account
|
||||
|
||||
@account.update(account_params_for_update)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Current.account
|
||||
end
|
||||
|
||||
def account_params_for_create
|
||||
{ name: params[:user][:account_name], plan_uid: :me_suite }
|
||||
end
|
||||
|
||||
def account_params_for_update
|
||||
params.require(:account).permit(:logo)
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).except(:account_name).permit(:email, :password, :first_name, :last_name, :interested_product_name)
|
||||
end
|
||||
end
|
||||
106
app/controllers/acquired_media_releases_controller.rb
Normal file
@@ -0,0 +1,106 @@
|
||||
class AcquiredMediaReleasesController < ApplicationController
|
||||
include ProjectContext, AcquiredMediaReleaseContext
|
||||
|
||||
before_action :set_project, only: [:index, :new, :create]
|
||||
before_action :set_acquired_media_release, only: [:edit, :update, :destroy]
|
||||
|
||||
include ProjectLayout
|
||||
|
||||
def index
|
||||
@acquired_media_releases = filtered_acquired_media_releases.order_by_recent.paginate(page: params[:page])
|
||||
end
|
||||
|
||||
def new
|
||||
@acquired_media_release = build_acquired_media_release
|
||||
end
|
||||
|
||||
def create
|
||||
@acquired_media_release = build_acquired_media_release(acquired_media_release_params)
|
||||
|
||||
if @acquired_media_release.save
|
||||
log_create_analytics
|
||||
SetTagsForReleasableJob.perform_later(@acquired_media_release)
|
||||
redirect_to [@project, :acquired_media_releases], notice: t(".notice")
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@project = @acquired_media_release.project
|
||||
end
|
||||
|
||||
def update
|
||||
@project = @acquired_media_release.project
|
||||
|
||||
if @acquired_media_release.update(acquired_media_release_params)
|
||||
redirect_to [@project, :acquired_media_releases], notice: t(".notice")
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@project = @acquired_media_release.project
|
||||
@acquired_media_release.destroy
|
||||
redirect_to [@project, :acquired_media_releases], alert: t(".alert")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def acquired_media_releases
|
||||
if @project
|
||||
policy_scope(@project.acquired_media_releases)
|
||||
else
|
||||
policy_scope(AcquiredMediaRelease)
|
||||
end
|
||||
end
|
||||
|
||||
def acquired_media_release_params
|
||||
params.require(:acquired_media_release).permit(
|
||||
:name,
|
||||
:territory,
|
||||
:term,
|
||||
:person_name,
|
||||
:person_phone,
|
||||
:person_email,
|
||||
:person_company,
|
||||
:person_title,
|
||||
:person_address_street1,
|
||||
:person_address_street2,
|
||||
:person_address_city,
|
||||
:person_address_state,
|
||||
:person_address_zip,
|
||||
:person_address_country,
|
||||
:contract,
|
||||
:applicable_medium_id, :applicable_medium_text,
|
||||
:territory_id, :territory_text,
|
||||
:term_id, :term_text,
|
||||
:restriction_id, :restriction_text,
|
||||
categories: [],
|
||||
file_infos_attributes: [
|
||||
:filename,
|
||||
:content_type,
|
||||
:byte_size
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def build_acquired_media_release(attrs = {})
|
||||
authorize @project.acquired_media_releases.build(attrs)
|
||||
end
|
||||
|
||||
def filtered_acquired_media_releases
|
||||
results = acquired_media_releases
|
||||
|
||||
if params[:query].present?
|
||||
results = results.search(params[:query])
|
||||
end
|
||||
|
||||
results
|
||||
end
|
||||
|
||||
def log_create_analytics
|
||||
TrackAnalyticsJob.perform_later(Current.user, Current.account, :track_create_non_native_release, release_type: AcquiredMediaRelease.to_s, user_agent: request.user_agent, user_ip: request.remote_ip)
|
||||
end
|
||||
end
|
||||
73
app/controllers/admin/accounts_controller.rb
Normal file
@@ -0,0 +1,73 @@
|
||||
class Admin::AccountsController < Admin::ApplicationController
|
||||
before_action :set_account, only: [:show, :edit, :update]
|
||||
|
||||
def index
|
||||
@accounts = filtered_accounts.order_by_name.paginate(page: params[:page])
|
||||
end
|
||||
|
||||
def new
|
||||
@account = build_account
|
||||
end
|
||||
|
||||
def create
|
||||
@account = build_account(account_params)
|
||||
if @account.save
|
||||
redirect_to account_auths_path({ account_id: @account.id }), notice: t(".notice")
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@videos = filtered_account_videos.order(created_at: :desc, project_id: :desc).paginate(page: params[:page])
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
if @account.update(account_params)
|
||||
redirect_to admin_accounts_path, notice: t(".notice")
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = find_account
|
||||
end
|
||||
|
||||
def find_account
|
||||
authorize accounts.find_by(slug: params[:id])
|
||||
end
|
||||
|
||||
def accounts
|
||||
policy_scope(Account)
|
||||
end
|
||||
|
||||
def build_account(params = {})
|
||||
authorize accounts.new(params)
|
||||
end
|
||||
|
||||
def account_params
|
||||
params.require(:account).permit(:name, :plan_uid)
|
||||
end
|
||||
|
||||
def filtered_accounts
|
||||
if params[:query].present?
|
||||
accounts.search(params[:query])
|
||||
else
|
||||
accounts
|
||||
end
|
||||
end
|
||||
|
||||
def filtered_account_videos
|
||||
if params[:query].present?
|
||||
@account.videos.search(params[:query])
|
||||
else
|
||||
@account.videos
|
||||
end
|
||||
end
|
||||
end
|
||||
18
app/controllers/admin/application_controller.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
class Admin::ApplicationController < ActionController::Base
|
||||
include Oath::ControllerHelpers # Methods for authentication
|
||||
include Pundit # Methods for authorization
|
||||
|
||||
before_action :require_login
|
||||
include SetCurrentRequestDetails
|
||||
before_action :require_admin_login
|
||||
after_action :verify_authorized, except: :index
|
||||
after_action :verify_policy_scoped, only: :index
|
||||
|
||||
private
|
||||
|
||||
def require_admin_login
|
||||
if !Current.user.admin?
|
||||
redirect_to signed_in_root_url, alert: "You are not authorized to access this"
|
||||
end
|
||||
end
|
||||
end
|
||||
33
app/controllers/admin/masquerades_controller.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
class Admin::MasqueradesController < Admin::ApplicationController
|
||||
before_action :set_user, only: [:create]
|
||||
skip_before_action :require_admin_login, only: [:destroy]
|
||||
|
||||
def create
|
||||
authorize :masquerade, :create?
|
||||
session[:admin_id] = current_user.id
|
||||
sign_in @user
|
||||
redirect_to signed_in_root_path
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize :masquerade, :destroy?
|
||||
sign_in User.find session[:admin_id]
|
||||
session.delete(:admin_id)
|
||||
session.delete(:active_account)
|
||||
redirect_to admin_users_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user
|
||||
@user = find_user
|
||||
end
|
||||
|
||||
def users
|
||||
policy_scope User
|
||||
end
|
||||
|
||||
def find_user
|
||||
authorize users.find(params[:user_id])
|
||||
end
|
||||
end
|
||||
90
app/controllers/admin/users_controller.rb
Normal file
@@ -0,0 +1,90 @@
|
||||
class Admin::UsersController < Admin::ApplicationController
|
||||
before_action :set_user, only: [:edit, :update]
|
||||
|
||||
def index
|
||||
@users = filtered_users.order("email")
|
||||
end
|
||||
|
||||
def new
|
||||
@user = build_user
|
||||
@accounts = accounts
|
||||
end
|
||||
|
||||
def create
|
||||
auth_params = user_create_params.slice(:account_id, :role)
|
||||
|
||||
@user = authorize sign_up(user_create_params.except(:account_id, :role))
|
||||
|
||||
if @user.valid?
|
||||
account = accounts.find(auth_params[:account_id])
|
||||
@user.account_auths << AccountAuth.create(user: @user, account: account, role: auth_params[:role])
|
||||
UserMailer.welcome(@user, account).deliver_later
|
||||
redirect_to admin_users_path, notice: t(".notice")
|
||||
else
|
||||
@accounts = accounts
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@accounts = accounts
|
||||
end
|
||||
|
||||
def update
|
||||
set_user_password
|
||||
|
||||
if @user.update(user_update_params.except(:password))
|
||||
redirect_to admin_users_path, notice: t(".notice")
|
||||
else
|
||||
@accounts = accounts
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@user = authorize User.find(params[:id])
|
||||
@user.destroy
|
||||
|
||||
redirect_to admin_users_path, alert: t(".alert")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user
|
||||
@user = find_user
|
||||
end
|
||||
|
||||
def accounts
|
||||
policy_scope Account
|
||||
end
|
||||
|
||||
def users
|
||||
policy_scope User
|
||||
end
|
||||
|
||||
def find_user
|
||||
authorize users.find(params[:id])
|
||||
end
|
||||
|
||||
def build_user(params = {})
|
||||
authorize users.new(params)
|
||||
end
|
||||
|
||||
def user_create_params
|
||||
params.require(:user).permit(:email, :password, :admin, :account_id, :role)
|
||||
end
|
||||
|
||||
def user_update_params
|
||||
params.require(:user).permit(:email, :admin, :password)
|
||||
end
|
||||
|
||||
def set_user_password
|
||||
if user_update_params[:password].present?
|
||||
Oath::Services::PasswordReset.new(@user, user_update_params[:password]).perform
|
||||
end
|
||||
end
|
||||
|
||||
def filtered_users
|
||||
params[:query].present? ? users.search(params[:query]) : users
|
||||
end
|
||||
end
|
||||
11
app/controllers/api/acquired_media_releases_controller.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class Api::AcquiredMediaReleasesController < Api::ReleasesController
|
||||
deserializable_resource :acquired_media_release, only: [:create, :update]
|
||||
|
||||
def model_name
|
||||
"acquired_media_release"
|
||||
end
|
||||
|
||||
def attributes_for_index
|
||||
[:name]
|
||||
end
|
||||
end
|
||||
48
app/controllers/api/api_controller.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
class Api::ApiController < ActionController::Base
|
||||
skip_before_action :verify_authenticity_token
|
||||
include Knock::Authenticable
|
||||
include Pundit
|
||||
|
||||
rescue_from Exception, :with => :return_error
|
||||
before_action :authenticate_user
|
||||
before_action do
|
||||
Current.user = current_user if current_user.present?
|
||||
end
|
||||
|
||||
def pundit_user
|
||||
UserContext.new(Current.user, Current.account)
|
||||
end
|
||||
|
||||
# Catch exception and return JSON-formatted error
|
||||
def return_error(exception)
|
||||
raise exception if Rails.env.test?
|
||||
|
||||
logger.error "==Handled======="
|
||||
logger.error exception.message
|
||||
logger.error exception.backtrace.join("\n")
|
||||
logger.error "==Handled======="
|
||||
case exception.class
|
||||
when ActiveRecord::RecordNotFound
|
||||
@status = 404
|
||||
@message = 'Record not found'
|
||||
when ActiveRecord::RecordInvalid
|
||||
@status = 422
|
||||
@message = 'Record invalid'
|
||||
when ArgumentError
|
||||
@status = 400
|
||||
@message = 'Argument Error'
|
||||
else
|
||||
@status = 500
|
||||
@message = 'Internal Error'
|
||||
end
|
||||
|
||||
# for some reason render json_errors is not working
|
||||
# simulating JSON API support
|
||||
render json: {
|
||||
errors: [{
|
||||
status: @status.to_s,
|
||||
title: @message
|
||||
}]
|
||||
}
|
||||
end
|
||||
end
|
||||
23
app/controllers/api/appearance_releases_controller.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
class Api::AppearanceReleasesController < Api::ReleasesController
|
||||
deserializable_resource :appearance_release, only: [:create]
|
||||
|
||||
def model_name
|
||||
"appearance_release"
|
||||
end
|
||||
|
||||
def attributes_for_index
|
||||
[:person_name]
|
||||
end
|
||||
|
||||
def handle_attachments(release, _)
|
||||
photo = release_create_params[:person_photo]
|
||||
photo[:io] = StringIO.new(Base64.decode64(photo[:io]))
|
||||
release.person_photo.attach(io: photo[:io], filename: photo[:filename])
|
||||
|
||||
guardian_photo = release_create_params[:guardian_photo]
|
||||
if guardian_photo
|
||||
guardian_photo[:io] = StringIO.new(Base64.decode64(guardian_photo[:io]))
|
||||
release.guardian_photo.attach(io: guardian_photo[:io], filename: guardian_photo[:filename])
|
||||
end
|
||||
end
|
||||
end
|
||||
55
app/controllers/api/broadcasts_controller.rb
Normal file
@@ -0,0 +1,55 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::BroadcastsController < Api::ApiController
|
||||
deserializable_resource :broadcast, only: [:update]
|
||||
|
||||
include ProjectContext
|
||||
|
||||
before_action :set_project
|
||||
before_action :set_broadcast, only: [:show, :update]
|
||||
|
||||
def index
|
||||
render jsonapi: broadcasts, class: { Broadcast: SerializableBroadcast }
|
||||
end
|
||||
|
||||
def show
|
||||
render jsonapi:
|
||||
@broadcast,
|
||||
class: {
|
||||
Broadcast: SerializableBroadcast,
|
||||
"ActiveStorage::Attachment".to_sym => ActiveStorage::SerializableAttachment
|
||||
},
|
||||
include: [:files]
|
||||
end
|
||||
|
||||
def update
|
||||
file_params.each do |file|
|
||||
file[:io] = StringIO.new(Base64.decode64(file[:io]))
|
||||
@broadcast.files.attach(io: file[:io], filename: file[:filename])
|
||||
end
|
||||
@broadcast.save!
|
||||
|
||||
render jsonapi:
|
||||
@broadcast,
|
||||
class: {
|
||||
Broadcast: SerializableBroadcast,
|
||||
"ActiveStorage::Attachment".to_sym => ActiveStorage::SerializableAttachment
|
||||
},
|
||||
include: [:files]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def file_params
|
||||
broadcast_params = params.require(:broadcast).permit!
|
||||
broadcast_params[:files]
|
||||
end
|
||||
|
||||
def broadcasts
|
||||
policy_scope(@project.broadcasts.where(status: %w[created idle]))
|
||||
end
|
||||
|
||||
def set_broadcast
|
||||
@broadcast = authorize policy_scope(@project.broadcasts).find(params[:id])
|
||||
end
|
||||
end
|
||||
63
app/controllers/api/contract_templates_controller.rb
Normal file
@@ -0,0 +1,63 @@
|
||||
class Api::ContractTemplatesController < Api::ApiController
|
||||
include ProjectContext
|
||||
|
||||
before_action :set_project, only: [:index]
|
||||
before_action :set_contract_template, only: [:show]
|
||||
|
||||
def index
|
||||
render jsonapi: contract_templates, class: { "ContractTemplate": index_serializable }
|
||||
end
|
||||
|
||||
def show
|
||||
handle_response(@contract_template)
|
||||
end
|
||||
|
||||
def handle_response(contract_template, status = :ok)
|
||||
mapping = {
|
||||
"ContractTemplate": show_serializable,
|
||||
"ActiveStorage::Attachment".to_sym => ActiveStorage::SerializableAttachment,
|
||||
}
|
||||
|
||||
render jsonapi: contract_template,
|
||||
status: status,
|
||||
class: mapping
|
||||
end
|
||||
|
||||
def attributes_for_index
|
||||
[:name]
|
||||
end
|
||||
|
||||
def index_serializable
|
||||
name = "contract_template"
|
||||
attributes_to_send = attributes_for_index
|
||||
Class.new(JSONAPI::Serializable::Resource) do
|
||||
type name
|
||||
|
||||
attributes_to_send.each do |attr|
|
||||
attribute attr.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def show_serializable
|
||||
name = "contract_template"
|
||||
Class.new(JSONAPI::Serializable::Resource) do
|
||||
type name
|
||||
|
||||
ContractTemplate.new.attributes.keys.each do |attr|
|
||||
attribute attr.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def contract_templates
|
||||
date = Date.parse(params.fetch(:updated_since, Date.new.to_s))
|
||||
policy_scope(ContractTemplate.where(project_id: @project.id, updated_at: date..Float::INFINITY))
|
||||
end
|
||||
|
||||
def set_contract_template
|
||||
@contract_template = ContractTemplate.find(params[:id])
|
||||
end
|
||||
end
|
||||
11
app/controllers/api/location_releases_controller.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class Api::LocationReleasesController < Api::ReleasesController
|
||||
deserializable_resource :location_release, only: [:create, :update]
|
||||
|
||||
def model_name
|
||||
"location_release"
|
||||
end
|
||||
|
||||
def attributes_for_index
|
||||
[:name]
|
||||
end
|
||||
end
|
||||
11
app/controllers/api/material_releases_controller.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class Api::MaterialReleasesController < Api::ReleasesController
|
||||
deserializable_resource :material_release, only: [:create, :update]
|
||||
|
||||
def model_name
|
||||
"material_release"
|
||||
end
|
||||
|
||||
def attributes_for_index
|
||||
[:name]
|
||||
end
|
||||
end
|
||||
35
app/controllers/api/notes_controller.rb
Normal file
@@ -0,0 +1,35 @@
|
||||
class Api::NotesController < Api::ApiController
|
||||
before_action :set_release
|
||||
deserializable_resource :note, only: [:create]
|
||||
|
||||
def index
|
||||
render jsonapi: @release.notes, class: { Note: SerializableNote }
|
||||
end
|
||||
|
||||
def create
|
||||
note = @release.notes.new(note_create_params)
|
||||
note.user_id = current_user.id
|
||||
note.email = current_user.email
|
||||
note.save!
|
||||
render jsonapi: note, class: { Note: SerializableNote }, status: :created
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def model_name
|
||||
request.path.match(/(\w+_releases)/).captures.first.singularize
|
||||
end
|
||||
|
||||
def model_constant
|
||||
model_name.camelize.constantize
|
||||
end
|
||||
|
||||
def set_release
|
||||
@release = authorize model_constant.find(params["#{model_name}_id"])
|
||||
end
|
||||
|
||||
def note_create_params
|
||||
parameters = params.require(:note).permit!
|
||||
parameters.slice(:content)
|
||||
end
|
||||
end
|
||||
5
app/controllers/api/profiles_controller.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class Api::ProfilesController < Api::ApiController
|
||||
def show
|
||||
render jsonapi: current_user
|
||||
end
|
||||
end
|
||||
6
app/controllers/api/projects_controller.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
class Api::ProjectsController < Api::ApiController
|
||||
def index
|
||||
projects = Current.user.accessible_projects_for(Current.account)
|
||||
render jsonapi: projects
|
||||
end
|
||||
end
|
||||
182
app/controllers/api/releases_controller.rb
Normal file
@@ -0,0 +1,182 @@
|
||||
class Api::ReleasesController < Api::ApiController
|
||||
include ProjectContext
|
||||
include CreateReleasableJobs
|
||||
|
||||
before_action :set_project, only: [:index]
|
||||
before_action :set_release, only: [:show, :update]
|
||||
before_action :set_template_and_project, only: [:create]
|
||||
|
||||
def index
|
||||
render jsonapi: releases, class: { "#{model_name.camelize}": index_serializable }
|
||||
end
|
||||
|
||||
def show
|
||||
handle_response(@release)
|
||||
end
|
||||
|
||||
def create
|
||||
params_without_photo = release_create_params.except(:photos, :person_photo)
|
||||
release = filtered_releases.new(params_without_photo)
|
||||
release.project_id = @project.id
|
||||
release.contract_template_id = @contract_template.id
|
||||
handle_attachments(release, release_create_params[:photos])
|
||||
release.save!(context: :native)
|
||||
log_create_analytics
|
||||
after_create(release)
|
||||
handle_response(release, :created)
|
||||
end
|
||||
|
||||
def update
|
||||
if model_name == "acquired_media_release"
|
||||
authorize @release, :update_file_infos?
|
||||
|
||||
@release.attributes = release_create_params
|
||||
@release.save!
|
||||
else
|
||||
authorize @release, :update_photos?
|
||||
|
||||
handle_attachments(@release, release_create_params[:photos])
|
||||
@release.save!
|
||||
end
|
||||
|
||||
handle_response(@release)
|
||||
end
|
||||
|
||||
def handle_response(release, status = :ok)
|
||||
if model_name == "acquired_media_release"
|
||||
mapping = {
|
||||
"#{model_name.camelize}": SerializableAcquiredMediaRelease,
|
||||
FileInfo: SerializableFileInfo
|
||||
}
|
||||
|
||||
render jsonapi: release,
|
||||
status: status,
|
||||
class: mapping,
|
||||
include: [:file_infos]
|
||||
else
|
||||
mapping = {
|
||||
"#{model_name.camelize}": show_serializable,
|
||||
"ActiveStorage::Attachment".to_sym => ActiveStorage::SerializableAttachment,
|
||||
}
|
||||
|
||||
render jsonapi: release,
|
||||
status: status,
|
||||
class: mapping,
|
||||
include: [:photos, :guardian_photos]
|
||||
end
|
||||
end
|
||||
|
||||
def model_name
|
||||
raise "Please specify model name underscored and lowercase e.g. appearance_release"
|
||||
end
|
||||
|
||||
def model_constant
|
||||
model_name.camelize.constantize
|
||||
end
|
||||
|
||||
def attributes_for_index
|
||||
raise "Please specify attributes for index"
|
||||
end
|
||||
|
||||
def index_serializable
|
||||
name = model_name
|
||||
attributes_to_send = attributes_for_index
|
||||
Class.new(JSONAPI::Serializable::Resource) do
|
||||
type name
|
||||
|
||||
attributes_to_send.each do |attr|
|
||||
attribute attr.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def show_serializable
|
||||
name = model_name
|
||||
constant = model_constant
|
||||
Class.new(JSONAPI::Serializable::Resource) do
|
||||
type name
|
||||
|
||||
constant.new.attributes.keys.each do |attr|
|
||||
attribute attr.to_sym
|
||||
end
|
||||
|
||||
if ["appearance_release", "talent_release"].include?(name)
|
||||
has_many :guardian_photos do
|
||||
data do
|
||||
[@object.guardian_photo.try(:attachment)].compact
|
||||
end
|
||||
meta do
|
||||
{ count: @object.try(:guardian_photo).try(:attached?) ? 1 : 0 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless name == "acquired_media_release"
|
||||
has_many :photos do
|
||||
if name == "appearance_release"
|
||||
data do
|
||||
[@object.photo.attachment]
|
||||
end
|
||||
end
|
||||
meta do
|
||||
{ count: @object.photos.size }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_deserializable
|
||||
constant = model_constant
|
||||
Class.new(JSONAPI::Deserializable::Resource) do
|
||||
constant.new.attributes.keys.except(:created_at, :updated_at, :id, :user_id).each do |attr|
|
||||
attribute attr.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def handle_attachments(release, params_photos)
|
||||
photos = params_photos || []
|
||||
photos.each do |photo|
|
||||
photo[:io] = StringIO.new(Base64.decode64(photo[:io]))
|
||||
release.photos.attach(io: photo[:io], filename: photo[:filename])
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def releases
|
||||
date = Date.parse(params.fetch(:updated_since, Date.new.to_s))
|
||||
table = model_constant.table_name
|
||||
policy_scope(model_constant.where(["#{table}.project_id = ? and #{table}.updated_at >= ?", @project.id, date]))
|
||||
end
|
||||
|
||||
def set_release
|
||||
@release = model_constant.find(params[:id])
|
||||
end
|
||||
|
||||
def set_template_and_project
|
||||
@contract_template = ContractTemplate.find(params[:contract_template_id])
|
||||
@project = @contract_template.project
|
||||
end
|
||||
|
||||
def filtered_releases
|
||||
if @project.present?
|
||||
policy_scope(@project.public_send(model_name.pluralize))
|
||||
else
|
||||
releases
|
||||
end
|
||||
end
|
||||
|
||||
def release_create_params
|
||||
parameters = params.require(model_name).permit!
|
||||
keys = model_constant.new.attributes.keys + [:guardian_photo, :person_photo, :photos, :signature, :signature_base64, :file_infos_attributes]
|
||||
parameters[:signature_base64] = parameters[:signature]
|
||||
parameters = parameters.slice(*keys).except(:created_at, :updated_at, :id, :user_id, :signature)
|
||||
parameters
|
||||
end
|
||||
|
||||
def log_create_analytics
|
||||
TrackAnalyticsJob.perform_later(Current.user, Current.account, :track_create_native_release, release_type: model_constant.to_s, account: @account, user_agent: request.user_agent, user_ip: request.remote_ip, application: :ios)
|
||||
end
|
||||
end
|
||||
78
app/controllers/api/sync_controller.rb
Normal file
@@ -0,0 +1,78 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::SyncController < Api::ApiController
|
||||
def index
|
||||
accessible_projects = current_user.accessible_projects_for(Current.account)
|
||||
@accounts = filter(current_user.accounts)
|
||||
@projects = filter(Project).all
|
||||
@contract_templates = filter(ContractTemplate.where(project: accessible_projects)).all
|
||||
@acquired_media_releases = (AcquiredMediaRelease.where(project: accessible_projects))
|
||||
@appearance_releases = (AppearanceRelease.where(project: accessible_projects))
|
||||
@location_releases = (LocationRelease.where(project: accessible_projects))
|
||||
@material_releases = (MaterialRelease.where(project: accessible_projects))
|
||||
@talent_releases = (TalentRelease.where(project: accessible_projects))
|
||||
@notes = notes_query(Note.where(notable: @appearance_releases + @location_releases + @material_releases + @talent_releases + @acquired_media_releases ))
|
||||
|
||||
render json: {
|
||||
data: {
|
||||
accounts: @accounts,
|
||||
projects: @projects,
|
||||
contract_templates: @contract_templates,
|
||||
acquired_media_releases: releases_query(@acquired_media_releases),
|
||||
appearance_releases: releases_query(@appearance_releases),
|
||||
location_releases: releases_query(@location_releases),
|
||||
material_releases: releases_query(@material_releases),
|
||||
talent_releases: releases_query(@talent_releases),
|
||||
notes: @notes
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def releases_query(release)
|
||||
filter(release).all.map { |release| release_json(release) }
|
||||
end
|
||||
|
||||
def notes_query(notes)
|
||||
filter(notes).all.map do |note|
|
||||
json = note.as_json
|
||||
json[:attributes][:email] = note.email
|
||||
json
|
||||
end
|
||||
end
|
||||
|
||||
def release_json(release)
|
||||
json = release.as_json
|
||||
|
||||
unless release.model_name.to_s == "AcquiredMediaRelease"
|
||||
json[:attributes][:photos] = release.photos.map do |photo|
|
||||
build_photo_object photo
|
||||
end
|
||||
end
|
||||
|
||||
if release.respond_to?(:guardian_photo)
|
||||
photo = release.guardian_photo
|
||||
json[:attributes][:guardian_photo] = photo.attached? ? build_photo_object(photo) : nil
|
||||
end
|
||||
|
||||
json
|
||||
end
|
||||
|
||||
def filter(relation)
|
||||
policy_scope(relation)
|
||||
end
|
||||
|
||||
def build_photo_object(photo)
|
||||
{
|
||||
id: photo.id.to_s,
|
||||
type: 'active_storage_attachment',
|
||||
attributes: {
|
||||
filename: photo.filename.to_s,
|
||||
content_type: photo.content_type,
|
||||
url: Rails.application.routes.url_helpers.rails_blob_url(photo, host: AppHost.new.domain_with_port),
|
||||
thumbnail_url: Rails.application.routes.url_helpers.rails_representation_url(photo.variant(resize: '150x150'), host: AppHost.new.domain_with_port)
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
21
app/controllers/api/talent_releases_controller.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
class Api::TalentReleasesController < Api::ReleasesController
|
||||
deserializable_resource :talent_release, only: [:create, :update]
|
||||
|
||||
def model_name
|
||||
"talent_release"
|
||||
end
|
||||
|
||||
def attributes_for_index
|
||||
[:person_name]
|
||||
end
|
||||
|
||||
def handle_attachments(release, _)
|
||||
super
|
||||
|
||||
guardian_photo = release_create_params[:guardian_photo]
|
||||
if guardian_photo
|
||||
guardian_photo[:io] = StringIO.new(Base64.decode64(guardian_photo[:io]))
|
||||
release.guardian_photo.attach(io: guardian_photo[:io], filename: guardian_photo[:filename])
|
||||
end
|
||||
end
|
||||
end
|
||||
36
app/controllers/api/user_token_controller.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
class Api::UserTokenController < Knock::AuthTokenController
|
||||
skip_before_action :verify_authenticity_token
|
||||
|
||||
rescue_from Exception, :with => :return_error
|
||||
|
||||
# Catch exception and return JSON-formatted error
|
||||
def return_error(exception)
|
||||
logger.error "==Handled======="
|
||||
logger.error exception.message
|
||||
logger.error exception.backtrace.join("\n")
|
||||
logger.error "==Handled======="
|
||||
case exception
|
||||
when ActiveRecord::RecordNotFound
|
||||
@status = 404
|
||||
@message = 'Record not found'
|
||||
when ActiveRecord::RecordInvalid
|
||||
@status = 422
|
||||
@message = 'Record invalid'
|
||||
when ArgumentError
|
||||
@status = 400
|
||||
@message = 'Argument Error'
|
||||
else
|
||||
@status = 500
|
||||
@message = 'Internal Error'
|
||||
end
|
||||
|
||||
# for some reason render json_errors is not working
|
||||
# simulating JSON API support
|
||||
render json: {
|
||||
errors: [{
|
||||
status: @status.to_s,
|
||||
title: @message
|
||||
}]
|
||||
}
|
||||
end
|
||||
end
|
||||
89
app/controllers/appearance_release_imports_controller.rb
Normal file
@@ -0,0 +1,89 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AppearanceReleaseImportsController < ApplicationController
|
||||
include AppearanceReleaseContext
|
||||
include ProjectContext
|
||||
include CreateReleasableJobs
|
||||
|
||||
before_action :set_project, only: [:create]
|
||||
|
||||
include ProjectLayout
|
||||
|
||||
def create
|
||||
authorize AppearanceRelease
|
||||
@failed_files = []
|
||||
attachments = appearance_release_params
|
||||
if attachments.nil?
|
||||
alert_message = t 'appearance_releases.create.no_attachments'
|
||||
else
|
||||
attachments.each do |attachment|
|
||||
create_imported_appearance_release attachment
|
||||
end
|
||||
end
|
||||
|
||||
unless @failed_files.empty?
|
||||
alert_message = t 'appearance_releases.create.failed_import'
|
||||
alert_message += '<br><ul>'
|
||||
@failed_files.each { |file_name| alert_message += "<li>#{file_name}</li>" }
|
||||
alert_message += '</ul>'
|
||||
end
|
||||
|
||||
redirect_to [@project, :appearance_releases], alert: alert_message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def appearance_releases
|
||||
if @project
|
||||
policy_scope(@project.appearance_releases)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def appearance_release_params
|
||||
params.require(:attachments)
|
||||
end
|
||||
|
||||
def build_appearance_release(params = {})
|
||||
authorize appearance_releases.build(params)
|
||||
end
|
||||
|
||||
def log_create_analytics
|
||||
TrackAnalyticsJob.perform_later(Current.user, Current.account, :track_create_non_native_release, release_type: AppearanceRelease.to_s, user_agent: request.user_agent, user_ip: request.remote_ip)
|
||||
end
|
||||
|
||||
def create_imported_appearance_release(attachment)
|
||||
blob = ActiveStorage::Blob.find_signed(attachment)
|
||||
return if blob.nil?
|
||||
|
||||
extension = blob.filename.extension_with_delimiter
|
||||
unless AppearanceRelease.acceptable_import_file_extensions.include? extension
|
||||
blob.purge
|
||||
@failed_files << blob.filename
|
||||
return
|
||||
end
|
||||
|
||||
random_contract_no = AppearanceRelease.random_contract_number.to_s
|
||||
appearance_release_params = {
|
||||
person_last_name: random_contract_no
|
||||
}
|
||||
|
||||
if blob.image?
|
||||
appearance_release_params[:person_photo] = attachment
|
||||
appearance_release_params[:person_first_name] = I18n.t('appearance_releases.shared.imported_appearance_release_headshot_name')
|
||||
elsif extension == '.pdf'
|
||||
appearance_release_params[:contract] = attachment
|
||||
appearance_release_params[:person_first_name] = I18n.t('appearance_releases.shared.imported_appearance_release_contract_name')
|
||||
end
|
||||
|
||||
appearance_release = build_appearance_release(appearance_release_params)
|
||||
|
||||
if appearance_release.save(context: :non_native)
|
||||
log_create_analytics
|
||||
after_create appearance_release
|
||||
else
|
||||
@failed_files << blob.filename
|
||||
end
|
||||
end
|
||||
end
|
||||
100
app/controllers/appearance_releases_controller.rb
Normal file
@@ -0,0 +1,100 @@
|
||||
class AppearanceReleasesController < ApplicationController
|
||||
include ProjectContext, AppearanceReleaseContext
|
||||
|
||||
before_action :set_project, only: [:index, :new, :create]
|
||||
before_action :set_appearance_release, only: [:edit, :update, :destroy]
|
||||
|
||||
include ProjectLayout
|
||||
|
||||
def index
|
||||
@appearance_releases = filtered_appearance_releases.order_by_recent.paginate(page: params[:page])
|
||||
end
|
||||
|
||||
def new
|
||||
@appearance_release = build_appearance_release
|
||||
end
|
||||
|
||||
def create
|
||||
@appearance_release = build_appearance_release(appearance_release_params)
|
||||
|
||||
if @appearance_release.save(context: :non_native)
|
||||
log_create_analytics
|
||||
AddHeadshotCollectionUidToProjectJob.perform_later(@project)
|
||||
SetTagsForReleasableJob.perform_later(@appearance_release)
|
||||
redirect_to [@project, :appearance_releases], notice: "The release has been imported. #{link_to_import_another}"
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@project = @appearance_release.project
|
||||
end
|
||||
|
||||
def update
|
||||
@project = @appearance_release.project
|
||||
|
||||
@appearance_release.attributes = appearance_release_params
|
||||
|
||||
if @appearance_release.save(context: :non_native)
|
||||
AddHeadshotCollectionUidToProjectJob.perform_later(@project)
|
||||
redirect_to [@project, :appearance_releases], notice: "The release has been updated"
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@project = @appearance_release.project
|
||||
|
||||
if @appearance_release.destroy
|
||||
AddHeadshotCollectionUidToProjectJob.perform_later(@appearance_release.project)
|
||||
|
||||
redirect_to [@project, :appearance_releases], alert: "The release has been deleted"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def appearance_releases
|
||||
if @project
|
||||
policy_scope(@project.appearance_releases)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def filtered_appearance_releases
|
||||
results = case params[:type_filter]
|
||||
when 'complete'
|
||||
appearance_releases.complete
|
||||
when 'incomplete'
|
||||
appearance_releases.incomplete
|
||||
else
|
||||
appearance_releases
|
||||
end
|
||||
results = results.search(params[:query]) if params[:query].present?
|
||||
results
|
||||
end
|
||||
|
||||
def appearance_release_params
|
||||
params.require(:appearance_release).permit(:contract, :guardian_address, :guardian_first_name, :guardian_last_name, :guardian_phone, :guardian_photo, :minor,
|
||||
:person_address, :person_first_name, :person_last_name, :person_phone, :person_email, :person_photo,
|
||||
:applicable_medium_id, :applicable_medium_text,
|
||||
:territory_id, :territory_text,
|
||||
:term_id, :term_text, :person_date_of_birth,
|
||||
:restriction_id, :restriction_text)
|
||||
end
|
||||
|
||||
def build_appearance_release(params = {})
|
||||
authorize appearance_releases.build(params)
|
||||
end
|
||||
|
||||
def link_to_import_another
|
||||
view_context.link_to "Import Another", [:new, @project, :appearance_release]
|
||||
end
|
||||
|
||||
def log_create_analytics
|
||||
TrackAnalyticsJob.perform_later(Current.user, Current.account, :track_create_non_native_release, release_type: AppearanceRelease.to_s, user_agent: request.user_agent, user_ip: request.remote_ip)
|
||||
end
|
||||
end
|
||||
70
app/controllers/application_controller.rb
Normal file
@@ -0,0 +1,70 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
include Oath::ControllerHelpers # Methods for authentication
|
||||
include Pundit # Methods for authorization
|
||||
include RememberMe::Controller
|
||||
|
||||
before_action :disable_browser_page_caching
|
||||
before_action :set_locale
|
||||
before_action :require_login
|
||||
|
||||
before_action :set_raven_context
|
||||
after_action :verify_authorized, except: :index
|
||||
after_action :verify_policy_scoped, only: :index
|
||||
|
||||
include SetCurrentRequestDetails
|
||||
before_action :redirect_accountless
|
||||
|
||||
private
|
||||
|
||||
def require_login
|
||||
if !masquerading? && remembered_user = remember("user")
|
||||
sign_in(remembered_user)
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
def redirect_accountless
|
||||
if Current.user && Current.account.nil?
|
||||
redirect_to accountless_user_path
|
||||
end
|
||||
end
|
||||
|
||||
def signed_in_as_admin?
|
||||
signed_in? && current_user.admin?
|
||||
end
|
||||
helper_method :signed_in_as_admin?
|
||||
|
||||
# Ensure that all url helpers include the current locale
|
||||
def default_url_options
|
||||
super.merge(locale: I18n.locale) # Use merge to avoid clobbering any options set during config
|
||||
end
|
||||
|
||||
# Set the locale for the current request
|
||||
def set_locale
|
||||
I18n.locale = params[:locale] || request.env["rack.locale"] || I18n.default_locale
|
||||
end
|
||||
|
||||
# Run authorization against Current.user which will ensure it works in async jobs and channels as well
|
||||
# All authorization depends on the Current.account as users will have a different role for each
|
||||
def pundit_user
|
||||
UserContext.new(Current.user, Current.account)
|
||||
end
|
||||
|
||||
# Set the context for Sentry exception handling service
|
||||
def set_raven_context
|
||||
account_id = (session[:active_account] || try(:current_user).try(:accounts).try(:first))
|
||||
Raven.user_context(id: current_user.id, account_id: account_id) if signed_in?
|
||||
Raven.extra_context(params: params.to_unsafe_h, url: request.url)
|
||||
end
|
||||
|
||||
def disable_browser_page_caching
|
||||
response.headers["Cache-Control"] = "no-cache, no-store"
|
||||
response.headers["Pragma"] = "no-cache"
|
||||
response.headers["Expires"] = "0"
|
||||
end
|
||||
|
||||
def masquerading?
|
||||
session[:admin_id].present?
|
||||
end
|
||||
helper_method :masquerading?
|
||||
end
|
||||
34
app/controllers/audio_reports_controller.rb
Normal file
@@ -0,0 +1,34 @@
|
||||
class AudioReportsController < ApplicationController
|
||||
def show
|
||||
report = build_report(params[:type])
|
||||
respond_to do |format|
|
||||
format.xlsx { send_data(report.to_xls, download_attributes_xls(report)) }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def video
|
||||
authorize policy_scope(Video).find(params[:video_id])
|
||||
end
|
||||
|
||||
def build_report(type)
|
||||
case type
|
||||
when "big"
|
||||
authorize ExcelReports::AudioReports::BrayInnovationGroupMusicCueReport.new(video)
|
||||
when "nat_geo"
|
||||
authorize ExcelReports::AudioReports::NatGeoMusicCueSheet.new(video)
|
||||
when "nat_geo-original"
|
||||
authorize ExcelReports::AudioReports::NatGeoOriginalMusicLog.new(video)
|
||||
else
|
||||
authorize ExcelReports::AudioReports::DiscoveryMusicCueReport.new(video)
|
||||
end
|
||||
end
|
||||
|
||||
def download_attributes_xls(report)
|
||||
{
|
||||
filename: report.filename,
|
||||
type: Mime[:xlsx]
|
||||
}
|
||||
end
|
||||
end
|
||||
58
app/controllers/bookmarks_controller.rb
Normal file
@@ -0,0 +1,58 @@
|
||||
class BookmarksController < ApplicationController
|
||||
before_action :set_video, except: [:destroy, :edit, :update]
|
||||
before_action :set_bookmark, only: [:destroy, :edit, :update]
|
||||
|
||||
def new
|
||||
@bookmark = @video.bookmarks.build(bookmark_params)
|
||||
end
|
||||
|
||||
def create
|
||||
@bookmark = @video.bookmarks.build(bookmark_params)
|
||||
|
||||
@bookmark.save
|
||||
@bookmarks = @video.bookmarks
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
@video = @bookmark.video
|
||||
|
||||
@bookmark.update(bookmark_params)
|
||||
@bookmarks = @video.bookmarks
|
||||
end
|
||||
|
||||
def destroy
|
||||
@video = @bookmark.video
|
||||
|
||||
@bookmark.destroy
|
||||
@bookmarks = @video.bookmarks
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def bookmark_params
|
||||
params.require(:bookmark).permit(:notes, :time_elapsed, :category)
|
||||
end
|
||||
|
||||
def bookmarks
|
||||
if @video
|
||||
policy_scope(@video.bookmarks)
|
||||
else
|
||||
policy_scope(Bookmark)
|
||||
end
|
||||
end
|
||||
|
||||
def videos
|
||||
policy_scope(Video)
|
||||
end
|
||||
|
||||
def set_bookmark
|
||||
@bookmark = authorize bookmarks.find(params[:id])
|
||||
end
|
||||
|
||||
def set_video
|
||||
@video = authorize videos.find(params[:video_id])
|
||||
end
|
||||
end
|
||||
109
app/controllers/broadcasts_controller.rb
Normal file
@@ -0,0 +1,109 @@
|
||||
class BroadcastsController < ApplicationController
|
||||
layout "project"
|
||||
|
||||
before_action :set_project
|
||||
before_action :build_broadcast, only: [:new, :create]
|
||||
before_action :set_broadcast, only: [:show, :destroy, :update]
|
||||
before_action :set_multi_view_broadcasts, only: [:show]
|
||||
|
||||
def index
|
||||
@broadcasts = filtered_broadcasts.order_by_recent.paginate(page: params[:page])
|
||||
end
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
def create
|
||||
@broadcast.attributes = broadcast_params
|
||||
|
||||
if @broadcast.save
|
||||
log_create_analytics
|
||||
redirect_to [@project, :broadcasts], notice: t(".notice")
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@broadcast.update(broadcast_params)
|
||||
@files = @broadcast.files.order("created_at DESC").paginate(page: 1)
|
||||
|
||||
pagination_content = ApplicationController.render html: helpers.will_paginate(@files, params: { active_tab: params[:active_tab] })
|
||||
BroadcastsChannel.file_upload_updates(@broadcast, @files, pagination_content)
|
||||
end
|
||||
|
||||
def show
|
||||
@conference_url = url_for [@broadcast.project, @broadcast, :zoom_meeting]
|
||||
@recordings = @broadcast.broadcast_recordings.order_by_recent.paginate(page: params[:page])
|
||||
@files = @broadcast.files.order("created_at DESC").paginate(page: params[:page])
|
||||
render layout: 'application'
|
||||
end
|
||||
|
||||
def destroy
|
||||
if @broadcast.destroy
|
||||
redirect_to [@project, :broadcasts], alert: t(".alert")
|
||||
else
|
||||
redirect_to [@project, :broadcasts], alert: t(".api_error")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def broadcast_params
|
||||
params.require(:broadcast).permit(:name, files: [])
|
||||
end
|
||||
|
||||
def set_project
|
||||
@project = policy_scope(Project).find(params[:project_id])
|
||||
end
|
||||
|
||||
def set_broadcast
|
||||
@broadcast = authorize policy_scope(Broadcast).find(params[:id])
|
||||
end
|
||||
|
||||
def build_broadcast
|
||||
@broadcast = authorize @project.broadcasts.build
|
||||
end
|
||||
|
||||
def broadcasts
|
||||
authorize policy_scope(@project.broadcasts)
|
||||
end
|
||||
|
||||
def set_multi_view_broadcasts
|
||||
authorized_broadcasts = authorize policy_scope(Broadcast).where(id: params[:multi_view_ids]).order_by_recent
|
||||
@multi_view_broadcasts = authorized_broadcasts.map { |b| MultiViewBroadcast.new(b, params[:multi_view_ids]) }
|
||||
end
|
||||
|
||||
def filtered_broadcasts
|
||||
results = broadcasts
|
||||
|
||||
if params[:query].present?
|
||||
results = results.search(params[:query])
|
||||
end
|
||||
|
||||
results
|
||||
end
|
||||
|
||||
def log_create_analytics
|
||||
TrackAnalyticsJob.perform_later(Current.user, Current.account, :track_create_live_stream, user_agent: request.user_agent, user_ip: request.remote_ip)
|
||||
end
|
||||
|
||||
class MultiViewBroadcast
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
delegate_missing_to :@broadcast
|
||||
|
||||
def initialize(broadcast, multi_view_ids)
|
||||
@broadcast = broadcast
|
||||
@multi_view_ids = multi_view_ids
|
||||
end
|
||||
|
||||
def url
|
||||
project_broadcast_path(@broadcast.project, @broadcast, multi_view_ids: @multi_view_ids, locale: I18n.locale)
|
||||
end
|
||||
|
||||
def uid
|
||||
id
|
||||
end
|
||||
end
|
||||
end
|
||||
40
app/controllers/bulk_taggings_controller.rb
Normal file
@@ -0,0 +1,40 @@
|
||||
class BulkTaggingsController < ApplicationController
|
||||
before_action :set_project
|
||||
before_action :set_releasables
|
||||
before_action :authorize_bulk_taggings, only: [:new, :create]
|
||||
|
||||
def new
|
||||
@releasable_ids = releasable_params[:ids]
|
||||
end
|
||||
|
||||
def create
|
||||
@releasables.each do |releasable|
|
||||
releasable.tag_list.add(params[:name])
|
||||
releasable.save
|
||||
end
|
||||
|
||||
all_releasables
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def releasable_params
|
||||
{ type: params[:releasable_name].classify.constantize, ids: JSON.parse(params[:releasable_ids]) }
|
||||
end
|
||||
|
||||
def set_releasables
|
||||
@releasables = releasable_params[:type].where(id: releasable_params[:ids])
|
||||
end
|
||||
|
||||
def set_project
|
||||
@project = policy_scope(Project).find(params[:project_id])
|
||||
end
|
||||
|
||||
def authorize_bulk_taggings
|
||||
authorize @releasables.first.tags
|
||||
end
|
||||
|
||||
def all_releasables
|
||||
@all_releasables = policy_scope(releasable_params[:type].where(project: @project)).order_by_recent.paginate(page: params[:page])
|
||||
end
|
||||
end
|
||||
0
app/controllers/concerns/.keep
Normal file
13
app/controllers/concerns/acquired_media_release_context.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module AcquiredMediaReleaseContext
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def acquired_media_releases
|
||||
policy_scope(AcquiredMediaRelease)
|
||||
end
|
||||
|
||||
def set_acquired_media_release
|
||||
acquired_media_release_id = params[:acquired_media_release_id] || params[:id]
|
||||
|
||||
@acquired_media_release = authorize acquired_media_releases.find(acquired_media_release_id)
|
||||
end
|
||||
end
|
||||
13
app/controllers/concerns/appearance_release_context.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module AppearanceReleaseContext
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def appearance_releases
|
||||
policy_scope(AppearanceRelease)
|
||||
end
|
||||
|
||||
def set_appearance_release
|
||||
appearance_release_id = params[:appearance_release_id] || params[:id]
|
||||
|
||||
@appearance_release = authorize appearance_releases.find(appearance_release_id)
|
||||
end
|
||||
end
|
||||
21
app/controllers/concerns/create_releasable_jobs.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module CreateReleasableJobs
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def after_create(releasable)
|
||||
if headshots?(releasable)
|
||||
AddHeadshotCollectionUidToProjectJob.perform_later(releasable.project)
|
||||
end
|
||||
SetTagsForReleasableJob.perform_later(releasable)
|
||||
if releasable.contract_template.present?
|
||||
AttachContractToReleasableJob.perform_later(releasable)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def headshots?(releasable)
|
||||
[AppearanceRelease, TalentRelease].include?(releasable.class)
|
||||
end
|
||||
end
|
||||
13
app/controllers/concerns/location_release_context.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module LocationReleaseContext
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def location_releases
|
||||
policy_scope(LocationRelease)
|
||||
end
|
||||
|
||||
def set_location_release
|
||||
location_release_id = params[:location_release_id] || params[:id]
|
||||
|
||||
@location_release = authorize location_releases.find(location_release_id)
|
||||
end
|
||||
end
|
||||
13
app/controllers/concerns/music_release_context.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module MusicReleaseContext
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def music_releases
|
||||
policy_scope(MusicRelease)
|
||||
end
|
||||
|
||||
def set_music_release
|
||||
music_release_id = params[:music_release_id] || params[:id]
|
||||
|
||||
@music_release = authorize music_releases.find(music_release_id)
|
||||
end
|
||||
end
|
||||
13
app/controllers/concerns/project_context.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module ProjectContext
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def projects
|
||||
policy_scope(Project)
|
||||
end
|
||||
|
||||
def set_project
|
||||
project_id = params[:project_id]
|
||||
|
||||
@project = projects.find(project_id)
|
||||
end
|
||||
end
|
||||
15
app/controllers/concerns/project_layout.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
module ProjectLayout
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
layout "project"
|
||||
|
||||
breadcrumb -> { t("shared.files") }, -> { project_for_layout }, match: :exact
|
||||
breadcrumb -> { controller_name.titleize }, -> { [project_for_layout, controller_name] }, match: :exact
|
||||
breadcrumb -> { action_name.titleize }, :url_for, only: [:new, :edit]
|
||||
|
||||
def project_for_layout
|
||||
@project || instance_variable_get("@#{controller_name.singularize}").project
|
||||
end
|
||||
end
|
||||
end
|
||||
10
app/controllers/concerns/set_current_request_details.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
module SetCurrentRequestDetails
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action do
|
||||
Current.user = current_user if signed_in?
|
||||
Current.account = Account.find(session[:active_account]) if session[:active_account]
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/controllers/concerns/talent_release_context.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module TalentReleaseContext
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def talent_releases
|
||||
policy_scope(TalentRelease)
|
||||
end
|
||||
|
||||
def set_talent_release
|
||||
talent_release_id = params[:talent_release_id] || params[:id]
|
||||
|
||||
@talent_release = authorize talent_releases.find(talent_release_id)
|
||||
end
|
||||
end
|
||||
34
app/controllers/contract_downloads_controller.rb
Normal file
@@ -0,0 +1,34 @@
|
||||
class ContractDownloadsController < ApplicationController
|
||||
include ProjectContext
|
||||
|
||||
before_action :set_project, only: [:index, :create]
|
||||
|
||||
include ProjectLayout
|
||||
|
||||
def create
|
||||
authorize policy_scope(Download).create
|
||||
fetch_releases
|
||||
|
||||
download = @project.downloads.create!(release_type: params[:release_type])
|
||||
other_downloads_in_progress = @project.downloads.unfinished_desc_order.offset(1)
|
||||
|
||||
if other_downloads_in_progress.any?
|
||||
in_progress_downloads_details = render_to_string "_other_pending_downloads", locals: { downloads: other_downloads_in_progress, release_type: params[:release_type] }, :layout => false
|
||||
ProjectsChannel.broadcast_download_generation_update(download, in_progress_downloads_details)
|
||||
else
|
||||
ProjectsChannel.broadcast_download_generation_update(download, I18n.t("contract_downloads.download.pending", release_type: params[:release_type].titleize))
|
||||
end
|
||||
|
||||
GenerateContractsZipJob.perform_later(@project, download, params[:release_type], @releases.ids)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_releases
|
||||
@releases = policy_scope(@project.public_send(releases))
|
||||
end
|
||||
|
||||
def releases
|
||||
params[:release_type].constantize.model_name.plural
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,69 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ContractTemplates::BlankContractsController < ApplicationController
|
||||
before_action :set_contract_template
|
||||
|
||||
def show
|
||||
send_file contract.to_pdf, download_attributes
|
||||
end
|
||||
|
||||
def new
|
||||
authorize BlankContract
|
||||
render 'blank_contracts/new'
|
||||
end
|
||||
|
||||
def create
|
||||
if number_of_copies_valid?
|
||||
send_file contract.to_pdf, download_attributes
|
||||
else
|
||||
authorize BlankContract
|
||||
redirect_back fallback_location: [:new, @contract_template, :blank_contracts], notice: t('.number_of_copies_invalid_notice')
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def number_of_copies_valid?
|
||||
number_of_copies = params[:number_of_copies].to_i
|
||||
number_of_copies.positive?
|
||||
rescue StandardError
|
||||
false
|
||||
end
|
||||
|
||||
def contract_templates
|
||||
policy_scope(ContractTemplate)
|
||||
end
|
||||
|
||||
def set_contract_template
|
||||
@contract_template = contract_templates.find(params[:contract_template_id])
|
||||
end
|
||||
|
||||
def releasable
|
||||
create_releasable_instance
|
||||
end
|
||||
|
||||
def create_releasable_instance
|
||||
template_release_type = @contract_template[:release_type]
|
||||
releasable = "#{template_release_type}_release".classify.safe_constantize.new
|
||||
releasable.contract_template = @contract_template
|
||||
releasable.project_id = @contract_template.project_id
|
||||
releasable
|
||||
end
|
||||
|
||||
def contract
|
||||
authorize BlankContract.new(releasable, params[:number_of_copies])
|
||||
end
|
||||
|
||||
def download_attributes
|
||||
{
|
||||
disposition: 'inline',
|
||||
filename: contract.filename,
|
||||
type: 'application/pdf'
|
||||
}
|
||||
end
|
||||
|
||||
def render_sample_html
|
||||
# NOTE: For development purposes, this contract renders with the current locale, not the locale of the release itself
|
||||
render contract.render_attributes
|
||||
end
|
||||
end
|
||||
28
app/controllers/contract_templates/qr_codes_controller.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
class ContractTemplates::QrCodesController < ApplicationController
|
||||
before_action :set_contract_template
|
||||
|
||||
def show
|
||||
send_file qr_code.to_png, download_attributes
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def contract_templates
|
||||
policy_scope(ContractTemplate)
|
||||
end
|
||||
|
||||
def set_contract_template
|
||||
@contract_template = contract_templates.find(params[:contract_template_id])
|
||||
end
|
||||
|
||||
def qr_code
|
||||
authorize QrCode.build_from_contract_template(@contract_template)
|
||||
end
|
||||
|
||||
def download_attributes
|
||||
{
|
||||
filename: qr_code.filename,
|
||||
type: "image/png",
|
||||
}
|
||||
end
|
||||
end
|
||||