commit 858fafc3c5305149dc37498a16f6af47e072e225 Author: Senad Uka Date: Sun May 31 22:38:19 2020 +0200 Initial commit diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 0000000..e94f814 --- /dev/null +++ b/.browserslistrc @@ -0,0 +1 @@ +defaults diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..92c84ee --- /dev/null +++ b/.env.sample @@ -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= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d36a671 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/.pairs b/.pairs new file mode 100644 index 0000000..077e0aa --- /dev/null +++ b/.pairs @@ -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 + diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..9b85b09 --- /dev/null +++ b/.rspec @@ -0,0 +1,3 @@ +--require spec_helper + +--tag ~integration \ No newline at end of file diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..bd4053b --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.6.3 \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..7c011f9 --- /dev/null +++ b/Gemfile @@ -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' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..b1f67d7 --- /dev/null +++ b/Gemfile.lock @@ -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 diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..e7525b2 --- /dev/null +++ b/Procfile @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..201510c --- /dev/null +++ b/README.md @@ -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 +``` diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..e85f913 --- /dev/null +++ b/Rakefile @@ -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 diff --git a/app.json b/app.json new file mode 100644 index 0000000..acd353c --- /dev/null +++ b/app.json @@ -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" + } + } + } +} diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js new file mode 100644 index 0000000..b16e53d --- /dev/null +++ b/app/assets/config/manifest.js @@ -0,0 +1,3 @@ +//= link_tree ../images +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css diff --git a/app/assets/images/.keep b/app/assets/images/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/images/BiG-logo.svg b/app/assets/images/BiG-logo.svg new file mode 100644 index 0000000..c915f64 --- /dev/null +++ b/app/assets/images/BiG-logo.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/anonymous_person.png b/app/assets/images/anonymous_person.png new file mode 100644 index 0000000..7a47277 Binary files /dev/null and b/app/assets/images/anonymous_person.png differ diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico new file mode 100644 index 0000000..ef526c1 Binary files /dev/null and b/app/assets/images/favicon.ico differ diff --git a/app/assets/images/folder.svg b/app/assets/images/folder.svg new file mode 100644 index 0000000..ec4150e --- /dev/null +++ b/app/assets/images/folder.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/assets/images/logo-deliverME-white.png b/app/assets/images/logo-deliverME-white.png new file mode 100644 index 0000000..d02b7fe Binary files /dev/null and b/app/assets/images/logo-deliverME-white.png differ diff --git a/app/assets/images/logo-releaseME-black.png b/app/assets/images/logo-releaseME-black.png new file mode 100644 index 0000000..58a9ec9 Binary files /dev/null and b/app/assets/images/logo-releaseME-black.png differ diff --git a/app/assets/images/logo_castme.png b/app/assets/images/logo_castme.png new file mode 100644 index 0000000..2425bd8 Binary files /dev/null and b/app/assets/images/logo_castme.png differ diff --git a/app/assets/images/logo_deliverme.png b/app/assets/images/logo_deliverme.png new file mode 100644 index 0000000..92fe1bf Binary files /dev/null and b/app/assets/images/logo_deliverme.png differ diff --git a/app/assets/images/logo_directme.png b/app/assets/images/logo_directme.png new file mode 100644 index 0000000..a5d5376 Binary files /dev/null and b/app/assets/images/logo_directme.png differ diff --git a/app/assets/images/logo_editme.png b/app/assets/images/logo_editme.png new file mode 100644 index 0000000..61b2957 Binary files /dev/null and b/app/assets/images/logo_editme.png differ diff --git a/app/assets/images/logo_expenseme.png b/app/assets/images/logo_expenseme.png new file mode 100644 index 0000000..e6aa5ea Binary files /dev/null and b/app/assets/images/logo_expenseme.png differ diff --git a/app/assets/images/logo_releaseme.png b/app/assets/images/logo_releaseme.png new file mode 100644 index 0000000..f8db6eb Binary files /dev/null and b/app/assets/images/logo_releaseme.png differ diff --git a/app/assets/images/releaseme-logo.svg b/app/assets/images/releaseme-logo.svg new file mode 100644 index 0000000..88d7580 --- /dev/null +++ b/app/assets/images/releaseme-logo.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/javascripts/appearance_release_import.js b/app/assets/javascripts/appearance_release_import.js new file mode 100644 index 0000000..4981fe3 --- /dev/null +++ b/app/assets/javascripts/appearance_release_import.js @@ -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'); + }); +}); diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js new file mode 100644 index 0000000..8ffba12 --- /dev/null +++ b/app/assets/javascripts/application.js @@ -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 . diff --git a/app/assets/javascripts/cable.js b/app/assets/javascripts/cable.js new file mode 100644 index 0000000..739aa5f --- /dev/null +++ b/app/assets/javascripts/cable.js @@ -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); diff --git a/app/assets/javascripts/channels/.keep b/app/assets/javascripts/channels/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/javascripts/channels/broadcasts.coffee b/app/assets/javascripts/channels/broadcasts.coffee new file mode 100644 index 0000000..7b5367f --- /dev/null +++ b/app/assets/javascripts/channels/broadcasts.coffee @@ -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 + \ No newline at end of file diff --git a/app/assets/javascripts/channels/projects.coffee b/app/assets/javascripts/channels/projects.coffee new file mode 100644 index 0000000..41061c9 --- /dev/null +++ b/app/assets/javascripts/channels/projects.coffee @@ -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 diff --git a/app/assets/javascripts/clipboard.js b/app/assets/javascripts/clipboard.js new file mode 100644 index 0000000..57cec00 --- /dev/null +++ b/app/assets/javascripts/clipboard.js @@ -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 = $(""); + $("body").append($temp); + $temp.val(value).select(); + document.execCommand("copy"); + $temp.remove(); + } + + // Perform the copying + copyToClipboard(value); +}); diff --git a/app/assets/javascripts/collapse_select.js b/app/assets/javascripts/collapse_select.js new file mode 100644 index 0000000..d3d19e4 --- /dev/null +++ b/app/assets/javascripts/collapse_select.js @@ -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"); + } +}); diff --git a/app/assets/javascripts/collapse_state.js b/app/assets/javascripts/collapse_state.js new file mode 100644 index 0000000..dbd0049 --- /dev/null +++ b/app/assets/javascripts/collapse_state.js @@ -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.."); +} diff --git a/app/assets/javascripts/custom_file_input.js b/app/assets/javascripts/custom_file_input.js new file mode 100644 index 0000000..e1a8203 --- /dev/null +++ b/app/assets/javascripts/custom_file_input.js @@ -0,0 +1,3 @@ +$(document).on("turbolinks:load", function() { + bsCustomFileInput.init() +}) diff --git a/app/assets/javascripts/digital_signature.js b/app/assets/javascripts/digital_signature.js new file mode 100644 index 0000000..5d6eee9 --- /dev/null +++ b/app/assets/javascripts/digital_signature.js @@ -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); + }); +}); diff --git a/app/assets/javascripts/evaporate.js b/app/assets/javascripts/evaporate.js new file mode 100644 index 0000000..f2572e8 --- /dev/null +++ b/app/assets/javascripts/evaporate.js @@ -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) + } +}); diff --git a/app/assets/javascripts/file_upload_progress.js b/app/assets/javascripts/file_upload_progress.js new file mode 100644 index 0000000..3ccf1f0 --- /dev/null +++ b/app/assets/javascripts/file_upload_progress.js @@ -0,0 +1,59 @@ +App.FileUploadProgress = { + // Creates a new progress bar element + createProgressBar: id => { + //
+ const progress = document.createElement("div") + progress.setAttribute("id", `progress_bar_${id}`) + progress.setAttribute("hidden", "hidden") + progress.classList.add("mt-2") + progress.classList.add("progress") + + //
+ 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) => { + //
{ + 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}`) + } +} diff --git a/app/assets/javascripts/multi_view_broadcasts.js b/app/assets/javascripts/multi_view_broadcasts.js new file mode 100644 index 0000000..27325ab --- /dev/null +++ b/app/assets/javascripts/multi_view_broadcasts.js @@ -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'); + } +} diff --git a/app/assets/javascripts/photo_preview.js b/app/assets/javascripts/photo_preview.js new file mode 100644 index 0000000..3f0cbb8 --- /dev/null +++ b/app/assets/javascripts/photo_preview.js @@ -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 = $("").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"); + }); +}); diff --git a/app/assets/javascripts/play_previous_recordings.js b/app/assets/javascripts/play_previous_recordings.js new file mode 100644 index 0000000..f2d1212 --- /dev/null +++ b/app/assets/javascripts/play_previous_recordings.js @@ -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(' '); +}); + diff --git a/app/assets/javascripts/popover.js b/app/assets/javascripts/popover.js new file mode 100644 index 0000000..ba8ddfb --- /dev/null +++ b/app/assets/javascripts/popover.js @@ -0,0 +1,9 @@ +// $(document).on("turbolinks:load", function() { +// $('body').popover({ +// selector: '[data-toggle=popover]' +// }); +// }); + +$(document).on("turbolinks:load", function() { + $('[data-toggle=popover]').popover(); +}); diff --git a/app/assets/javascripts/select_releasables.js b/app/assets/javascripts/select_releasables.js new file mode 100644 index 0000000..fe175e1 --- /dev/null +++ b/app/assets/javascripts/select_releasables.js @@ -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 = $('').attr({ type: 'hidden', name: 'releasable_ids', value: JSON.stringify(releasable_ids) }); + var input_name = $('').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'); +}); \ No newline at end of file diff --git a/app/assets/javascripts/tooltip.js b/app/assets/javascripts/tooltip.js new file mode 100644 index 0000000..0d75283 --- /dev/null +++ b/app/assets/javascripts/tooltip.js @@ -0,0 +1,5 @@ +$(document).on("turbolinks:load", function() { + $('body').tooltip({ + selector: '[data-toggle=tooltip]' + }); +}); diff --git a/app/assets/javascripts/trix.js b/app/assets/javascripts/trix.js new file mode 100644 index 0000000..6c9e9c7 --- /dev/null +++ b/app/assets/javascripts/trix.js @@ -0,0 +1,4 @@ +// Do not allow file attachments in rich text content +addEventListener("trix-file-accept", function(event) { + event.preventDefault(); +}) diff --git a/app/assets/javascripts/turbolinks-csp.js b/app/assets/javascripts/turbolinks-csp.js new file mode 100644 index 0000000..192f338 --- /dev/null +++ b/app/assets/javascripts/turbolinks-csp.js @@ -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 + <%= javascript_tag nonce: true do %> + hbspt.forms.create({ + portalId: "7344617", + formId: "ac9ebc7e-adfd-4308-ac5a-3052b6891b64" + }); + <% end %> +
+
+
+ + +
+
+ <%= card_header text: "Sign Up Below For Instant Access" %> +
+ <%= render "form" %> +
+
+
+ + diff --git a/app/views/accounts/update.js.erb b/app/views/accounts/update.js.erb new file mode 100644 index 0000000..8cf5c9d --- /dev/null +++ b/app/views/accounts/update.js.erb @@ -0,0 +1,2 @@ +$("#account_logo_display").html("<%= j render(partial: 'logo', locals: { account: @account }) %>"); +$("#account_logo").prop('disabled', false); \ No newline at end of file diff --git a/app/views/acquired_media_releases/_acquired_media_release.html.erb b/app/views/acquired_media_releases/_acquired_media_release.html.erb new file mode 100644 index 0000000..4741e61 --- /dev/null +++ b/app/views/acquired_media_releases/_acquired_media_release.html.erb @@ -0,0 +1,48 @@ + + <%= check_box_tag "acquired_media_release_ids[]", acquired_media_release.id, false %> + + <%= acquired_media_release.name %> + + + <% if acquired_media_release.file_infos.any? %> + <%= acquired_media_release.file_infos.size %> + <% else %> + <%= fa_icon("warning", text: t(".no_media"), class: "text-danger") %> + <% end %> + + + <%= notes_preview acquired_media_release.notes.order_by_recent %> + + "> + <%= tags_preview acquired_media_release, acquired_media_release.tags %> + + + <%= acquired_media_release.signed_on %> + + +
+ <%= button_tag t(".actions.manage"), class: "btn btn-light btn-sm dropdown-toggle border", data: { toggle: "dropdown", boundary: "window" }, aria: { haspopup: true, expanded: false } %> + +
+ + + diff --git a/app/views/acquired_media_releases/_form.html.erb b/app/views/acquired_media_releases/_form.html.erb new file mode 100644 index 0000000..049c736 --- /dev/null +++ b/app/views/acquired_media_releases/_form.html.erb @@ -0,0 +1,37 @@ +<%= errors_summary_for acquired_media_release %> +<%= bootstrap_form_with model: model, local: true do |form| %> + <%= field_set_tag content_tag(:span, t(".acquired_media_details.heading"), class: "h6 text-muted text-uppercase") do %> +
+ <%= form.text_field :name, required: true, wrapper_class: "col-12" %> +
+ <%= form.form_group :categories, label: { text: "Categories" } do %> + <% AcquiredMediaRelease::CATEGORIES.each do |category| %> + <%= form.check_box :categories, { multiple: true, label: category }, category, false %> + <% end %> + <% end %> + <% end %> + +
+ + <%= field_set_tag content_tag(:span, t(".files.heading"), class: "h6 text-muted text-uppercase") do %> +
+ <%= fa_icon "warning" %> + For optimal accuracy, please ensure video file names and photo file names match the source file name in the editing sequence. +
+ <%= render "shared/file_infos_dropzone", form: form, releasable: acquired_media_release %> + <% end %> + +
+ + <%= field_set_tag content_tag(:span, t(".contract_and_rights.heading"), class: "h6 text-muted text-uppercase") do %> + <%= render "shared/contract_fields", form: form, release: acquired_media_release %> + <%= render "shared/exploitable_rights_fields", form: form %> + <% end %> + +
+ <%= link_to t("shared.cancel"), [acquired_media_release.project, :acquired_media_releases], class: "col-3 text-reset" %> +
+ <%= form.button id: "submit_release", class: class_string("btn btn-block", ["btn-success", "btn-primary"] => acquired_media_release.new_record?), data: { disable_with: t("shared.disable_with") } %> +
+
+<% end %> diff --git a/app/views/acquired_media_releases/edit.html.erb b/app/views/acquired_media_releases/edit.html.erb new file mode 100644 index 0000000..367e8f8 --- /dev/null +++ b/app/views/acquired_media_releases/edit.html.erb @@ -0,0 +1,6 @@ +
+ <%= card_header text: t(".heading"), close_action_path: [@project, :acquired_media_releases] %> +
+ <%= render "form", model: @acquired_media_release, acquired_media_release: @acquired_media_release %> +
+
diff --git a/app/views/acquired_media_releases/index.html.erb b/app/views/acquired_media_releases/index.html.erb new file mode 100644 index 0000000..8a9f7d3 --- /dev/null +++ b/app/views/acquired_media_releases/index.html.erb @@ -0,0 +1,51 @@ +
+
+
+ <% if policy(AcquiredMediaRelease).new? %> + <%= link_to fa_icon("plus", text: t(".actions.new")), [:new, @project, :acquired_media_release], class: "btn btn-primary mr-2 mb-2" %> + <% end %> + + <% if @acquired_media_releases.any? && policy(AcquiredMediaRelease).tag_multiple? %> + <%= button_to_bulk_tagging(@project) %> + <% end %> + + <% if @acquired_media_releases.any? && policy(AcquiredMediaRelease).download_multiple? %> + <%= link_to "Download All", [@project, :contract_downloads, release_type: @acquired_media_releases.name], method: :post, remote: true, class: "btn btn-light border ml-auto mr-2 mb-2", data: { + disable_with: "Please wait..." } %> + <% end %> + + <%= bootstrap_form_with url: [@project, :acquired_media_releases], method: :get, remote: true, layout: :inline, id: "search" do |form| %> + <%= form.search_field :query, hide_label: true, placeholder: t(".actions.search"), class: "rounded-pill-right", value: params[:query], prepend: form.button(fa_icon("search"), class: "btn btn-light border mb-2 rounded-pill-left") %> + <% end %> +
+
+
+ +
+ + + + + + + + + + + + + + <% if @acquired_media_releases.any? %> + <%= render @acquired_media_releases %> + <% else %> + + + + <% end %> + +
<%= check_box_tag "acquired_media_release_ids[]", false, false %><%= AcquiredMediaRelease.human_attribute_name(:name) %><%= t(".table_headers.file_infos_count") %><%= t(".table_headers.notes") %><%= t(".table_headers.tags") %><%= t(".table_headers.signed_at") %>
<%= t(".empty") %>
+
+ +
+ <%= will_paginate @acquired_media_releases %> +
diff --git a/app/views/acquired_media_releases/index.js.erb b/app/views/acquired_media_releases/index.js.erb new file mode 100644 index 0000000..4bc7e72 --- /dev/null +++ b/app/views/acquired_media_releases/index.js.erb @@ -0,0 +1,3 @@ +$("#acquired_media_releases").html("<%= j render(@acquired_media_releases) %>"); +$("form input[type='search']").val("<%= params[:query] %>"); +$("#acquired_media_releases_pagination").html("<%= j will_paginate(@acquired_media_releases) %>"); diff --git a/app/views/acquired_media_releases/new.html.erb b/app/views/acquired_media_releases/new.html.erb new file mode 100644 index 0000000..183aa79 --- /dev/null +++ b/app/views/acquired_media_releases/new.html.erb @@ -0,0 +1,6 @@ +
+ <%= card_header text: t(".heading"), close_action_path: [@project, :acquired_media_releases] %> +
+ <%= render "form", model: [@project, @acquired_media_release], acquired_media_release: @acquired_media_release %> +
+
diff --git a/app/views/active_storage/blobs/_blob.html.erb b/app/views/active_storage/blobs/_blob.html.erb new file mode 100644 index 0000000..049f57e --- /dev/null +++ b/app/views/active_storage/blobs/_blob.html.erb @@ -0,0 +1,14 @@ +
attachment--<%= blob.filename.extension %>"> + <% if blob.representable? %> + <%= image_tag blob.representation(resize_to_fit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %> + <% end %> + +
+ <% if caption = blob.try(:caption) %> + <%= caption %> + <% else %> + <%= blob.filename %> + <%= number_to_human_size blob.byte_size %> + <% end %> +
+
diff --git a/app/views/admin/accounts/_account.html.erb b/app/views/admin/accounts/_account.html.erb new file mode 100644 index 0000000..1ba6d5e --- /dev/null +++ b/app/views/admin/accounts/_account.html.erb @@ -0,0 +1,33 @@ + + + <%= account.name %> + + + <%= account.plan_name %> + + + <%= account.projects.size %> + + + <%= number_with_delimiter convert_duration(account.current_month_video_duration_total, from: :seconds, to: :minutes).round %> minutes + + + <%= number_with_delimiter convert_duration(account.video_duration_total, from: :seconds, to: :minutes).round %> minutes + + + <%= number_to_human_size account.storage_total %> + + + <%= time_ago_in_words(account.created_at) %> ago + + +
+ <%= button_tag "Manage", class: "btn btn-light btn-sm dropdown-toggle border", data: { toggle: "dropdown", boundary: "window" }, aria: { haspopup: true, expanded: false } %> + +
+ + diff --git a/app/views/admin/accounts/_form.html.erb b/app/views/admin/accounts/_form.html.erb new file mode 100644 index 0000000..e1618e4 --- /dev/null +++ b/app/views/admin/accounts/_form.html.erb @@ -0,0 +1,14 @@ +<%= errors_summary_for account %> +<%= bootstrap_form_with model: [:admin, account], local: true do |form| %> + <%= field_set_tag content_tag(:span, "Account Details", class: "h6 text-muted text-uppercase") do %> + <%= form.text_field :name %> + <%= form.select :plan_uid, options_for_account_plan_select, { label: "Plan" }, class: "form-control custom-select" %> + <% end %> + +
+ <%= link_to t("shared.cancel"), [:admin, :accounts], class: "col-3 text-reset" %> +
+ <%= form.button class: class_string("btn btn-block", ["btn-success", "btn-primary"] => account.new_record?), data: { disable_with: t("shared.disable_with") } %> +
+
+<% end %> diff --git a/app/views/admin/accounts/_user.html.erb b/app/views/admin/accounts/_user.html.erb new file mode 100644 index 0000000..9126544 --- /dev/null +++ b/app/views/admin/accounts/_user.html.erb @@ -0,0 +1,16 @@ + + <%= user.email %> + <%= user.account_auths.map(&:role).join(',') %> + + <% if user != Current.user %> +
+ <%= button_tag "Manage", class: "btn btn-light btn-sm dropdown-toggle border", data: { toggle: "dropdown", boundary: "window" }, aria: { haspopup: true, expanded: false } %> + +
+ <% end %> + + diff --git a/app/views/admin/accounts/_video.html.erb b/app/views/admin/accounts/_video.html.erb new file mode 100644 index 0000000..c88a381 --- /dev/null +++ b/app/views/admin/accounts/_video.html.erb @@ -0,0 +1,13 @@ + + <%= video.project.name %> + <%= video.name %> + +
+ <%= button_tag "Manage", class: "btn btn-light btn-sm dropdown-toggle border", data: { toggle: "dropdown", boundary: "window" }, aria: { haspopup: true, expanded: false } %> + +
+ + diff --git a/app/views/admin/accounts/edit.html.erb b/app/views/admin/accounts/edit.html.erb new file mode 100644 index 0000000..e6252d0 --- /dev/null +++ b/app/views/admin/accounts/edit.html.erb @@ -0,0 +1,6 @@ +
+ <%= card_header text: "Edit Account", close_action_path: [:admin, :accounts] %> +
+ <%= render "form", account: @account %> +
+
diff --git a/app/views/admin/accounts/index.html.erb b/app/views/admin/accounts/index.html.erb new file mode 100644 index 0000000..02de509 --- /dev/null +++ b/app/views/admin/accounts/index.html.erb @@ -0,0 +1,39 @@ +
+ <%= bootstrap_form_with url: admin_accounts_path, method: :get, remote: true, layout: :inline, id: "search" do |form| %> + <%= form.search_field :query, hide_label: true, placeholder: t(".actions.search"), class: "rounded-pill-right", value: params[:query], prepend: form.button(fa_icon("search"), id: "search-button", class: "btn btn-light border rounded-pill-left") %> + <% end%> + <% if policy(Account).new? %> + <%= link_to fa_icon("plus", text: "New Account"), [:new, :admin, :account], class: "btn btn-primary mb-3" %> + <% end %> +
+ + +
+ + + + + + + + + + + + + + + <% if @accounts.any? %> + <%= render @accounts %> + <% else %> + + + + <% end %> + +
NamePlan# ProjectsMonthly Video Upload MinutesTotal Video Upload MinutesTotal StorageCreated At
<%= t(".empty") %>
+
+ +
+ <%= will_paginate @accounts %> +
diff --git a/app/views/admin/accounts/index.js.erb b/app/views/admin/accounts/index.js.erb new file mode 100644 index 0000000..46dee9f --- /dev/null +++ b/app/views/admin/accounts/index.js.erb @@ -0,0 +1,3 @@ +$("#accounts").html("<%= j render @accounts %>"); +$("form input[type='search']").val("<%= params[:query] %>"); +$("#accounts_pagination").html("<%= j will_paginate @accounts %>"); diff --git a/app/views/admin/accounts/new.html.erb b/app/views/admin/accounts/new.html.erb new file mode 100644 index 0000000..f53359d --- /dev/null +++ b/app/views/admin/accounts/new.html.erb @@ -0,0 +1,6 @@ +
+ <%= card_header text: "New Account", close_action_path: [:admin, :accounts] %> +
+ <%= render "form", account: @account %> +
+
diff --git a/app/views/admin/accounts/show.html.erb b/app/views/admin/accounts/show.html.erb new file mode 100644 index 0000000..dfc6ca1 --- /dev/null +++ b/app/views/admin/accounts/show.html.erb @@ -0,0 +1,38 @@ +
+ <%= card_header text: @account.name, close_action_path: [:admin, :accounts] %> +
+ <%= card_field_set_tag "Account Details" do %> +
+
Plan
+
<%= @account.plan_name %>
+
Users
+
<%= @account.users.size %>
+
Created at
+
<%= time_ago_in_words(@account.created_at) %> ago
+
+ <% end %> + + <%= card_field_set_tag "Videos" do %> + <%= bootstrap_form_with url: [:admin, @account], method: :get, remote: true, layout: :inline, id: "search" do |form| %> + <%= form.search_field :query, hide_label: true, placeholder: t("shared.search"), class: "rounded-pill-right", value: params[:query], prepend: form.button(fa_icon("search"), id: "search-button", class: "btn btn-light border rounded-pill-left") %> + <% end%> +
+ + + + + + + + + + <%= render partial: "admin/accounts/video", collection: @videos %> + +
ProjectName
+
+
+ <%= will_paginate @videos %> +
+ <% end %> +
+
diff --git a/app/views/admin/accounts/show.js.erb b/app/views/admin/accounts/show.js.erb new file mode 100644 index 0000000..fb51193 --- /dev/null +++ b/app/views/admin/accounts/show.js.erb @@ -0,0 +1,2 @@ +$("#videos").html("<%= j render partial: "admin/accounts/video", collection: @videos %>"); +$("#videos_pagination").html("<%= j will_paginate @videos %>"); \ No newline at end of file diff --git a/app/views/admin/application/_header.html.erb b/app/views/admin/application/_header.html.erb new file mode 100644 index 0000000..282eb99 --- /dev/null +++ b/app/views/admin/application/_header.html.erb @@ -0,0 +1,36 @@ +
+
+
+ <%= link_to signed_in_root_path, class: "navbar-brand" do %> + <%= suite_wordmark %> + <% end %> + <%= link_to fa_icon("arrow-circle-left", text: "Back To Account"), :projects, class: "text-reset text-decoration-none" %> +
+
+

Admin

+
+
+
+ <%= link_to :profile, class: "text-decoration-none text-reset dropdown-toggle", data: { toggle: "dropdown" } do %> + <%= get_name_or_email(Current.user) %> + <%= image_or_text_avatar(Current.user) %> + <% end %> + + +
+
+
+
diff --git a/app/views/admin/application/_side_nav.html.erb b/app/views/admin/application/_side_nav.html.erb new file mode 100644 index 0000000..00ea144 --- /dev/null +++ b/app/views/admin/application/_side_nav.html.erb @@ -0,0 +1,20 @@ + diff --git a/app/views/admin/users/_form.html.erb b/app/views/admin/users/_form.html.erb new file mode 100644 index 0000000..b96fdfb --- /dev/null +++ b/app/views/admin/users/_form.html.erb @@ -0,0 +1,24 @@ +<%= errors_summary_for user %> +<%= bootstrap_form_with model: [:admin, user], local: true, html: { autocorrect: :off, autocapitalize: :none, autocomplete: :off, spellcheck: false } do |form| %> + <%= form.email_field :email %> + + <%# autocomplete: 'new-password' prevent the existing password value from showing up %> + <%= form.password_field :password, autocomplete: "new-password" %> + + <% if user.new_record? %> + <%= form.collection_select :account_id, accounts, :id, :name, { prompt: "Select an Account", required: true }, class: "form-control custom-select" %> + <%= form.select :role, options_for_role_select, {}, class: "form-control custom-select" %> + <% end %> + +
+ <%= form.check_box :admin, label: "Admin User" %> +
<%= fa_icon :warning, text: "This option grants access to the Admin dashboard. This should only be for trusted BiG employees." %>
+
+ +
+ <%= link_to t("shared.cancel"), [:admin, :users], class: "col-3 text-reset" %> +
+ <%= form.button class: class_string("btn btn-block", ["btn-success", "btn-primary"] => user.new_record?), data: { disable_with: t("shared.disable_with") } %> +
+
+<% end %> diff --git a/app/views/admin/users/_user.html.erb b/app/views/admin/users/_user.html.erb new file mode 100644 index 0000000..30583d0 --- /dev/null +++ b/app/views/admin/users/_user.html.erb @@ -0,0 +1,32 @@ + + + <%= user.email %> + + + <%= user.first_name %> + + + <%= user.last_name %> + + + <%= user.account_auths.map(&:role).compact.join(",") %> + + + <%= user.accounts.map(&:name).join(",") %> + + + <%= time_ago_in_words(user.created_at) %> ago + + + <% if user != Current.user %> +
+ <%= button_tag "Manage", class: "btn btn-light btn-sm dropdown-toggle border", data: { toggle: "dropdown", boundary: "window" }, aria: { haspopup: true, expanded: false } %> + +
+ <% end %> + + diff --git a/app/views/admin/users/edit.html.erb b/app/views/admin/users/edit.html.erb new file mode 100644 index 0000000..1044f88 --- /dev/null +++ b/app/views/admin/users/edit.html.erb @@ -0,0 +1,6 @@ +
+ <%= card_header text: "Edit User", close_action_path: [:admin, :users] %> +
+ <%= render "form", user: @user, accounts: @accounts %> +
+
diff --git a/app/views/admin/users/index.html.erb b/app/views/admin/users/index.html.erb new file mode 100644 index 0000000..f2ba72b --- /dev/null +++ b/app/views/admin/users/index.html.erb @@ -0,0 +1,34 @@ +
+ <% if policy(User).new? %> + <%= link_to fa_icon("plus", text: t(".actions.new")), [:new, :admin, :user], class: "btn btn-primary mb-3" %> + <% end %> + + <%= bootstrap_form_with url: admin_users_path, method: :get, remote: true, layout: :inline, id: "search" do |form| %> + <%= form.search_field :query, hide_label: true, placeholder: t(".actions.search"), class: "rounded-pill-right", value: params[:query], prepend: form.button(fa_icon("search"), class: "btn btn-light border rounded-pill-left") %> + <% end %> +
+ +
+ + + + + + + + + + + + + + <% if @users.any? %> + <%= render @users %> + <% else %> + + + + <% end %> + +
EmailFirst NameLast NameRoleAccount NameCreated At
<%= t(".empty") %>
+
diff --git a/app/views/admin/users/index.js.erb b/app/views/admin/users/index.js.erb new file mode 100644 index 0000000..a7a3767 --- /dev/null +++ b/app/views/admin/users/index.js.erb @@ -0,0 +1,2 @@ +$("#users").html("<%= j render(@users) %>"); +$("form input[type='search']").val("<%= params[:query] %>"); \ No newline at end of file diff --git a/app/views/admin/users/new.html.erb b/app/views/admin/users/new.html.erb new file mode 100644 index 0000000..4c2d522 --- /dev/null +++ b/app/views/admin/users/new.html.erb @@ -0,0 +1,6 @@ +
+ <%= card_header text: "New User", close_action_path: [:admin, :users] %> +
+ <%= render "form", user: @user, accounts: @accounts %> +
+
diff --git a/app/views/admin_mailer/new_video.html.erb b/app/views/admin_mailer/new_video.html.erb new file mode 100644 index 0000000..d8eb206 --- /dev/null +++ b/app/views/admin_mailer/new_video.html.erb @@ -0,0 +1,12 @@ +

Hi BiG Team,

+ +

A new video titled "<%= @video.name || @video.file.filename %>" has been uploaded to <%= @video.project.account.name %>'s "<%= @video.project.name %>" project.

+ +

+<%= link_to "Click here", [@video, :video_analyses, locale: I18n.locale, host: AppHost.new.domain_with_port] %> +to begin analyzing this video. +

+ +

+- BiG Notifier +

diff --git a/app/views/admin_mailer/new_video.text.erb b/app/views/admin_mailer/new_video.text.erb new file mode 100644 index 0000000..1e258b3 --- /dev/null +++ b/app/views/admin_mailer/new_video.text.erb @@ -0,0 +1,8 @@ +Hi BiG Team, + +A new video titled "<%= @video.name || @video.file.filename %>" has been uploaded to <%= @video.project.account.name %>'s "<%= @video.project.name %>" project. + +Use the URL below to begin analyzing this video: +<%= url_for [@video, :video_analyses, locale: I18n.locale, host: AppHost.new.domain_with_port] %> + +- BiG Notifier diff --git a/app/views/admin_mailer/updated_video_edl_file.html.erb b/app/views/admin_mailer/updated_video_edl_file.html.erb new file mode 100644 index 0000000..889dcde --- /dev/null +++ b/app/views/admin_mailer/updated_video_edl_file.html.erb @@ -0,0 +1,12 @@ +

Hi BiG Team,

+ +

<%= @edl_type_updated %> file has been updated for video titled "<%= @video.name || @video.file.filename %>" for <%= @video.project.account.name %>'s "<%= @video.project.name %>" project.

+ +

+<%= link_to "Click here", [@video, :video_analyses, locale: I18n.locale, host: AppHost.new.domain_with_port] %> +to begin re-analyzing this video. +

+ +

+- BiG Notifier +

diff --git a/app/views/admin_mailer/updated_video_edl_file.text.erb b/app/views/admin_mailer/updated_video_edl_file.text.erb new file mode 100644 index 0000000..1e185c0 --- /dev/null +++ b/app/views/admin_mailer/updated_video_edl_file.text.erb @@ -0,0 +1,8 @@ +Hi BiG Team, + +<%= @edl_type_updated %> file has been updated for video titled "<%= @video.name || @video.file.filename %>" for <%= @video.project.account.name %>'s "<%= @video.project.name %>" project. + +Use the URL below to begin re-analyzing this video: +<%= url_for [@video, :video_analyses, locale: I18n.locale, host: AppHost.new.domain_with_port] %> + +- BiG Notifier diff --git a/app/views/appearance_releases/_appearance_release.html.erb b/app/views/appearance_releases/_appearance_release.html.erb new file mode 100644 index 0000000..9779356 --- /dev/null +++ b/app/views/appearance_releases/_appearance_release.html.erb @@ -0,0 +1,51 @@ + + <%= check_box_tag "appearance_release_ids[]", appearance_release.id, false %> + + <% if appearance_release.photo.attached? %> + <%= image_tag medium_variant(appearance_release.photo) %> + <% else %> + <%= fa_icon("warning", text: t(".no_photos"), class: "text-danger") %> + <% end %> + + + <%= appearance_release.name %> + + + <%= contact_info( + address: appearance_release.person_address, + phone: appearance_release.person_phone, + email: appearance_release.person_email + ) %> + + + <%= notes_preview appearance_release.notes.order_by_recent %> + + "> + <%= tags_preview appearance_release, appearance_release.tags %> + + + <%= appearance_release.signed_on %> + + +
+ <%= button_tag t(".actions.manage"), class: "btn btn-light btn-sm dropdown-toggle border", data: { toggle: "dropdown", boundary: "window" }, aria: { haspopup: true, expanded: false } %> + +
+ + diff --git a/app/views/appearance_releases/_form.html.erb b/app/views/appearance_releases/_form.html.erb new file mode 100644 index 0000000..0f9d150 --- /dev/null +++ b/app/views/appearance_releases/_form.html.erb @@ -0,0 +1,86 @@ +<%= errors_summary_for appearance_release %> +<%= bootstrap_form_with model: model, local: true, validation_context: :non_native do |form| %> + <%= field_set_tag content_tag(:span, t(".person_details.heading"), class: "h6 text-muted text-uppercase") do %> + <%= form.form_group :minor do %> + <%= form.check_box :minor, label: t("helpers.label.appearance_release.minor"), data: { target: "[data-ujs-target=guardian-fields]", toggle: "collapse" } %> + <% end %> + +
+ <%= form.text_field :person_first_name, required: true, wrapper_class: "col-sm-3" %> + <%= form.text_field :person_last_name, required: true, wrapper_class: "col-sm-3" %> + <%= form.phone_field :person_phone, wrapper_class: "col-sm-6" %> +
+
+ <%= form.email_field :person_email, wrapper_class: "col-sm-6" %> + <%= form.date_field :person_date_of_birth, wrapper_class: "col-sm-6", placeholder: Date.current %> + <%= form.text_field :person_address, wrapper_class: "col-sm-6" %> +
+ +
!appearance_release.minor?) %>" data-ujs-target="guardian-fields"> +
+ <%= form.text_field :guardian_first_name, required: appearance_release.minor?, wrapper_class: "col-sm-3" %> + <%= form.text_field :guardian_last_name, required: appearance_release.minor?, wrapper_class: "col-sm-3" %> + <%= form.phone_field :guardian_phone, wrapper_class: "col-sm-6" %> +
+
+ <%= form.text_field :guardian_address, wrapper_class: "col-sm-6" %> +
+
+ <% end %> + +
+ + <%= field_set_tag content_tag(:span, t(".contract_and_rights.heading"), class: "h6 text-muted text-uppercase") do %> + <%= render "shared/contract_fields", form: form, release: appearance_release %> + <%= render "shared/exploitable_rights_fields", form: form %> + <% end %> + +
+ + <%= field_set_tag content_tag(:span, t(".photos.heading"), class: "h6 text-muted text-uppercase") do %> +
+

!appearance_release.minor?) %>" data-ujs-target="guardian-fields"><%= t(".photos.person_photo.heading") %>

+
+
+ No photo yet +
+
+ <% if appearance_release.person_photo.attached? %> + <%= javascript_tag nonce: true do %> + App.PhotoPreview.set("[data-behavior=person-photo-preview]", "<%= url_for(appearance_release.person_photo.variant(auto_orient: true, resize: '200x200')) %>"); + <% end %> + <% end %> +
+ <%= form.hidden_field :person_photo, value: form.object.person_photo.signed_id if appearance_release.person_photo.attached?%> + <%= form.file_field :person_photo, hide_label: true, data: { ujs_target: "person-photo-input" }, help: "PNG or JPG only", accept: appearance_release.class.face_photo_acceptable_content_types.join(",") %> +
+
+ +
!appearance_release.minor?) %>" data-ujs-target="guardian-fields"> +
+

<%= t(".photos.guardian_photo.heading") %>

+
+
+ No photo yet +
+
+ <% if appearance_release.guardian_photo.attached? %> + <%= javascript_tag nonce: true do %> + App.PhotoPreview.set("[data-behavior=guardian-photo-preview]", "<%= url_for(appearance_release.guardian_photo.variant(auto_orient: true, resize: '200x200')) %>"); + <% end %> + <% end %> +
+ <%= form.hidden_field :guardian_photo, value: form.object.guardian_photo.signed_id if appearance_release.guardian_photo.attached?%> + <%= form.file_field :guardian_photo, hide_label: true, data: { ujs_target: "guardian-photo-input" }, help: "PNG or JPG only", accept: appearance_release.class.face_photo_acceptable_content_types.join(",") %> +
+
+
+ <% end %> + +
+ <%= link_to t("shared.cancel"), [appearance_release.project, :appearance_releases], class: "col-3 text-reset" %> +
+ <%= form.button id: "submit_release", class: class_string("btn btn-block", ["btn-success", "btn-primary"] => appearance_release.new_record?), data: { disable_with: t("shared.disable_with") } %> +
+
+<% end %> diff --git a/app/views/appearance_releases/_type_filter_actions.html.erb b/app/views/appearance_releases/_type_filter_actions.html.erb new file mode 100644 index 0000000..f63397d --- /dev/null +++ b/app/views/appearance_releases/_type_filter_actions.html.erb @@ -0,0 +1,11 @@ + diff --git a/app/views/appearance_releases/edit.html.erb b/app/views/appearance_releases/edit.html.erb new file mode 100644 index 0000000..1aa446f --- /dev/null +++ b/app/views/appearance_releases/edit.html.erb @@ -0,0 +1,6 @@ +
+ <%= card_header text: t(".heading"), close_action_path: [@project, :appearance_releases] %> +
+ <%= render "form", model: @appearance_release, appearance_release: @appearance_release %> +
+
diff --git a/app/views/appearance_releases/index.html.erb b/app/views/appearance_releases/index.html.erb new file mode 100644 index 0000000..f496ca2 --- /dev/null +++ b/app/views/appearance_releases/index.html.erb @@ -0,0 +1,70 @@ +
+
+
+
+ <% if policy(AppearanceRelease).new? %> +
+ <%= bootstrap_form_with url: project_appearance_release_imports_path, method: :post, id: "appearance_releases_import", remote: true, layout: :inline do |form| %> + <%= form.file_field :attachments, disable: true, direct_upload: true, multiple: true, accept: AppearanceRelease.acceptable_import_file_extensions.join(','), id: "appearance_release_file_upload", hide_label: true, hidden: true %> + <%= form.button fa_icon('plus', text: t('.actions.new')), class: 'btn btn-primary mr-2 mb-2', type: :button, id: 'import_appearance_releases' %> + <% end %> +
+ <% end %> + + <% if @appearance_releases.any? && policy(AppearanceRelease).tag_multiple? %> + <%= button_to_bulk_tagging(@project) %> + <% end %> + + <% if @appearance_releases.any? && policy(AppearanceRelease).download_multiple? %> + <%= link_to "Download All", [@project, :contract_downloads, release_type: @appearance_releases.name], method: :post, remote: true, class: "btn btn-light border mr-2 mb-2", data: { disable_with: "Please wait..." } %> + <% end %> +
+
+
+ +
+
+
+
+ <%= render 'type_filter_actions' %> +
+ +
+ <%= bootstrap_form_with url: [@project, :appearance_releases], method: :get, remote: true, layout: :inline, id: "search" do |form| %> + <%= form.hidden_field :type_filter, value: params[:type_filter] || 'all', id: 'type_filter_value' %> + <%= form.search_field :query, hide_label: true, placeholder: t(".actions.search"), class: "rounded-pill-right", value: params[:query], prepend: form.button(fa_icon("search"), class: "btn btn-light border mb-2 rounded-pill-left") %> + <% end %> +
+
+
+
+ +
+ + + + + + + + + + + + + + + <% if @appearance_releases.any? %> + <%= render @appearance_releases %> + <% else %> + + + + <% end %> + +
<%= check_box_tag "appearance_release_ids[]", false, false %><%= AppearanceRelease.human_attribute_name(:person_name) %><%= AppearanceRelease.human_attribute_name(:contact_info) %><%= t(".table_headers.notes") %><%= t(".table_headers.tags") %><%= t(".table_headers.signed_at") %>
<%= t(".empty") %>
+
+ +
+ <%= will_paginate @appearance_releases %> +
diff --git a/app/views/appearance_releases/index.js.erb b/app/views/appearance_releases/index.js.erb new file mode 100644 index 0000000..ebc1c7c --- /dev/null +++ b/app/views/appearance_releases/index.js.erb @@ -0,0 +1,5 @@ +$("#appearance_releases").html("<%= j render(@appearance_releases) %>"); +$("form input[type='search']").val("<%= params[:query] %>"); +$("#type_filter_actions").html("<%= j render 'type_filter_actions' %>"); +$("#appearance_releases_pagination").html("<%= j will_paginate(@appearance_releases) %>"); +$('#type_filter_value').val("<%= params[:type_filter] %>"); diff --git a/app/views/appearance_releases/new.html.erb b/app/views/appearance_releases/new.html.erb new file mode 100644 index 0000000..6bc6aa8 --- /dev/null +++ b/app/views/appearance_releases/new.html.erb @@ -0,0 +1,6 @@ +
+ <%= card_header text: t(".heading"), close_action_path: [@project, :appearance_releases] %> +
+ <%= render "form", model: [@project, @appearance_release], appearance_release: @appearance_release %> +
+
diff --git a/app/views/application/_breadcrumbs.html.erb b/app/views/application/_breadcrumbs.html.erb new file mode 100644 index 0000000..a0f22cc --- /dev/null +++ b/app/views/application/_breadcrumbs.html.erb @@ -0,0 +1,11 @@ +<% if breadcrumbs? %> + +<% end %> diff --git a/app/views/application/_flash.html.erb b/app/views/application/_flash.html.erb new file mode 100644 index 0000000..0da14bc --- /dev/null +++ b/app/views/application/_flash.html.erb @@ -0,0 +1,5 @@ +<% if flash.alert.present? %> +
<%= flash.alert.html_safe %>
+<% elsif flash.notice.present? %> +
<%= flash.notice.html_safe %>
+<% end %> diff --git a/app/views/application/_header.html.erb b/app/views/application/_header.html.erb new file mode 100644 index 0000000..f82a665 --- /dev/null +++ b/app/views/application/_header.html.erb @@ -0,0 +1,58 @@ +
+
+
+ <%= link_to signed_in_root_path, class: "navbar-brand" do %> + <%= suite_wordmark %> + <% end %> +
+
+

+ <% if signed_in? %> + <% if Current.user.accounts.size > 1 %> + + <% else %> + <%= Current.account.name if Current.account %> + <% end %> + <% else %> + <%= yield :page_title %> + <% end %> +

+
+
+ <% if signed_in? %> +
+ <%= link_to :profile, class: "text-decoration-none text-reset dropdown-toggle", data: { toggle: "dropdown" } do %> + <%= get_name_or_email(Current.user) %> + <%= image_or_text_avatar(Current.user) %> + <% end %> + + +
+ <% elsif params[:controller] != "sessions" %> + Already Have a ME Suite Account? + <%= link_to "Sign In", [:new, :session], class: "btn btn-sm btn-light border" %> + <% end %> +
+
+
diff --git a/app/views/application/_masquerade.html.erb b/app/views/application/_masquerade.html.erb new file mode 100644 index 0000000..4c5ca5d --- /dev/null +++ b/app/views/application/_masquerade.html.erb @@ -0,0 +1,3 @@ +
+ <%= link_to fa_icon("user-secret", text: "Stop Masquerading"), admin_masquerade_path, method: :delete %> +
diff --git a/app/views/application/_sidebar.html.erb b/app/views/application/_sidebar.html.erb new file mode 100644 index 0000000..a78879e --- /dev/null +++ b/app/views/application/_sidebar.html.erb @@ -0,0 +1,50 @@ + diff --git a/app/views/blank_contracts/_logo.html.erb b/app/views/blank_contracts/_logo.html.erb new file mode 100644 index 0000000..20f2b7f --- /dev/null +++ b/app/views/blank_contracts/_logo.html.erb @@ -0,0 +1 @@ +<%= image_tag logo.variant(auto_orient: true, resize: "100x100") %>
\ No newline at end of file diff --git a/app/views/blank_contracts/_project_info.html.erb b/app/views/blank_contracts/_project_info.html.erb new file mode 100644 index 0000000..0367bf6 --- /dev/null +++ b/app/views/blank_contracts/_project_info.html.erb @@ -0,0 +1,9 @@ +

<%= t ".heading" %>

+
+ <%= description_list_pair_for project, :producer_name, append: ":" %> + <%= description_list_pair_for project, :producer_address, append: ":" %> + <%= description_list_pair_for project, :name, append: ":" %> + <%= description_list_pair_for project, :client_name, append: ":" %> + <%= description_list_pair_for project, :description, append: ":" %> + <%= description_list_pair_for project, :details, append: ":" %> +
diff --git a/app/views/blank_contracts/_signature_page.html.erb b/app/views/blank_contracts/_signature_page.html.erb new file mode 100644 index 0000000..4ebdac6 --- /dev/null +++ b/app/views/blank_contracts/_signature_page.html.erb @@ -0,0 +1,39 @@ +

<%= t ".heading" %>

+

<%= t ".instructions", releasable_name: "#{releasable.model_name.name.titleize}" %>

+ +<% # Signer information %> +
+ <% # Only guardian signs if talent is a minor %> + <% if contract_template.guardian_clause.blank? %> + <%= description_list_pair "Signature:", "___________________________________________" %> + <% end %> + <%= description_list_pair "Name:", "___________________________________________" %> + <%= description_list_pair "Contact Address:", "___________________________________________" %> + <%= description_list_pair "Contact Phone:", "___________________________________________" %> + <%= description_list_pair "Contact Email:", "___________________________________________" %> + <% if releasable.model_name == "AppearanceRelease" %> + <%= description_list_pair "Person Date of Birth:", "___________________________________________" %> + <% end %> + <%= description_list_pair "Signed On:", "___________________________________________" %> + <% if releasable.model_name == "LocationRelease" %> + <%= description_list_pair "Filming Started On:", "___________________________________________" %> + <%= description_list_pair "Filming Ended On:", "___________________________________________" %> + <% end %> + <% if contract_template.fee? %> + <%= description_list_pair "Fee:", number_to_currency(contract_template.fee) %> + <% end %> +
+ +<% if contract_template.guardian_clause.present? %> + <% # Guardian information %> +
+

Guardian Information

+
+ <%= description_list_pair "Signature:", "___________________________________________" %> + <%= description_list_pair "Guardian Name:", "___________________________________________" %> + <%= description_list_pair "Guardian Address:", "___________________________________________" %> + <%= description_list_pair "Guardian Phone:", "___________________________________________" %> + <%= description_list_pair "Signed On:", "___________________________________________" %> +
+ +<% end %> diff --git a/app/views/blank_contracts/new.html.erb b/app/views/blank_contracts/new.html.erb new file mode 100644 index 0000000..7eb1e7a --- /dev/null +++ b/app/views/blank_contracts/new.html.erb @@ -0,0 +1,8 @@ +<%= bootstrap_form_with url: contract_template_blank_contracts_path, local: true, layout: :inline do |form| %> + <%= form.number_field :number_of_copies, value: 1 %> + <%= form.submit t 'shared.print' %> +<% end %> + +
+

<%= t ".preview_heading" %>

+ diff --git a/app/views/blank_contracts/pdf.html.erb b/app/views/blank_contracts/pdf.html.erb new file mode 100644 index 0000000..e70dbc3 --- /dev/null +++ b/app/views/blank_contracts/pdf.html.erb @@ -0,0 +1,39 @@ +<% copies.times do |copy_index| %> +
+ <% has_logo = local_assigns[:logo] %> + + + + + + + + + + + + + +
+ <% if has_logo %> + + <% end %> + + +
 <%= serial_numbers[copy_index] %>
 <%= t '.do_not_copy_warning' %>
+
+ <% if contract_template.body.present? %> + <%= contract_template.body %> +
+ <% end %> + <% if contract_template.guardian_clause.present? %> +

Guardian Clause

+ <%= contract_template.guardian_clause %> + <% end %> +
+
+ <%= render "blank_contracts/signature_page", releasable: releasable, contract_template: contract_template %> +
+<% end %> \ No newline at end of file diff --git a/app/views/bookmarks/_bookmark.html.erb b/app/views/bookmarks/_bookmark.html.erb new file mode 100644 index 0000000..eb8a447 --- /dev/null +++ b/app/views/bookmarks/_bookmark.html.erb @@ -0,0 +1,26 @@ + + + <%= bookmark.category %> + + + <%= bookmark.notes %> + + + + <%= bookmark.appears_at %> + + + + + + diff --git a/app/views/bookmarks/_edit_bookmark_modal.html.erb b/app/views/bookmarks/_edit_bookmark_modal.html.erb new file mode 100644 index 0000000..56dcae1 --- /dev/null +++ b/app/views/bookmarks/_edit_bookmark_modal.html.erb @@ -0,0 +1,13 @@ +<%= content_tag :div, class: "modal modal-right", id: "edit_bookmark_modal", aria: { labelledby: "modalLabel", hidden: true }, role: "dialog", tabindex: -1 do %> + +<% end %> diff --git a/app/views/bookmarks/_empty_bookmarks.html.erb b/app/views/bookmarks/_empty_bookmarks.html.erb new file mode 100644 index 0000000..bef40f3 --- /dev/null +++ b/app/views/bookmarks/_empty_bookmarks.html.erb @@ -0,0 +1,3 @@ + + <%= t(".empty") %> + \ No newline at end of file diff --git a/app/views/bookmarks/_form.html.erb b/app/views/bookmarks/_form.html.erb new file mode 100644 index 0000000..ce6c5df --- /dev/null +++ b/app/views/bookmarks/_form.html.erb @@ -0,0 +1,18 @@ +<%= bootstrap_form_with model: model, layout: :horizontal, label_col: "col-3", control_col: "col-9" do |form| %> + + +<% end %> \ No newline at end of file diff --git a/app/views/bookmarks/_new_bookmark_modal.html.erb b/app/views/bookmarks/_new_bookmark_modal.html.erb new file mode 100644 index 0000000..ba319bb --- /dev/null +++ b/app/views/bookmarks/_new_bookmark_modal.html.erb @@ -0,0 +1,13 @@ +<%= content_tag :div, class: "modal modal-right", id: "new_bookmark_modal", aria: { labelledby: "modalLabel", hidden: true }, role: "dialog", tabindex: -1 do %> + +<% end %> diff --git a/app/views/bookmarks/create.js.erb b/app/views/bookmarks/create.js.erb new file mode 100644 index 0000000..582c35b --- /dev/null +++ b/app/views/bookmarks/create.js.erb @@ -0,0 +1,7 @@ +<% # Add a marker to the timeline %> +$(document).trigger("addBookmark-<%= @video.id %>", [<%= raw @bookmark.to_json %>]); + +<% # Close the bookmark modal %> +$("#new_bookmark_modal").modal("toggle"); + +$("#video_bookmarks").html("<%= j render @bookmarks %>"); diff --git a/app/views/bookmarks/destroy.js.erb b/app/views/bookmarks/destroy.js.erb new file mode 100644 index 0000000..b1606d3 --- /dev/null +++ b/app/views/bookmarks/destroy.js.erb @@ -0,0 +1,5 @@ +$("#video_bookmarks tr#<%= dom_id(@bookmark) %>").remove(); + +<% unless @bookmarks.any? %> + $("#video_bookmarks").html("<%= j render(partial: 'empty_bookmarks') %>"); +<% end %> \ No newline at end of file diff --git a/app/views/bookmarks/edit.js.erb b/app/views/bookmarks/edit.js.erb new file mode 100644 index 0000000..4d49e42 --- /dev/null +++ b/app/views/bookmarks/edit.js.erb @@ -0,0 +1,9 @@ +$("[data-toggle=tooltip]").tooltip("dispose"); +<% # Remove the modal if it already exists %> + +$("#edit_bookmark_modal").remove(); +$("#new_bookmark_modal").remove(); + +<% # Create and show the modal %> +$("body").append("<%= j render('edit_bookmark_modal', bookmark: @bookmark) %>"); +$("#edit_bookmark_modal").modal("toggle"); \ No newline at end of file diff --git a/app/views/bookmarks/new.js.erb b/app/views/bookmarks/new.js.erb new file mode 100644 index 0000000..f689f05 --- /dev/null +++ b/app/views/bookmarks/new.js.erb @@ -0,0 +1,8 @@ +$("[data-toggle=tooltip]").tooltip("dispose"); +<% # Remove the modal if it already exists %> +$("#new_bookmark_modal").remove(); +$("#edit_bookmark_modal").remove(); + +<% # Create and show the modal %> +$("body").append("<%= j render('new_bookmark_modal', bookmark: @bookmark) %>"); +$("#new_bookmark_modal").modal("toggle"); diff --git a/app/views/bookmarks/update.js.erb b/app/views/bookmarks/update.js.erb new file mode 100644 index 0000000..c836cd7 --- /dev/null +++ b/app/views/bookmarks/update.js.erb @@ -0,0 +1,8 @@ +<% # Remove the modal if it already exists %> +$("#edit_bookmark_modal").toggle(); +$(".modal-backdrop").remove(); +<% # Create and show the modal %> +$("#video_bookmarks").html("<%= j render @bookmarks %>"); + +<%# Replacing html removes the scroll bar %> +$("body").css({"overflow": "visible"}); diff --git a/app/views/broadcasts/_broadcast.html.erb b/app/views/broadcasts/_broadcast.html.erb new file mode 100644 index 0000000..e579a24 --- /dev/null +++ b/app/views/broadcasts/_broadcast.html.erb @@ -0,0 +1,27 @@ + + <%= check_box_tag "broadcast_ids[]", broadcast.id, false %> + + <%= broadcast.name %> + + + <%= broadcast.status.titleize %> + + + <%= time_ago_in_words(broadcast.created_at) %> ago + + +
+ <%= button_tag t(".actions.manage"), class: "btn btn-light btn-sm dropdown-toggle border", data: { toggle: "dropdown", boundary: "window" }, aria: { haspopup: true, expanded: false } %> + +
+ + diff --git a/app/views/broadcasts/_broadcast_recording_nav.html.erb b/app/views/broadcasts/_broadcast_recording_nav.html.erb new file mode 100644 index 0000000..5ef7ca0 --- /dev/null +++ b/app/views/broadcasts/_broadcast_recording_nav.html.erb @@ -0,0 +1,2 @@ +<%= link_to broadcast_recording.download_file_name, "javascript:void(0);", class: "dropdown-item", data: { behavior: "play_recording", playback_url: broadcast_recording.playback_url } %> + diff --git a/app/views/broadcasts/_broadcast_recordings.html.erb b/app/views/broadcasts/_broadcast_recordings.html.erb new file mode 100644 index 0000000..2dd23fb --- /dev/null +++ b/app/views/broadcasts/_broadcast_recordings.html.erb @@ -0,0 +1,13 @@ +<% if recordings.present? %> +

Click below to download the recordings of the live stream.

+ +
+ <%= will_paginate(recordings, params: {controller: "broadcasts", action: "show", project_id: broadcast.project_id, id: broadcast.id, page: params[:page], active_tab: 'recordings'}) %> +
+<% else %> +

Recording of the live stream will appear here.

+<% end %> diff --git a/app/views/broadcasts/_broadcast_status.html.erb b/app/views/broadcasts/_broadcast_status.html.erb new file mode 100644 index 0000000..2c94608 --- /dev/null +++ b/app/views/broadcasts/_broadcast_status.html.erb @@ -0,0 +1,21 @@ +<% if broadcast.streamer_connected? || (broadcast.streamer_recording? && !broadcast.active?) %> +
+ <%= fa_icon "info-circle" %> + Live stream has connected successfully and will be available soon. +
+<% elsif broadcast.streamer_recording? && broadcast.active? %> +
+ <%= fa_icon "success" %> + Live stream has begun, click play to watch it. +
+<% elsif broadcast.streamer_disconnected? %> +
+ <%= fa_icon "warning" %> + Live stream got disconnected. +
+<% elsif (broadcast.idle? && broadcast.streamer_idle?) || (broadcast.created? && broadcast.streamer_idle?) %> +
+ <%= fa_icon "info-circle" %> + Live stream is waiting to begin. +
+<% end %> \ No newline at end of file diff --git a/app/views/broadcasts/_file.html.erb b/app/views/broadcasts/_file.html.erb new file mode 100644 index 0000000..b0c90a8 --- /dev/null +++ b/app/views/broadcasts/_file.html.erb @@ -0,0 +1,12 @@ +
  • + <% if file.variable? %> + <%= link_to image_tag(file.variant(resize_and_pad: [300, 300, background: "#F7F8F9"]), class: "bg-light img-thumbnail img-fluid"), file, target: "_blank" %> + <% else %> +
    + <%= link_to file, target: "_blank" do %> + <%= fa_icon("file", style: "font-size: 2rem") %> +
    <%= file.filename %>
    + <% end %> +
    + <% end %> +
  • diff --git a/app/views/broadcasts/_file_form.html.erb b/app/views/broadcasts/_file_form.html.erb new file mode 100644 index 0000000..21a333d --- /dev/null +++ b/app/views/broadcasts/_file_form.html.erb @@ -0,0 +1,4 @@ +<%= bootstrap_form_for model, layout: :inline, remote: true do |form| %> + <%= form.file_field :files, direct_upload: true, multiple: true, accept: "*", hide_label: true, wrapper_class: "w-65 mr-2" %> + <%= form.button fa_icon("upload", text: "Add File"), class: "btn btn-primary", type: :submit, data: { disable_with: fa_icon("spinner", text: "Adding File") } %> +<% end %> diff --git a/app/views/broadcasts/_form.html.erb b/app/views/broadcasts/_form.html.erb new file mode 100644 index 0000000..71dea53 --- /dev/null +++ b/app/views/broadcasts/_form.html.erb @@ -0,0 +1,12 @@ +<%= errors_summary_for broadcast %> + +<%= bootstrap_form_with model: model, local: true do |form| %> + <%= form.text_field :name %> + +
    + <%= link_to t("shared.cancel"), [project, :broadcasts], class: "col-3 text-reset" %> +
    + <%= form.submit class: class_string("btn btn-block", ["btn-success", "btn-primary"] => broadcast.new_record?), data: { disable_with: t("shared.disable_with") } %> +
    +
    +<% end %> diff --git a/app/views/broadcasts/_video.html.erb b/app/views/broadcasts/_video.html.erb new file mode 100644 index 0000000..45fccca --- /dev/null +++ b/app/views/broadcasts/_video.html.erb @@ -0,0 +1,15 @@ +<% if broadcast.streamer_recording? && broadcast.active? %> +
    +<% else %> +
    + + + + + + +
    + Video player will appear here when the stream becomes available. +
    +
    +<% end %> \ No newline at end of file diff --git a/app/views/broadcasts/index.html.erb b/app/views/broadcasts/index.html.erb new file mode 100644 index 0000000..5e91f76 --- /dev/null +++ b/app/views/broadcasts/index.html.erb @@ -0,0 +1,44 @@ +<%= product_wordmark :direct_me, class: "small mb-3" %> + +
    +
    +
    + <% if policy(Broadcast).new? %> + <%= link_to fa_icon("plus", text: t(".actions.new")), [:new, @project, :broadcast], class: "btn btn-primary mb-2" %> + <% end %> + + <%= link_to "Multi-View", "javascript:void(0);", class: "btn btn-light border disabled ml-auto mr-2 mb-2", id: "multi_view_broadcasts" %> + + <%= bootstrap_form_with url: [@project, :broadcasts], method: :get, remote: true, layout: :inline, id: "search" do |form| %> + <%= form.search_field :query, hide_label: true, placeholder: t("shared.search"), value: params[:query], prepend: form.button(fa_icon("search"), class: "btn btn-light border mb-2") %> + <% end %> +
    +
    +
    + +
    + + + + + + + + + + + + <% if @broadcasts.any? %> + <%= render @broadcasts %> + <% else %> + + + + <% end %> + +
    <%= t(".table_headers.broadcast_name") %><%= t(".table_headers.broadcast_status") %><%= t(".table_headers.broadcast_created_at") %>
    <%= t(".empty") %>
    +
    + +
    + <%= will_paginate @broadcasts %> +
    diff --git a/app/views/broadcasts/index.js.erb b/app/views/broadcasts/index.js.erb new file mode 100644 index 0000000..c80ca45 --- /dev/null +++ b/app/views/broadcasts/index.js.erb @@ -0,0 +1,3 @@ +$("#broadcasts").html("<%= j render(@broadcasts) %>"); +$("form input[type='search']").val("<%= params[:query] %>"); +$("#broadcasts_pagination").html("<%= j will_paginate(@broadcasts) %>"); diff --git a/app/views/broadcasts/new.html.erb b/app/views/broadcasts/new.html.erb new file mode 100644 index 0000000..14335b4 --- /dev/null +++ b/app/views/broadcasts/new.html.erb @@ -0,0 +1,6 @@ +
    + <%= card_header text: t(".heading"), close_action_path: [@project, :broadcasts] %> +
    + <%= render "form", model: [@project, @broadcast], broadcast: @broadcast, project: @project %> +
    +
    diff --git a/app/views/broadcasts/show.html.erb b/app/views/broadcasts/show.html.erb new file mode 100644 index 0000000..561ce71 --- /dev/null +++ b/app/views/broadcasts/show.html.erb @@ -0,0 +1,153 @@ +<%= content_for :meta do %> + <% if @project %> + + <% end %> + <% if @broadcast %> + + <% end %> +<% end %> + +<% content_for :header do %> +
    +
    +
    + <%= product_wordmark(:direct_me, class: 'navbar-brand') %> +
    +
    +
    +<% end %> + +
    +
    +
    +
    +
    +

    <%= @broadcast.name %>

    + +
    +
    +
    +
    + <%= render partial: 'broadcasts/video', locals: { broadcast: @broadcast } %> + <% if @broadcast.streamer_recording? && @broadcast.active? %> + <%= javascript_tag nonce: true do %> + new Clappr.Player({ + parentId: '#broadcast_video', + source: "<%= @broadcast.stream_playback_url %>", + width: '100%', + height: '100%', + mute: true, + autoPlay: true, + hlsMinimumDvrSize: 1 + }); + <% end %> + <% end %> +
    +
    +
    +
    +
    +
    + +
    +
    +
    !params[:active_tab].present?) %>" id="home"> +
    + <%= render partial: 'broadcasts/broadcast_status', locals: { broadcast: @broadcast } %> +
    + <% unless controller.class.module_parent.to_s == "Public" %> +
    +
    + +
    + <% if @multi_view_broadcasts.present? %> + <% tokens = @multi_view_broadcasts.map(&:token) %> + +
    + +
    + <% else %> + +
    + +
    + <% end %> +
    +
    +
    + <% end %> +

    If you want to join the ZOOM meeting dedicated to this broadcast, follow the link below.

    + <%= link_to 'Video Conference', @conference_url, class: 'btn btn-primary btn-block', target: '_blank' %> +
    +
    params[:active_tab] == 'files') %>" id="files"> +
    + <% if controller.class.module_parent.to_s == "Public" %> + <%= render partial: "public/broadcasts/file_form", locals: { model: [@broadcast], token: @broadcast.token } %> + <% else %> + <%= render partial: "broadcasts/file_form", locals: { model: [@project, @broadcast] } %> + <% end %> +
    +
    +
      + <% if @files.present? %> + <%= render partial: "broadcasts/file", collection: @files %> + <% else %> +
    • + Files will appear here. +
    • + <% end %> +
    +
    + <%= will_paginate(@files, params: { active_tab: 'files' }) if @files.present? %> +
    +
    +
    +
    params[:active_tab] == 'recordings') %>" id="recordings"> +
    + <%= render partial: 'broadcasts/broadcast_recordings', locals: { recordings: @recordings, broadcast: @broadcast } %> +
    +
    +
    +
    +
    +
    +
    diff --git a/app/views/broadcasts/update.js.erb b/app/views/broadcasts/update.js.erb new file mode 100644 index 0000000..92d4bbb --- /dev/null +++ b/app/views/broadcasts/update.js.erb @@ -0,0 +1,9 @@ +$("#broadcast_file_form").html("<%= j render(partial: "broadcasts/file_form", locals: { model: [@project, @broadcast] }) %>"); + +var file_id = "#<%= dom_id(@files.first) %>" +if ($("#broadcast_file_list").has(file_id).length == 0) { + $("#broadcast_file_list").html("<%= j render(partial: "broadcasts/file", collection: @files) %>"); + $("#broadcast_files_pagination").html("<%= j will_paginate(@files) %>"); +} + +bsCustomFileInput.init(); \ No newline at end of file diff --git a/app/views/bulk_taggings/_new_bulk_tag_modal.html.erb b/app/views/bulk_taggings/_new_bulk_tag_modal.html.erb new file mode 100644 index 0000000..7ef1806 --- /dev/null +++ b/app/views/bulk_taggings/_new_bulk_tag_modal.html.erb @@ -0,0 +1,23 @@ + \ No newline at end of file diff --git a/app/views/bulk_taggings/create.js.erb b/app/views/bulk_taggings/create.js.erb new file mode 100644 index 0000000..8a7f3db --- /dev/null +++ b/app/views/bulk_taggings/create.js.erb @@ -0,0 +1,5 @@ +$("#tag_multiple_releases").modal("toggle"); +$("#<%= params[:releasable_name] %>").html("<%= j render(@all_releasables) %>"); +$("#<%= params[:releasable_name] %>_pagination").html("<%= j will_paginate(@all_releasables) %>"); +$("#tag_all").parent().replaceWith("<%= j button_to_bulk_tagging(@project) %>"); +$("[data-behavior=all-selectable]").prop("checked", false); \ No newline at end of file diff --git a/app/views/bulk_taggings/new.js.erb b/app/views/bulk_taggings/new.js.erb new file mode 100644 index 0000000..24f1d56 --- /dev/null +++ b/app/views/bulk_taggings/new.js.erb @@ -0,0 +1,11 @@ +var modalSelector = "#tag_multiple_releases" +var modal = "<%= j render("new_bulk_tag_modal", releasable_ids: @releasable_ids, releasable_name: @releasable_name, project_id: @project.id) %>" + +// If the modal already exists, replace it. Otherwise, create it +if ($(modalSelector).length > 0) { + $(modalSelector).replaceWith(modal) +} else { + $('body').append(modal) +} + +$(modalSelector).modal("toggle") \ No newline at end of file diff --git a/app/views/contract_downloads/_other_pending_downloads.html.erb b/app/views/contract_downloads/_other_pending_downloads.html.erb new file mode 100644 index 0000000..4ec98b9 --- /dev/null +++ b/app/views/contract_downloads/_other_pending_downloads.html.erb @@ -0,0 +1,14 @@ +

    Your <%= release_type.titleize %> contracts are being prepared for download. You will be notified when it's ready. +

    +

    The following downloads are also in progress:

    +
      + <% downloads.each do |download| %> + <% if download.release_type == "reports"%> +
    • <%= download.release_type.titleize %> (as of <%= time_ago_in_words(download.created_at) %> ago) +
    • + <% else %> +
    • <%= download.release_type.titleize %> contracts (as of <%= time_ago_in_words(download.created_at) %> ago) +
    • + <% end %> + <% end %> +
    diff --git a/app/views/contract_downloads/index.html.erb b/app/views/contract_downloads/index.html.erb new file mode 100644 index 0000000..b041784 --- /dev/null +++ b/app/views/contract_downloads/index.html.erb @@ -0,0 +1,32 @@ +
    + <%= bootstrap_form_with url: project_downloads_path, method: :get, remote: true, layout: :inline, id: "search" do |form| %> + <%= form.search_field :query, hide_label: true, placeholder: t(".actions.search"), class: "rounded-pill-right", value: params[:query], prepend: form.button(fa_icon("search"), id: "search-button", class: "btn btn-light border rounded-pill-left") %> + <% end %> +
    + +
    + + + + + + + + + + + + <% if @downloads.any? %> + <%= render @downloads %> + <% else %> + + + + <% end %> + +
    <%= t(".table_headers.download_name") %><%= t(".table_headers.download_type") %><%= t(".table_headers.download_status") %><%= t(".table_headers.download_created_at") %>
    <%= t(".empty") %>
    +
    + +
    + <%= will_paginate @downloads %> +
    diff --git a/app/views/contract_templates/_contract_template.html.erb b/app/views/contract_templates/_contract_template.html.erb new file mode 100644 index 0000000..c3813ee --- /dev/null +++ b/app/views/contract_templates/_contract_template.html.erb @@ -0,0 +1,36 @@ + + + <%= contract_template.name %> + + + <% if contract_template.fee? %> + <%= number_to_currency(contract_template.fee) %> + <% else %> + <%= t(".no_fee") %> + <% end %> + + + <%= release_type_title(contract_template.release_type) %> + + + <%= contract_template.releases.size %> + + +
    + <%= button_tag t(".actions.manage"), class: "btn btn-light btn-sm dropdown-toggle border", data: { toggle: "dropdown", boundary: "window" }, aria: { haspopup: true, expanded: false } %> + +
    + <%= link_to t(".actions.sign"), [:new, contract_template.project.account, contract_template.project, contract_template, "#{contract_template.release_type}_release"], class: "btn btn-sm btn-primary", target: :_blank %> + + diff --git a/app/views/contract_templates/_form.html.erb b/app/views/contract_templates/_form.html.erb new file mode 100644 index 0000000..0917df1 --- /dev/null +++ b/app/views/contract_templates/_form.html.erb @@ -0,0 +1,40 @@ +<%= bootstrap_form_with model: [project, contract_template], local: true do |form| %> + <%= field_set_tag content_tag(:span, t(".release_info.heading"), class: "h6 text-muted text-uppercase") do %> +
    + <%= form.text_field :name, wrapper_class: "col-sm-6" %> + <%= form.select :release_type, options_for_release_type_select(project, @release_type), { wrapper_class: "col-sm-6" }, data: { toggle: "collapse-select", target: "#guardian_clause", show_values: %w(appearance talent) }, class: "form-control custom-select" %> +
    +
    + <%= form.number_field :fee, min:"0", max:"99999999", step: "0.01", prepend: "$", help: "Leave at $0.00 for no-fee", wrapper_class: "col-sm-6" %> +
    + <% end %> + +
    + + <%= field_set_tag content_tag(:span, t(".exploitable_rights.heading"), class: "h6 text-muted text-uppercase")do %> + <%= render "shared/exploitable_rights_fields", form: form %> + <% end %> + +
    + + <%= field_set_tag content_tag(:span, t(".legal.heading"), class: "h6 text-muted text-uppercase") do %> + <%= form.form_group do %> + <%= form.rich_text_area :body %> + <% end %> +
    + <%= form.form_group do %> + <%= form.rich_text_area :guardian_clause %> + <% end %> +
    + <% end %> + +
    + <%= link_to t("shared.cancel"), [project, :contract_templates], class: "col-3 text-reset" %> +
    + <%= form.button "Preview", name: "commit", value: "preview", class: 'btn btn-info btn-block', formtarget: "_blank" %> +
    +
    + <%= form.submit class: "btn btn-success btn-block", data: { disable_with: false } %> +
    +
    +<% end %> diff --git a/app/views/contract_templates/index.html.erb b/app/views/contract_templates/index.html.erb new file mode 100644 index 0000000..05bb012 --- /dev/null +++ b/app/views/contract_templates/index.html.erb @@ -0,0 +1,35 @@ +<%= product_wordmark :release_me, class: "small mb-3" %> + +

    + <% if policy(ContractTemplate).new? %> + <%= link_to fa_icon("plus", text: t(".actions.new")), [:new, @project, :contract_template], class: "btn btn-primary" %> + <%= link_to fa_icon("clone", text: t(".actions.import")), [:new, @project, :release_template_imports], class: "btn btn-secondary" %> + <% end %> +

    + +
    + + + + + + + + + + + + <% if @contract_templates.any? %> + <%= render @contract_templates %> + <% else %> + + + + <% end %> + +
    <%= ContractTemplate.human_attribute_name(:name) %><%= ContractTemplate.human_attribute_name(:fee) %><%= t(".table_headers.release_type") %><%= t(".table_headers.signed_release_count") %>
    <%= t(".empty") %>
    +
    + +
    + <%= will_paginate @contract_templates %> +
    diff --git a/app/views/contract_templates/new.html.erb b/app/views/contract_templates/new.html.erb new file mode 100644 index 0000000..39eb9ce --- /dev/null +++ b/app/views/contract_templates/new.html.erb @@ -0,0 +1,6 @@ +
    + <%= card_header text: t(".heading"), close_action_path: [@project, :contract_templates] %> +
    + <%= render "form", project: @project, contract_template: @contract_template %> +
    +
    diff --git a/app/views/contracts/_files.html.erb b/app/views/contracts/_files.html.erb new file mode 100644 index 0000000..19ce468 --- /dev/null +++ b/app/views/contracts/_files.html.erb @@ -0,0 +1,46 @@ +<% if preview %> +

    PREVIEW ONLY

    +<% end %> + +

    Files

    + <% unless release.description.blank? %> +

    + Description: <%= release.description %> +

    + <% end %> + + <% photos = release.file_infos.photo %> + <% unless photos.empty? %> +

    Photos

    +
      + <% photos.each do |file_info| %> +
    • + <%= file_info.filename %> +
    • + <% end %> +
    + <% end %> + + <% videos = release.file_infos.video %> + <% unless videos.empty? %> +

    Videos

    +
      + <% videos.each do |file_info| %> +
    • + <%= file_info.filename %> +
    • + <% end %> +
    + <% end %> + + <% other = release.file_infos.other %> + <% unless other.empty? %> +

    Other files

    +
      + <% other.each do |file_info| %> +
    • + <%= file_info.filename %> +
    • + <% end %> +
    + <% end %> diff --git a/app/views/contracts/_logo.html.erb b/app/views/contracts/_logo.html.erb new file mode 100644 index 0000000..4a55475 --- /dev/null +++ b/app/views/contracts/_logo.html.erb @@ -0,0 +1,2 @@ +<%= image_tag account_logo_variant(logo) %> +
    diff --git a/app/views/contracts/_notes.html.erb b/app/views/contracts/_notes.html.erb new file mode 100644 index 0000000..58045c4 --- /dev/null +++ b/app/views/contracts/_notes.html.erb @@ -0,0 +1,12 @@ +

    Notes

    +
      + <% notable.notes.order(created_at: :desc).each do |note| %> +
    • + <%= note.content %> +
      + <%= note.email %> +
      + <%= note.created_at.strftime("%D %l:%M %p") %> +
    • + <% end %> +
    diff --git a/app/views/contracts/_photos.html.erb b/app/views/contracts/_photos.html.erb new file mode 100644 index 0000000..6f429f7 --- /dev/null +++ b/app/views/contracts/_photos.html.erb @@ -0,0 +1,34 @@ +<% if preview %> +

    PREVIEW ONLY

    +<% end %> +<% @total_photos_count = release.photos.size %> +<% if release.respond_to? :guardian_photo %> + <% @total_photos_count += release.guardian_photo.attached? ? 1 : 0 %> +<% end %> +

    <%= t '.heading', count: @total_photos_count %>

    + +
      + <% if preview %> + <%= image_tag dummy_photo, size: "200x200" %> + Dummy photo + <% else %> + <% if release.minor? %> +

      <%= t '.minor_photos_heading', count: release.photos.size %>

      + <% end %> + <% release.photos.each do |photo| %> +
    • + <%= image_tag photo.variant(auto_orient: true, resize: "200x200") %>
      + <%= photo.filename.to_s %> +
    • + <% end %> + <% if release.respond_to?(:guardian_photo) && release.guardian_photo.attached? %> +
      +

      <%= t '.guardian_photo_heading' %>

      +
    • + <%= image_tag release.guardian_photo.variant(auto_orient: true, resize: "200x200") %>
      + <%= release.guardian_photo.filename.to_s %> +
    • + <% end %> + <% end %> +
    + diff --git a/app/views/contracts/_project_info.html.erb b/app/views/contracts/_project_info.html.erb new file mode 100644 index 0000000..0367bf6 --- /dev/null +++ b/app/views/contracts/_project_info.html.erb @@ -0,0 +1,9 @@ +

    <%= t ".heading" %>

    +
    + <%= description_list_pair_for project, :producer_name, append: ":" %> + <%= description_list_pair_for project, :producer_address, append: ":" %> + <%= description_list_pair_for project, :name, append: ":" %> + <%= description_list_pair_for project, :client_name, append: ":" %> + <%= description_list_pair_for project, :description, append: ":" %> + <%= description_list_pair_for project, :details, append: ":" %> +
    diff --git a/app/views/contracts/_signature_page.html.erb b/app/views/contracts/_signature_page.html.erb new file mode 100644 index 0000000..e6ad5d6 --- /dev/null +++ b/app/views/contracts/_signature_page.html.erb @@ -0,0 +1,61 @@ +<% if preview %> +

    PREVIEW ONLY

    +<% end %> + +

    <%= t ".heading" %>

    +

    <%= t ".instructions", releasable_name: "#{releasable.model_name.name.titleize}" %>

    + +<% # Signer information %> +
    + <% # Only guardian signs if talent is a minor %> + <% unless releasable.minor? %> +
    <%= AppearanceRelease.human_attribute_name(:signature) %>:
    +
    + <% if preview %> + <%= image_tag dummy_signature %> + <% elsif releasable.signature.attached? %> + <%= image_tag releasable.signature.variant(auto_orient: true, resize: "200x200") %> + <% end %> +
    + <% end %> + <%= description_list_pair_for releasable, :name, append: ":" %> + <%= description_list_pair "Contact Address:", releasable.contact_person.address %> + <%= description_list_pair "Contact Phone:", releasable.contact_person.phone %> + <%= description_list_pair "Contact Email:", releasable.contact_person.email %> + <% if releasable.model_name == "AppearanceRelease" %> + <%= description_list_pair "Person Date of Birth:", releasable&.person_date_of_birth&.strftime("%D") %> + <% end %> + <%= description_list_pair_for releasable, :signed_on, append: ":" %> + <% if releasable.model_name == "LocationRelease" %> + <%= description_list_pair "Filming Started On:", releasable&.filming_started_on&.strftime("%D") %> + <%= description_list_pair "Filming Ended On:", releasable&.filming_ended_on&.strftime("%D") %> + <% end %> + <% if contract_template.fee? %> + <%= description_list_pair "Fee:", number_to_currency(contract_template.fee) %> + <% end %> + <% if releasable.model_name == "MaterialRelease" %> + <%= description_list_pair "Description:", releasable.description %> + <% end %> +
    + +<% if releasable.minor? %> +
    +

    Guardian Information

    + + <% # Guardian information %> +
    +
    <%= AppearanceRelease.human_attribute_name(:signature) %>:
    +
    + <% if preview %> + <%= image_tag dummy_signature %> + <% elsif releasable.signature.attached? %> + <%= image_tag releasable.signature.variant(auto_orient: true, resize: "200x200") %> + <% end %> +
    + <%= description_list_pair_for releasable, :guardian_name, append: ":" %> + <%= description_list_pair_for releasable, :guardian_address, append: ":" %> + <%= description_list_pair_for releasable, :guardian_phone, append: ":" %> + <%= description_list_pair_for releasable, :signed_on, append: ":" %> +
    + +<% end %> diff --git a/app/views/contracts/_tags.html.erb b/app/views/contracts/_tags.html.erb new file mode 100644 index 0000000..3c05821 --- /dev/null +++ b/app/views/contracts/_tags.html.erb @@ -0,0 +1,8 @@ +

    Tags

    +
      + <% taggable.tags.each do |tag| %> +
    • + <%= tag %> +
    • + <% end %> +
    diff --git a/app/views/contracts/pdf.html.erb b/app/views/contracts/pdf.html.erb new file mode 100644 index 0000000..b38fde7 --- /dev/null +++ b/app/views/contracts/pdf.html.erb @@ -0,0 +1,41 @@ +<% if local_assigns[:logo] %> + +
    +<% end %> +<% if preview %> +

    PREVIEW ONLY

    +<% end %> +<% if contract_template.body.present? %> + <%= contract_template.body %> +
    +<% end %> +<% if releasable.minor? && contract_template.guardian_clause.present? %> +

    Guardian Clause

    + <%= contract_template.guardian_clause %> +<% end %> +
    + <%= render "contracts/signature_page", releasable: releasable, contract_template: contract_template, preview: preview %> +
    +<% if releasable.class == AcquiredMediaRelease %> +
    + <%= render "contracts/files", release: releasable, preview: preview %> +
    +<% end %> +<% if releasable.try(:photos).present? %> +
    + <%= render "contracts/photos", release: releasable, preview: preview %> +
    +<% end %> + +<% if releasable.notes.any? %> +
    + <%= render "contracts/notes", notable: releasable %> +
    +<% end %> +<% if releasable.tags.any? %> +
    + <%= render "contracts/tags", taggable: releasable %> +
    +<% end %> diff --git a/app/views/directories/_directories.html.erb b/app/views/directories/_directories.html.erb new file mode 100644 index 0000000..eeec97a --- /dev/null +++ b/app/views/directories/_directories.html.erb @@ -0,0 +1,16 @@ +
    +

    <%= t(".heading") %> (<%= directories.size %>)

    +
      +
      +
    • +
      + <%= fa_icon("plus-circle", class: "text-success", style: "font-size:4rem") %> + <% if policy(Directory).new? %> + <%= link_to t("projects.index.actions.folder"), [:new, @project, :directory], class: "mt-4 text-reset text-decoration-none stretched-link" %> + <% end %> +
      +
    • +
      + <%= render directories, project: @project %> +
    +
    \ No newline at end of file diff --git a/app/views/directories/_directory.html.erb b/app/views/directories/_directory.html.erb new file mode 100644 index 0000000..29e1d46 --- /dev/null +++ b/app/views/directories/_directory.html.erb @@ -0,0 +1,29 @@ +
    +
  • +
    + <%= directory.permissions %> + +
    +
    + <%= link_to [project, directory], class: "d-block pb-5 text-decoration-none text-reset link-stretched" do %> +

    <%= directory.name %>

    + <% end %> +
    + +
  • +
    diff --git a/app/views/directories/_file.html.erb b/app/views/directories/_file.html.erb new file mode 100644 index 0000000..f8d1b3d --- /dev/null +++ b/app/views/directories/_file.html.erb @@ -0,0 +1,21 @@ + + + <%= file.filename %> + + + <%= file.created_at %> + + +
    + <%= button_tag t(".manage"), class: "btn btn-light btn-sm dropdown-toggle border", data: { toggle: "dropdown", boundary: "window" }, aria: { haspopup: true, expanded: false } %> + +
    + + diff --git a/app/views/directories/_file_form.html.erb b/app/views/directories/_file_form.html.erb new file mode 100644 index 0000000..f0257fd --- /dev/null +++ b/app/views/directories/_file_form.html.erb @@ -0,0 +1,8 @@ +<%= bootstrap_form_with model: model do |form| %> + <%= field_set_tag content_tag(:span, t(".heading"), class: "h6 text-muted text-uppercase") do %> + <%= render "shared/files_dropzone_fields", form: form, directory: directory %> + <% end %> +
    + <%= form.submit t(".submit"), class: "btn btn-block btn-success", data: { disable_with: t("shared.disable_with") } %> +
    +<% end %> diff --git a/app/views/directories/_form.html.erb b/app/views/directories/_form.html.erb new file mode 100644 index 0000000..3563bde --- /dev/null +++ b/app/views/directories/_form.html.erb @@ -0,0 +1,21 @@ +<%= errors_summary_for directory %> + +<%= bootstrap_form_with model: model, local: true do |form| %> + <%= form.text_field :name %> + <%= form.select :category, Directory.categories.keys, {}, class: "form-control custom-select" %> + <% if policy(@directory).can_view_permissions_settings? %> + <%= form.form_group :permissions, label: { text: "Access Permissions:" }, help: t(".permissions_help") do %> + <% if Current.user.manager?(Current.account) %> + <%= form.radio_button :permissions, "Everyone", label: "Everyone" %> + <%= form.radio_button :permissions, "Account Managers & Project Managers", label: "Account Managers & Project Managers" %> + <% elsif Current.user.account_manager?(Current.account) %> + <%= form.radio_button :permissions, "Everyone", label: "Everyone" %> + <%= form.radio_button :permissions, "Account Managers & Project Managers", label: "Account Managers & Project Managers" %> + <%= form.radio_button :permissions, "Account Managers Only", label: "Account Managers Only" %> + <% end %> + <% end %> + <% end %> +
    + <%= form.submit class: class_string("btn btn-block", ["btn-success", "btn-primary"] => directory.new_record?), data: { disable_with: t("shared.disable_with") } %> +
    +<% end %> diff --git a/app/views/directories/edit.html.erb b/app/views/directories/edit.html.erb new file mode 100644 index 0000000..3125e0c --- /dev/null +++ b/app/views/directories/edit.html.erb @@ -0,0 +1,6 @@ +
    + <%= card_header text: t(".heading"), close_action_path: @project %> +
    + <%= render "form", model: [@project, @directory], directory: @directory %> +
    +
    \ No newline at end of file diff --git a/app/views/directories/new.html.erb b/app/views/directories/new.html.erb new file mode 100644 index 0000000..3c18f4a --- /dev/null +++ b/app/views/directories/new.html.erb @@ -0,0 +1,6 @@ +
    + <%= card_header text: t(".heading"), close_action_path: @project %> +
    + <%= render "form", model: [@project, @directory], directory: @directory %> +
    +
    diff --git a/app/views/directories/new_file.html.erb b/app/views/directories/new_file.html.erb new file mode 100644 index 0000000..ae042b3 --- /dev/null +++ b/app/views/directories/new_file.html.erb @@ -0,0 +1,5 @@ +
    +
    + <%= render "file_form", model: [@project, @directory], directory: @directory %> +
    +
    \ No newline at end of file diff --git a/app/views/directories/show.html.erb b/app/views/directories/show.html.erb new file mode 100644 index 0000000..8db1170 --- /dev/null +++ b/app/views/directories/show.html.erb @@ -0,0 +1,34 @@ +
    + <% if policy(Directory).new_file? %> + <%= link_to fa_icon("plus", text: "Add File"), [:new_file, @project, @directory], class: "btn btn-primary" %> + <% end %> + + <%= bootstrap_form_with url: [@project, @directory], method: :get, remote: true, layout: :inline do |form| %> + <%= form.search_field :query, hide_label: true, placeholder: t(".actions.search"), class: "rounded-pill-right", value: params[:query], prepend: form.button(fa_icon("search"), class: "btn btn-light border rounded-pill-left") %> + <% end %> +
    + +
    + + + + + + + + + + <% if @files.any? %> + <%= render partial: "file", collection: @files %> + <% else %> + + + + <% end %> + +
    <%= Directory.human_attribute_name(:name) %><%= Directory.human_attribute_name(:created_at) %>
    <%= t(".empty") %>
    +
    + +
    + <%= will_paginate @files %> +
    diff --git a/app/views/directories/show.js.erb b/app/views/directories/show.js.erb new file mode 100644 index 0000000..f8beab2 --- /dev/null +++ b/app/views/directories/show.js.erb @@ -0,0 +1,3 @@ +$("#files").html("<%= j render(partial: 'file', collection: @files) %>"); +$("form input[type='search']").val("<%= params[:query] %>"); +$("#files_pagination").html("<%= j will_paginate(@files) %>"); diff --git a/app/views/downloads/_download.html.erb b/app/views/downloads/_download.html.erb new file mode 100644 index 0000000..1b4f83c --- /dev/null +++ b/app/views/downloads/_download.html.erb @@ -0,0 +1,27 @@ + + + <%= download.name %> + + + <%= download.release_type&.titleize %> + + + <%= download.status.titleize %> + + + <%= download.created_at.strftime("%D %T") %> + + +
    + <%= button_tag t(".actions.manage"), class: "btn btn-light btn-sm dropdown-toggle border", data: { toggle: "dropdown", boundary: "window" }, aria: { haspopup: true, expanded: false } %> + +
    + + \ No newline at end of file diff --git a/app/views/downloads/index.html.erb b/app/views/downloads/index.html.erb new file mode 100644 index 0000000..1255796 --- /dev/null +++ b/app/views/downloads/index.html.erb @@ -0,0 +1,32 @@ +
    + <%= bootstrap_form_with url: project_downloads_path, method: :get, remote: true, layout: :inline, id: "search" do |form| %> + <%= form.search_field :query, hide_label: true, placeholder: t(".actions.search"), class: "rounded-pill-right", value: params[:query], prepend: form.button(fa_icon("search"), id: "search-button", class: "btn btn-light border rounded-pill-left") %> + <% end %> +
    + +
    + + + + + + + + + + + + <% if @downloads.any? %> + <%= render partial: "download", collection: @downloads %> + <% else %> + + + + <% end %> + +
    <%= t(".table_headers.download_name") %><%= t(".table_headers.download_type") %><%= t(".table_headers.download_status") %><%= t(".table_headers.download_created_at") %>
    <%= t(".empty") %>
    +
    + +
    + <%= will_paginate @downloads %> +
    diff --git a/app/views/downloads/index.js.erb b/app/views/downloads/index.js.erb new file mode 100644 index 0000000..7024ae4 --- /dev/null +++ b/app/views/downloads/index.js.erb @@ -0,0 +1,3 @@ +$("#downloads").html("<%= j render(@downloads) %>"); +$("form input[type='search']").val("<%= params[:query] %>"); +$("#downloads_pagination").html("<%= j will_paginate @downloads %>"); \ No newline at end of file diff --git a/app/views/file_infos/edit.html.erb b/app/views/file_infos/edit.html.erb new file mode 100644 index 0000000..b5399c4 --- /dev/null +++ b/app/views/file_infos/edit.html.erb @@ -0,0 +1,26 @@ +
    + <%= card_header text: t(".heading"), subtext: @releasable.name, close_action_path: [@project, @releasable.model_name.plural] %> +
    + <% if @releasable.file_infos.any? %> +
    + <%= fa_icon "info-circle" %> + To Add Photos & Videos to the release: Drag & Drop Files or Click or Tap here to browse media. +
    + <% end %> +
    + <%= fa_icon "warning" %> + For optimal accuracy, please ensure video file names and photo file names match the source file name in the editing sequence. +
    + <%= bootstrap_form_with model: @releasable, url: [@releasable, :file_infos] do |form| %> + <%= render "shared/file_infos_dropzone", form: form, releasable: @releasable %> + +
    + <%= link_to t("shared.cancel"), [@project, @releasable.model_name.plural], class: "col-3 text-reset" %> +
    + <%= form.button id: "submit_release", class: "btn btn-block btn-primary", data: { disable_with: t("shared.disable_with") } %> +
    +
    + <% end %> +
    +
    + diff --git a/app/views/layouts/admin/application.html.erb b/app/views/layouts/admin/application.html.erb new file mode 100644 index 0000000..a12ee6a --- /dev/null +++ b/app/views/layouts/admin/application.html.erb @@ -0,0 +1,31 @@ + + + + BiGMedia.ai App + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + + + + <%= favicon_link_tag %> + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + + + + <%= render "header" %> +
    +
    +
    + <%= render "side_nav" %> +
    + +
    + <%= render "application/flash" %> + <%= yield %> +
    +
    +
    + + diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000..5bee701 --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,36 @@ + + + + BiGMedia.ai App + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + + + <%= yield :meta %> + + <%= favicon_link_tag %> + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + <%= javascript_pack_tag 'application' %> + <%= javascript_include_analytics_js %> + + + + <%= content_for?(:header) ? yield(:header) : render("header") %> + <%= render "masquerade" if masquerading? %> +
    +
    + <%= render "flash" %> +
    + <%= content_for?(:content) ? yield(:content) : yield %> + + + + <%= identify_user_for_analytics Current.user %> + <%= javascript_tag nonce: true do %> + if (!navigator.cookieEnabled && window.location.pathname !== "<%= cookies_disabled_path %>") { + window.location = "<%= cookies_disabled_path %>"; + } + <% end %> + diff --git a/app/views/layouts/contract_pdf.html.erb b/app/views/layouts/contract_pdf.html.erb new file mode 100644 index 0000000..4d06e33 --- /dev/null +++ b/app/views/layouts/contract_pdf.html.erb @@ -0,0 +1,14 @@ + + + + + + + + + + <%= yield %> + + diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb new file mode 100644 index 0000000..cbd34d2 --- /dev/null +++ b/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb new file mode 100644 index 0000000..37f0bdd --- /dev/null +++ b/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/app/views/layouts/project.html.erb b/app/views/layouts/project.html.erb new file mode 100644 index 0000000..27f985f --- /dev/null +++ b/app/views/layouts/project.html.erb @@ -0,0 +1,16 @@ +<%= content_for :meta do %> + +<% end %> +<% content_for :content do %> +
    +
    + <%= render "sidebar", project: @project %> +
    + +
    + <%= render "breadcrumbs" %> + <%= yield %> +
    +
    +<% end %> +<%= render template: "layouts/application" %> diff --git a/app/views/location_releases/_form.html.erb b/app/views/location_releases/_form.html.erb new file mode 100644 index 0000000..33aeb45 --- /dev/null +++ b/app/views/location_releases/_form.html.erb @@ -0,0 +1,56 @@ +<%= errors_summary_for location_release %> +<%= bootstrap_form_with model: model, local: true do |form| %> + <%= field_set_tag content_tag(:span, t(".location_details.heading"), class: "h6 text-muted text-uppercase") do %> +
    + <%= form.text_field :name, required: true, wrapper_class: "col-12" %> +
    + <%= render "shared/address_fields", form: form, subject: "" %> + <% end %> + +
    + + <%= field_set_tag content_tag(:span, t(".signer_details.heading"), class: "h6 text-muted text-uppercase") do %> +
    + <%= form.text_field :person_first_name, wrapper_class: "col-sm-3" %> + <%= form.text_field :person_last_name, wrapper_class: "col-sm-3" %> + <%= form.phone_field :person_phone, wrapper_class: "col-sm-6" %> +
    +
    + <%= form.email_field :person_email, wrapper_class: "col-sm-6" %> +
    +
    + <%= form.text_field :person_company, wrapper_class: "col-sm-6" %> + <%= form.text_field :person_title, wrapper_class: "col-sm-6" %> +
    + <%= render "shared/address_fields", form: form, subject: "person" %> + <% end %> + +
    + + <%= field_set_tag content_tag(:span, t(".contract_and_rights.heading"), class: "h6 text-muted text-uppercase") do %> + <%= render "shared/contract_fields", form: form, release: location_release %> + <%= render "shared/exploitable_rights_fields", form: form %> + <% end %> + +
    + + <%= field_set_tag t(".filming_info.heading") do %> +
    + <%= form.text_field :filming_started_on, wrapper_class: "col-sm-6", class: "datepicker-control", readonly: true %> + <%= form.text_field :filming_ended_on, wrapper_class: "col-sm-6", class: "datepicker-control", readonly: true %> +
    + <% end %> + +
    + + <%= field_set_tag content_tag(:span, t(".photos.heading"), class: "h6 text-muted text-uppercase") do %> + <%= render "shared/photos_dropzone_fields", form: form, release: location_release %> + <% end %> + +
    + <%= link_to t("shared.cancel"), [location_release.project, :location_releases], class: "col-3 text-reset" %> +
    + <%= form.button id: "submit_release", class: class_string("btn btn-block", ["btn-success", "btn-primary"] => location_release.new_record?), data: { disable_with: t("shared.disable_with") } %> +
    +
    +<% end %> diff --git a/app/views/location_releases/_location_release.html.erb b/app/views/location_releases/_location_release.html.erb new file mode 100644 index 0000000..4e17703 --- /dev/null +++ b/app/views/location_releases/_location_release.html.erb @@ -0,0 +1,52 @@ + + <%= check_box_tag "location_release_ids[]", location_release.id, false %> + + <% if location_release.photo.attached? %> + <%= image_tag medium_variant(location_release.photo), class: "img-fluid" %> + <% else %> + <%= fa_icon("warning", text: t(".no_photos"), class: "text-danger") %> + <% end %> + + + <%= location_release.name %> + + + <%= contact_info address: location_release.address %> + + + <%= notes_preview location_release.notes.order_by_recent %> + + "> + <%= tags_preview location_release, location_release.tags %> + + + <%= location_release.signed_on %> + + + +
    + <%= button_tag t(".actions.manage"), class: "btn btn-light btn-sm dropdown-toggle border", data: { toggle: "dropdown", boundary: "window" }, aria: { haspopup: true, expanded: false } %> + +
    + + + diff --git a/app/views/location_releases/edit.html.erb b/app/views/location_releases/edit.html.erb new file mode 100644 index 0000000..ddfe9b0 --- /dev/null +++ b/app/views/location_releases/edit.html.erb @@ -0,0 +1,6 @@ +
    + <%= card_header text: t(".heading"), close_action_path: [@project, :location_releases] %> +
    + <%= render "form", model: @location_release, location_release: @location_release %> +
    +
    diff --git a/app/views/location_releases/index.html.erb b/app/views/location_releases/index.html.erb new file mode 100644 index 0000000..3760b18 --- /dev/null +++ b/app/views/location_releases/index.html.erb @@ -0,0 +1,53 @@ +
    +
    +
    + <% if policy(LocationRelease).new? %> + <%= link_to fa_icon("plus", text: t(".actions.new")), [:new, @project, :location_release], class: "btn btn-primary mr-2 mb-2" %> + <% end %> + + <% if @location_releases.any? && policy(LocationRelease).tag_multiple? %> + <%= button_to_bulk_tagging(@project) %> + <% end %> + + <% if @location_releases.any? && policy(LocationRelease).download_multiple? %> + <%= link_to "Download All", [@project, :contract_downloads, release_type: @location_releases.name], method: :post, remote: true, class: "btn btn-light border ml-auto mr-2 mb-2", data: { + disable_with: "Please wait..." } %> + <% end %> + + <%= bootstrap_form_with url: [@project, :location_releases], method: :get, remote: true, layout: :inline, id: "search" do |form| %> + <%= form.search_field :query, hide_label: true, placeholder: t(".actions.search"), class: "rounded-pill-right", value: params[:query], prepend: form.button(fa_icon("search"), class: "btn btn-light border mb-2 rounded-pill-left") %> + <% end %> +
    +
    +
    + +
    + + + + + + + + + + + + + + + <% if @location_releases.any? %> + <%= render @location_releases %> + <% else %> + + + + <% end %> + +
    <%= check_box_tag "location_release_ids[]", false, false %><%= LocationRelease.human_attribute_name(:name) %><%= t(".table_headers.address") %> + <%= t(".table_headers.notes") %><%= t(".table_headers.tags") %><%= t(".table_headers.signed_at") %>
    <%= t(".empty") %>
    +
    + +
    + <%= will_paginate @location_releases %> +
    diff --git a/app/views/location_releases/index.js.erb b/app/views/location_releases/index.js.erb new file mode 100644 index 0000000..263a8a6 --- /dev/null +++ b/app/views/location_releases/index.js.erb @@ -0,0 +1,3 @@ +$("#location_releases").html("<%= j render(@location_releases) %>"); +$("form input[type='search']").val("<%= params[:query] %>"); +$("#location_releases_pagination").html("<%= j will_paginate(@location_releases) %>"); diff --git a/app/views/location_releases/new.html.erb b/app/views/location_releases/new.html.erb new file mode 100644 index 0000000..fa2ad49 --- /dev/null +++ b/app/views/location_releases/new.html.erb @@ -0,0 +1,6 @@ +
    + <%= card_header text: t(".heading"), close_action_path: [@project, :location_releases] %> +
    + <%= render "form", model: [@project, @location_release], location_release: @location_release %> +
    +
    diff --git a/app/views/material_releases/_form.html.erb b/app/views/material_releases/_form.html.erb new file mode 100644 index 0000000..8a336ca --- /dev/null +++ b/app/views/material_releases/_form.html.erb @@ -0,0 +1,49 @@ +<%= errors_summary_for material_release %> +<%= bootstrap_form_with model: model, local: true do |form| %> + <%= field_set_tag content_tag(:span, t(".material_details.heading"), class: "h6 text-muted text-uppercase") do %> +
    + <%= form.text_field :name, required: true, wrapper_class: "col-12" %> +
    +
    + <%= form.text_area :description, placeholder: true, wrapper_class: "col-sm-12", rows: 6 %> +
    + <% end %> + +
    + + <%= field_set_tag content_tag(:span, t(".signer_details.heading"), class: "h6 text-muted text-uppercase") do %> +
    + <%= form.text_field :person_first_name, wrapper_class: "col-sm-3" %> + <%= form.text_field :person_last_name, wrapper_class: "col-sm-3" %> + <%= form.phone_field :person_phone, wrapper_class: "col-sm-6" %> +
    +
    + <%= form.email_field :person_email, wrapper_class: "col-sm-6" %> +
    +
    + <%= form.text_field :person_company, wrapper_class: "col-sm-6" %> + <%= form.text_field :person_title, wrapper_class: "col-sm-6" %> +
    + <%= render "shared/address_fields", form: form, subject: "person" %> + <% end %> + +
    + + <%= field_set_tag content_tag(:span, t(".contract_and_rights.heading"), class: "h6 text-muted text-uppercase") do %> + <%= render "shared/contract_fields", form: form, release: material_release %> + <%= render "shared/exploitable_rights_fields", form: form %> + <% end %> + +
    + + <%= field_set_tag content_tag(:span, t(".photos.heading"), class: "h6 text-muted text-uppercase") do %> + <%= render "shared/photos_dropzone_fields", form: form, release: material_release %> + <% end %> + +
    + <%= link_to t("shared.cancel"), [material_release.project, :material_releases], class: "col-3 text-reset" %> +
    + <%= form.button id: "submit_release", class: class_string("btn btn-block", ["btn-success", "btn-primary"] => material_release.new_record?), data: { disable_with: t("shared.disable_with") } %> +
    +
    +<% end %> diff --git a/app/views/material_releases/_material_release.html.erb b/app/views/material_releases/_material_release.html.erb new file mode 100644 index 0000000..9b7929f --- /dev/null +++ b/app/views/material_releases/_material_release.html.erb @@ -0,0 +1,49 @@ + + <%= check_box_tag "material_release_ids[]", material_release.id, false %> + + <% if material_release.photo.attached? %> + <%= image_tag medium_variant(material_release.photo), class: "img-fluid" %> + <% else %> + <%= fa_icon("warning", text: t(".no_photos"), class: "text-danger") %> + <% end %> + + + <%= material_release.name %> + + + <%= notes_preview material_release.notes.order_by_recent %> + + "> + <%= tags_preview material_release, material_release.tags %> + + + <%= material_release.signed_on %> + + + +
    + <%= button_tag t(".actions.manage"), class: "btn btn-light btn-sm dropdown-toggle border", data: { toggle: "dropdown", boundary: "window" }, aria: { haspopup: true, expanded: false } %> + +
    + + + diff --git a/app/views/material_releases/edit.html.erb b/app/views/material_releases/edit.html.erb new file mode 100644 index 0000000..13f31eb --- /dev/null +++ b/app/views/material_releases/edit.html.erb @@ -0,0 +1,6 @@ +
    + <%= card_header text: t(".heading"), close_action_path: [@project, :material_releases] %> +
    + <%= render "form", model: @material_release, material_release: @material_release %> +
    +
    diff --git a/app/views/material_releases/index.html.erb b/app/views/material_releases/index.html.erb new file mode 100644 index 0000000..39aa2a6 --- /dev/null +++ b/app/views/material_releases/index.html.erb @@ -0,0 +1,52 @@ +
    +
    +
    + <% if policy(MaterialRelease).new? %> + <%= link_to fa_icon("plus", text: t(".actions.new")), [:new, @project, :material_release], class: "btn btn-primary mr-2 mb-2" %> + <% end %> + + <% if @material_releases.any? && policy(MaterialRelease).tag_multiple? %> + <%= button_to_bulk_tagging(@project) %> + <% end %> + + <% if @material_releases.any? && policy(MaterialRelease).download_multiple? %> + <%= link_to "Download All", [@project, :contract_downloads, release_type: @material_releases.name], method: :post, remote: true, class: "btn btn-light border ml-auto mr-2 mb-2", data: { + disable_with: "Please wait..." } %> + <% end %> + + <%= bootstrap_form_with url: [@project, :material_releases], method: :get, remote: true, layout: :inline, id: "search" do |form| %> + <%= form.search_field :query, hide_label: true, placeholder: t(".actions.search"), class: "rounded-pill-right", value: params[:query], prepend: form.button(fa_icon("search"), class: "btn btn-light border mb-2 rounded-pill-left") %> + <% end %> +
    +
    +
    + +
    + + + + + + + + + + + + + + + <% if @material_releases.any? %> + <%= render @material_releases %> + <% else %> + + + + <% end %> + +
    <%= check_box_tag "material_release_ids[]", false, false %><%= MaterialRelease.human_attribute_name(:name) %><%= t(".table_headers.notes") %><%= t(".table_headers.tags") %><%= t(".table_headers.signed_at") %>
    <%= t(".empty") %>
    +
    + +
    + <%= will_paginate @material_releases %> +
    diff --git a/app/views/material_releases/index.js.erb b/app/views/material_releases/index.js.erb new file mode 100644 index 0000000..69a9833 --- /dev/null +++ b/app/views/material_releases/index.js.erb @@ -0,0 +1,3 @@ +$("#material_releases").html("<%= j render(@material_releases) %>"); +$("form input[type='search']").val("<%= params[:query] %>"); +$("#material_releases_pagination").html("<%= j will_paginate(@material_releases) %>"); diff --git a/app/views/material_releases/new.html.erb b/app/views/material_releases/new.html.erb new file mode 100644 index 0000000..2788d03 --- /dev/null +++ b/app/views/material_releases/new.html.erb @@ -0,0 +1,6 @@ +
    + <%= card_header text: t(".heading"), close_action_path: [@project, :material_releases] %> +
    + <%= render "form", model: [@project, @material_release], material_release: @material_release %> +
    +
    diff --git a/app/views/music_releases/_form.html.erb b/app/views/music_releases/_form.html.erb new file mode 100644 index 0000000..d5535db --- /dev/null +++ b/app/views/music_releases/_form.html.erb @@ -0,0 +1,80 @@ +<%= errors_summary_for music_release %> +<%= bootstrap_form_with model: model, local: true do |form| %> + <%= field_set_tag content_tag(:span, t(".music_details.heading"), class: "h6 text-muted text-uppercase") do %> +
    + <%= form.text_field :name, required: true, wrapper_class: "col-12" %> +
    + <% end %> + +
    + + <%= field_set_tag content_tag(:span, t(".files.heading"), class: "h6 text-muted text-uppercase") do %> +
    + <%= fa_icon "warning" %> + For optimal accuracy, please ensure music file names match the source file name in the editing + sequence. +
    + <%= render "shared/file_infos_dropzone", form: form, releasable: music_release %> + <% end %> + +
    + + <%= field_set_tag content_tag(:span, t(".contract_and_rights.heading"), class: "h6 text-muted text-uppercase") do %> + <%= render "shared/contract_fields", form: form, release: music_release %> + <%= render "shared/exploitable_rights_fields", form: form %> + <% end %> + +
    + + <%= field_set_tag content_tag(:span, t(".composers.heading"), class: "h6 text-muted text-uppercase") do %> + <%= form.fields_for :composers do |composer_form| %> +
    + <%= composer_form.label "Composer #{composer_form.index+1}" %> +
    + <%= composer_form.text_field :name, placeholder: true, hide_label: true, wrapper_class: "col-sm-3" %> + <%= composer_form.text_field :cae_number, placeholder: "CAE / IPI #", hide_label: true, wrapper_class: "col-sm-3" %> + <%= composer_form.text_field :affiliation, placeholder: true, hide_label: true, wrapper_class: "col-sm-3" %> + <%= composer_form.number_field :percentage, placeholder: true, hide_label: true, wrapper_class: "col-sm-3" %> +
    +
    + <% end %> + <% end %> + +
    + + <%= field_set_tag content_tag(:span, t(".publishers.heading"), class: "h6 text-muted text-uppercase") do %> + <%= form.fields_for :publishers do |publisher_form| %> +
    + <%= publisher_form.label "Publisher #{publisher_form.index+1}" %> +
    + <%= publisher_form.text_field :name, placeholder: true, hide_label: true, wrapper_class: "col-sm-6" %> + <%= publisher_form.text_field :affiliation, placeholder: true, hide_label: true, wrapper_class: "col-sm-3" %> + <%= publisher_form.number_field :percentage, placeholder: true, hide_label: true, wrapper_class: "col-sm-3" %> +
    +
    + <% end %> + <% end %> + +
    + + <%= field_set_tag content_tag(:span, t(".signer_details.heading"), class: "h6 text-muted text-uppercase") do %> +
    + <%= form.text_field :person_first_name, wrapper_class: "col-sm-3" %> + <%= form.text_field :person_last_name, wrapper_class: "col-sm-3" %> + <%= form.phone_field :person_phone, wrapper_class: "col-sm-6" %> + <%= form.email_field :person_email, wrapper_class: "col-sm-6" %> +
    +
    + <%= form.text_field :person_company, wrapper_class: "col-sm-6" %> + <%= form.text_field :person_title, wrapper_class: "col-sm-6" %> +
    + <%= render "shared/address_fields", form: form, subject: "person" %> + <% end %> + +
    + <%= link_to t("shared.cancel"), [music_release.project, :music_releases], class: "col-3 text-reset" %> +
    + <%= form.button id: "submit_release", class: class_string("btn btn-block", ["btn-success", "btn-primary"] => music_release.new_record?), data: { disable_with: t("shared.disable_with") } %> +
    +
    +<% end %> diff --git a/app/views/music_releases/_music_release.html.erb b/app/views/music_releases/_music_release.html.erb new file mode 100644 index 0000000..45c4587 --- /dev/null +++ b/app/views/music_releases/_music_release.html.erb @@ -0,0 +1,49 @@ + + <%= check_box_tag "music_release_ids[]", music_release.id, false %> + + <%= music_release.name %> + + + <%= music_release.file_infos.size %> + + + <%= music_release.composers.size %> + + + <%= music_release.publishers.size %> + + + <%= notes_preview music_release.notes.order_by_recent %> + + "> + <%= tags_preview music_release, music_release.tags %> + + + + <%= music_release.signed_on %> + + + +
    + <%= button_tag t(".actions.manage"), class: "btn btn-light btn-sm dropdown-toggle border", data: { toggle: "dropdown", boundary: "window" }, aria: { haspopup: true, expanded: false } %> + +
    + + + diff --git a/app/views/music_releases/edit.html.erb b/app/views/music_releases/edit.html.erb new file mode 100644 index 0000000..41c19ce --- /dev/null +++ b/app/views/music_releases/edit.html.erb @@ -0,0 +1,6 @@ +
    + <%= card_header text: t(".heading"), close_action_path: [@project, :music_releases] %> +
    + <%= render "form", model: @music_release, music_release: @music_release %> +
    +
    diff --git a/app/views/music_releases/index.html.erb b/app/views/music_releases/index.html.erb new file mode 100644 index 0000000..0bfc42a --- /dev/null +++ b/app/views/music_releases/index.html.erb @@ -0,0 +1,53 @@ +
    +
    +
    + <% if policy(MusicRelease).new? %> + <%= link_to fa_icon("plus", text: t(".actions.new")), [:new, @project, :music_release], class: "btn btn-primary mr-2 mb-2" %> + <% end %> + + <% if @music_releases.any? && policy(MusicRelease).tag_multiple? %> + <%= button_to_bulk_tagging(@project) %> + <% end %> + + <% if @music_releases.any? && policy(MusicRelease).download_multiple? %> + <%= link_to "Download All", [@project, :contract_downloads, release_type: @music_releases.name], method: :post, remote: true, class: "btn btn-light border ml-auto mr-2 mb-2" %> + <% end %> + + <%= bootstrap_form_with url: [@project, :music_releases], method: :get, remote: true, layout: :inline, id: "search" do |form| %> + <%= form.search_field :query, hide_label: true, placeholder: t(".actions.search"), class: "rounded-pill-right", value: params[:query], prepend: form.button(fa_icon("search"), class: "btn btn-light border mb-2 rounded-pill-left") %> + <% end %> +
    +
    +
    + +
    + + + + + + + + + + + + + + + + + <% if @music_releases.any? %> + <%= render @music_releases %> + <% else %> + + + + <% end %> + +
    <%= check_box_tag "music_release_ids[]", false, false %><%= MusicRelease.human_attribute_name(:name) %><%= t(".table_headers.file_infos_count") %><%= t(".table_headers.composers_count") %><%= t(".table_headers.publishers_count") %><%= t(".table_headers.notes") %><%= t(".table_headers.tags") %><%= t(".table_headers.signed_at") %>
    <%= t(".empty") %>
    +
    + +
    + <%= will_paginate @music_releases %> +
    diff --git a/app/views/music_releases/index.js.erb b/app/views/music_releases/index.js.erb new file mode 100644 index 0000000..6c1706d --- /dev/null +++ b/app/views/music_releases/index.js.erb @@ -0,0 +1,3 @@ +$("#music_releases").html("<%= j render(@music_releases) %>"); +$("form input[type='search']").val("<%= params[:query] %>"); +$("#music_releases_pagination").html("<%= j will_paginate(@music_releases) %>"); diff --git a/app/views/music_releases/new.html.erb b/app/views/music_releases/new.html.erb new file mode 100644 index 0000000..ca9ea9e --- /dev/null +++ b/app/views/music_releases/new.html.erb @@ -0,0 +1,6 @@ +
    + <%= card_header text: t(".heading"), close_action_path: [@project, :music_releases] %> +
    + <%= render "form", model: [@project, @music_release], music_release: @music_release %> +
    +
    diff --git a/app/views/notes/_form.html.erb b/app/views/notes/_form.html.erb new file mode 100644 index 0000000..6105467 --- /dev/null +++ b/app/views/notes/_form.html.erb @@ -0,0 +1,19 @@ +<%= bootstrap_form_with model: [releasable, note] do |form| %> + +<% end %> diff --git a/app/views/notes/_new_note_modal.html.erb b/app/views/notes/_new_note_modal.html.erb new file mode 100644 index 0000000..7a79fcd --- /dev/null +++ b/app/views/notes/_new_note_modal.html.erb @@ -0,0 +1,3 @@ + diff --git a/app/views/notes/_notes_modal.html.erb b/app/views/notes/_notes_modal.html.erb new file mode 100644 index 0000000..7978b95 --- /dev/null +++ b/app/views/notes/_notes_modal.html.erb @@ -0,0 +1,29 @@ + + diff --git a/app/views/notes/create.js.erb b/app/views/notes/create.js.erb new file mode 100644 index 0000000..6543c61 --- /dev/null +++ b/app/views/notes/create.js.erb @@ -0,0 +1,8 @@ +var $modal = $("#<%= dom_id Note.new, dom_id(@releasable) %>") + +<% if @note.valid? %> + $modal.modal("toggle"); + $("tr#<%= dom_id @releasable %>").replaceWith("<%= j render(@releasable) %>"); +<% else %> + $modal.html("<%= j render(partial: "form", locals: { releasable: @releasable, note: @note }) %>") +<% end %> diff --git a/app/views/notes/index.js.erb b/app/views/notes/index.js.erb new file mode 100644 index 0000000..e1c4657 --- /dev/null +++ b/app/views/notes/index.js.erb @@ -0,0 +1,11 @@ +var modalSelector = "#<%= dom_id @releasable, "notes" %>" +var modal = "<%= j render("notes_modal", releasable: @releasable, notes: @notes) %>" + +// If the modal already exists, replace it. Otherwise, create it +if ($(modalSelector).length > 0) { + $(modalSelector).replaceWith(modal) +} else { + $('body').append(modal) +} + +$(modalSelector).modal("toggle") diff --git a/app/views/notes/new.js.erb b/app/views/notes/new.js.erb new file mode 100644 index 0000000..e36403b --- /dev/null +++ b/app/views/notes/new.js.erb @@ -0,0 +1,11 @@ +var modalSelector = "#<%= dom_id @note, dom_id(@releasable) %>" +var modal = "<%= j render("new_note_modal", releasable: @releasable, note: @note) %>" + +// If the modal already exists, replace it. Otherwise, create it +if ($(modalSelector).length > 0) { + $(modalSelector).replaceWith(modal) +} else { + $('body').append(modal) +} + +$(modalSelector).modal("toggle") diff --git a/app/views/pages/.keep b/app/views/pages/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/views/pages/accountless_user.html.erb b/app/views/pages/accountless_user.html.erb new file mode 100644 index 0000000..79b4609 --- /dev/null +++ b/app/views/pages/accountless_user.html.erb @@ -0,0 +1,6 @@ +
    +
    +

    You are signed in but not added to an account

    +

    You've reached this page because you have an user profile on MeSuite but it is not associated with an active account. You will not be able to navigate the site until you are invited to a project or an account.

    +
    +
    diff --git a/app/views/pages/cookies_disabled.html.erb b/app/views/pages/cookies_disabled.html.erb new file mode 100644 index 0000000..42e77e4 --- /dev/null +++ b/app/views/pages/cookies_disabled.html.erb @@ -0,0 +1,7 @@ +
    +
    +

    Cookies are disabled

    +

    You've reached this page because cookies are disabled in your browser. You will not be able to navigate the site until cookies are enabled.

    +

    Please <%= link_to "click here", "https://help.overdrive.com/en-us/0606.html", target: :_blank %> to learn how to easily enable cookies.

    +
    +
    diff --git a/app/views/password_resets/edit.html.erb b/app/views/password_resets/edit.html.erb new file mode 100644 index 0000000..74d6a0c --- /dev/null +++ b/app/views/password_resets/edit.html.erb @@ -0,0 +1,8 @@ +<% content_for :page_title, t(".title") %> + +
    + <%= bootstrap_form_with scope: :password_reset, url: password_reset_path(@user.password_reset_token), method: :patch, local: true, html: { autocorrect: :off, autocapitalize: :none, autocomplete: :off, spellcheck: false } do |form| %> + <%= form.password_field :password, required: true %> + <%= form.button t(".submit"), class: "btn btn-block btn-lg btn-primary" %> + <% end %> +
    diff --git a/app/views/password_resets/new.html.erb b/app/views/password_resets/new.html.erb new file mode 100644 index 0000000..86e2914 --- /dev/null +++ b/app/views/password_resets/new.html.erb @@ -0,0 +1,8 @@ +<% content_for :page_title, t(".title") %> + +
    + <%= bootstrap_form_with scope: :password_reset, url: password_resets_path, method: :post, html: { autocorrect: :off, autocapitalize: :none, autocomplete: :off, spellcheck: false } do |form| %> + <%= form.email_field :email, required: true %> + <%= form.button t(".submit"), class: "btn btn-block btn-lg btn-primary" %> + <% end %> +
    diff --git a/app/views/photos/edit.html.erb b/app/views/photos/edit.html.erb new file mode 100644 index 0000000..4a6145f --- /dev/null +++ b/app/views/photos/edit.html.erb @@ -0,0 +1,16 @@ +
    + <%= card_header text: t(".heading"), subtext: @releasable.name, close_action_path: [@project, @releasable.model_name.plural] %> +
    + <%= bootstrap_form_with model: @releasable, url: [@releasable, :photos] do |form| %> + <%= render "shared/photos_dropzone_fields", form: form, release: @releasable %> + +
    + <%= link_to t("shared.cancel"), [@project, @releasable.model_name.plural], class: "col-3 text-reset" %> +
    + <%= form.button id: "submit_release", class: "btn btn-block btn-primary", data: { disable_with: t("shared.disable_with") } %> +
    +
    + <% end %> +
    +
    + diff --git a/app/views/profiles/show.html.erb b/app/views/profiles/show.html.erb new file mode 100644 index 0000000..0e989e8 --- /dev/null +++ b/app/views/profiles/show.html.erb @@ -0,0 +1,36 @@ +
    +
    +
    +
    + <%= errors_summary_for @user %> + + <%= bootstrap_form_with model: @user, url: profile_path, local: true do |form| %> +
    + <% if @user.avatar.attached? %> + <%= image_tag(profile_variant(@user.avatar), class: "rounded-circle", alt: "User Avatar") %> + <% else %> + <%= text_avatar(@user, size: 6.25) %> + <% end %> +

    + <% # TODO: Use null object pattern %> + <% if Current.account %> + <%= @user.role_for(Current.account).to_s.titleize %> + <% end %> +

    +
    + +
    + <%= form.file_field :avatar, accept: "image/*", direct_upload: true %> + <%= form.text_field :first_name %> + <%= form.text_field :last_name %> + <%= form.time_zone_select :time_zone, nil, {default: @user.time_zone}, class: "form-control custom-select" %> +
    + +
    + <%= form.submit t("profiles.form.submit"), class: "btn btn-block btn-primary", data: { disable_with: t("shared.disable_with") } %> +
    + <% end %> +
    +
    +
    +
    diff --git a/app/views/project_memberships/_account_manager.html.erb b/app/views/project_memberships/_account_manager.html.erb new file mode 100644 index 0000000..5572258 --- /dev/null +++ b/app/views/project_memberships/_account_manager.html.erb @@ -0,0 +1,10 @@ + + + <%= image_or_text_avatar(account_manager) %> + + <%= account_manager.email %> + <%= account_manager.full_name %> + <%= account_manager.role_for(Current.account).to_s.titleize %> + + + diff --git a/app/views/project_memberships/_form.html.erb b/app/views/project_memberships/_form.html.erb new file mode 100644 index 0000000..859ff75 --- /dev/null +++ b/app/views/project_memberships/_form.html.erb @@ -0,0 +1,3 @@ +<%= bootstrap_form_with model: [project_membership.project, project_membership], html: { autocorrect: :off, autocapitalize: :none, autocomplete: :off, spellcheck: false } do |form| %> + <%= form.email_field :user_email, hide_label: true, append: form.button(fa_icon("paper-plane-o", text: t(".submit")), class: "btn btn-primary"), placeholder: true %> +<% end %> diff --git a/app/views/project_memberships/_project_membership.html.erb b/app/views/project_memberships/_project_membership.html.erb new file mode 100644 index 0000000..18ab642 --- /dev/null +++ b/app/views/project_memberships/_project_membership.html.erb @@ -0,0 +1,13 @@ + + + <%= image_or_text_avatar(project_membership.user) %> + + <%= project_membership.user.email %> + <%= project_membership.user.full_name %> + <%= project_membership.user.role_for(Current.account).to_s.titleize %> + + <% if policy(project_membership).destroy? && project_membership.user != Current.user %> + <%= link_to t(".actions.remove"), project_membership, method: :delete, data: { confirm: t(".confirm") }, class: "btn btn-light btn-sm border" %> + <% end %> + + diff --git a/app/views/project_memberships/index.html.erb b/app/views/project_memberships/index.html.erb new file mode 100644 index 0000000..d8d4a0d --- /dev/null +++ b/app/views/project_memberships/index.html.erb @@ -0,0 +1,33 @@ +
    + <%= card_header text: t(".heading"), subtext: @project.client_name, close_action_path: [@project] %> +
    + <% if policy(ProjectMembership).new? %> +
    +

    <%= t(".invitation.heading") %>

    + <%= render "form", project_membership: @project_membership %> +
    + <% end %> + +
    +

    <%= t(".team_roster.heading", count: @project.members.size) %>

    +
    + + + + + + + + + + + + <%= render partial: "account_manager", collection: @account_managers %> + <%= render @project_memberships %> + +
    EmailNameRole
    +
    +
    +
    +
    + diff --git a/app/views/projects/_empty_projects.html.erb b/app/views/projects/_empty_projects.html.erb new file mode 100644 index 0000000..4721f72 --- /dev/null +++ b/app/views/projects/_empty_projects.html.erb @@ -0,0 +1,12 @@ +
    + <% if policy(Project).new? %> +

    <%= t(".heading") %>

    +

    <%= t(".message") %>

    +

    + <%= link_to fa_icon("plus", text: t(".action")), [:new, :project], class: "btn btn-success" %> +

    + <% else %> +

    <%= t(".heading") %>

    +

    <%= t(".member_message") %>

    + <% end %> +
    diff --git a/app/views/projects/_folder_card.html.erb b/app/views/projects/_folder_card.html.erb new file mode 100644 index 0000000..4ec3831 --- /dev/null +++ b/app/views/projects/_folder_card.html.erb @@ -0,0 +1,9 @@ +
    +
    +
    + <%= fa_icon "folder-open-o", class: "text-muted", style: "font-size:3rem" %> +
    + <%= yield %> +
    +
    +
    diff --git a/app/views/projects/_form.html.erb b/app/views/projects/_form.html.erb new file mode 100644 index 0000000..a8a91d7 --- /dev/null +++ b/app/views/projects/_form.html.erb @@ -0,0 +1,19 @@ +<%= bootstrap_form_with model: project, local: true do |form| %> + <%= form.text_field :name %> + <%= form.select :predefined_client_name, options_for_select(options_for_predefined_client_name_select, selected_project_client_value(project)), {}, data: { toggle: "collapse-select", target: "#other_client", show_values: [:other] }, class: "form-control custom-select" %> +
    + <%= form.text_field :client_name, placeholder: true %> + <%= form.form_group do %> + <%= form.label t(".features_settings.label") %> + <%= form.fields_for :features_settings do |settings_f| %> + <% available_release_types.each do |feature_name| %> + <%= settings_f.check_box feature_name, label: t(".features_settings.#{feature_name}"), checked: project.settings(:features).send(feature_name) %> + <% end %> + <% end %> + <% end %> +
    + <%= form.text_area :description, placeholder: true, rows: 3 %> +
    + <%= form.submit class: class_string("btn btn-block", ["btn-success", "btn-primary"] => project.new_record?), data: { disable_with: t("shared.disable_with") } %> +
    +<% end %> diff --git a/app/views/projects/_project.html.erb b/app/views/projects/_project.html.erb new file mode 100644 index 0000000..6bbd15c --- /dev/null +++ b/app/views/projects/_project.html.erb @@ -0,0 +1,31 @@ +
    +
  • +
    + <%= project.client_name %> + +
    +
    + <%= link_to project, class: "d-block pb-5 text-decoration-none text-reset link-stretched" do %> +

    <%= project.name %>

    + <% end %> +
    + +
  • +
    + diff --git a/app/views/projects/edit.html.erb b/app/views/projects/edit.html.erb new file mode 100644 index 0000000..e3b88de --- /dev/null +++ b/app/views/projects/edit.html.erb @@ -0,0 +1,12 @@ +
    +
    +
    +
    + <%= card_header text: t(".heading"), close_action_path: [:projects] %> +
    + <%= render "form", project: @project %> +
    +
    +
    +
    +
    diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb new file mode 100644 index 0000000..8c5b3e1 --- /dev/null +++ b/app/views/projects/index.html.erb @@ -0,0 +1,20 @@ +<% if @projects.any? %> +
    +

    <%= t(".heading") %> (<%= @projects.size %>)

    +
      + <% if policy(Project).new? %> +
      +
    • +
      + <%= fa_icon("plus-circle", class: "text-success", style: "font-size:4rem") %> + <%= link_to t(".actions.new"), [:new, :project], class: "mt-4 text-reset text-decoration-none stretched-link" %> +
      +
    • +
      + <% end %> + <%= render @projects %> +
    +
    +<% else %> + <%= render "empty_projects" %> +<% end %> diff --git a/app/views/projects/new.html.erb b/app/views/projects/new.html.erb new file mode 100644 index 0000000..e3b88de --- /dev/null +++ b/app/views/projects/new.html.erb @@ -0,0 +1,12 @@ +
    +
    +
    +
    + <%= card_header text: t(".heading"), close_action_path: [:projects] %> +
    + <%= render "form", project: @project %> +
    +
    +
    +
    +
    diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb new file mode 100644 index 0000000..6aac1c8 --- /dev/null +++ b/app/views/projects/show.html.erb @@ -0,0 +1,23 @@ +
    + <% available_release_types_model_names.sort.each do |release_model_name| %> + <% project_feature @project, release_model_name.singular do %> + <%= render "folder_card" do %> + <%= link_to t("projects.show.#{release_model_name.singular}", count: @project.public_send(release_model_name.plural).size), [@project, release_model_name.plural], class: "text-decoration-none text-reset stretched-link" %> + <% end %> + <% end %> + <% end %> + <% if policy(Project).show_reports? %> + <%= render "folder_card" do %> + <%= link_to t("projects.show.report"), [@project, :reports], class: "text-decoration-none text-reset stretched-link" %> + <% end %> + <% end %> + <% if policy(Project).show_downloads? %> + <%= render "folder_card" do %> + <%= link_to t("projects.show.downloads"), [@project, :downloads], class: "text-decoration-none text-reset stretched-link" %> + <% end %> + <% end %> +
    + +
    + +<%= render "directories/directories", directories: @directories %> diff --git a/app/views/public/acquired_media_releases/create.html.erb b/app/views/public/acquired_media_releases/create.html.erb new file mode 100644 index 0000000..6eda1cb --- /dev/null +++ b/app/views/public/acquired_media_releases/create.html.erb @@ -0,0 +1 @@ +

    Your release was successfully submitted. Thank you.

    \ No newline at end of file diff --git a/app/views/public/acquired_media_releases/new.html.erb b/app/views/public/acquired_media_releases/new.html.erb new file mode 100644 index 0000000..2380db0 --- /dev/null +++ b/app/views/public/acquired_media_releases/new.html.erb @@ -0,0 +1,62 @@ +
    +
    + <%= errors_summary_for @acquired_media_release %> + <%= bootstrap_form_with model: [@account, @project, @contract_template, @acquired_media_release], local: true, validation_context: :native do |form| %> + <%= card_field_set_tag t(".legal.heading") do %> +

    <%= @contract_template.body %>

    + <% if @contract_template.fee? %> +

    + Fee <%= number_to_currency @contract_template.fee %> +

    + <% end %> + <% end %> + +
    + + <%= card_field_set_tag t(".acquired_media_info.heading") do %> +
    + <%= form.text_field :name, required: true, wrapper_class: "col-12", label: 'Licensor ("Owner")' %> +
    +
    + <%= form.text_area :description, wrapper_class: "col-12" %> +
    + <%= form.form_group :categories, label: { text: "Categories" } do %> + <% AcquiredMediaRelease::CATEGORIES.each do |category| %> + <%= form.check_box :categories, { multiple: true, label: category }, category, false %> + <% end %> + <% end %> + <% end %> + +
    + + <%= card_field_set_tag t(".personal_info.heading") do %> +
    + <%= form.text_field :person_title, wrapper_class: "col-sm-6" %> + <%= form.text_field :person_phone, wrapper_class: "col-sm-6", label: 'Phone' %> + <%= form.text_field :person_fax, wrapper_class: "col-sm-6", label: 'Fax' %> +
    + <%= render "shared/address_fields", form: form, subject: "person" %> + <% end %> + +
    + + <%= card_field_set_tag t(".files.heading") do %> +
    + <%= fa_icon "warning" %> + For optimal accuracy, please ensure video file names and photo file names match the source file name in the editing sequence. +
    + <%= render "shared/file_infos_dropzone", form: form, releasable: @acquired_media_release %> + <% end %> + +
    + + <%= card_field_set_tag t(".signature.heading") do %> + <%= render "shared/signature_fields", form: form %> + <% end %> + +
    + <%= form.button t("shared.submit_release_long"), class: "btn btn-block btn-lg btn-success", data: { disable_with: t("shared.disable_with") } %> +
    + <% end %> +
    +
    diff --git a/app/views/public/appearance_releases/create.html.erb b/app/views/public/appearance_releases/create.html.erb new file mode 100644 index 0000000..e9384ee --- /dev/null +++ b/app/views/public/appearance_releases/create.html.erb @@ -0,0 +1 @@ +

    Your release was successfully submitted. Thank you.

    diff --git a/app/views/public/appearance_releases/new.html.erb b/app/views/public/appearance_releases/new.html.erb new file mode 100644 index 0000000..9055980 --- /dev/null +++ b/app/views/public/appearance_releases/new.html.erb @@ -0,0 +1,127 @@ +
    +
    + <%= errors_summary_for @appearance_release %> + <%= bootstrap_form_with model: [@account, @project, @contract_template, @appearance_release], local: true, validation_context: :native do |form| %> +
    <%= t ".instructions_html", name: @project.name %>
    + <%= card_field_set_tag t(".legal.heading") do %> +

    <%= @contract_template.body %>

    + <% if @contract_template.fee? %> +

    + Fee <%= number_to_currency @contract_template.fee %> +

    + <% end %> + <% end %> + +
    + + <% unless @contract_template.guardian_clause.blank? %> + <%= form.form_group :minor do %> + <%= form.check_box :minor, label: t("helpers.label.appearance_release.minor"), data: { target: "[data-ujs-target=guardian-fields]", toggle: "collapse" } %> + <% end %> + + <%= card_field_set_tag t(".guardian_clause.heading") do %> +

    <%= @contract_template.guardian_clause %>

    + <% end %> +
    + <% end %> + + <%= card_field_set_tag t(".personal_info.heading") do %> +
    <%= t ".personal_info.instructions" %>
    +
    + <%= form.text_field :person_first_name, required: true, wrapper_class: "col-sm-6" %> + <%= form.text_field :person_last_name, required: true, wrapper_class: "col-sm-6" %> + <%= form.phone_field :person_phone, wrapper_class: "col-sm-6" %> +
    +
    + <%= form.email_field :person_email, wrapper_class: "col-sm-6" %> + <%= form.date_field :person_date_of_birth, wrapper_class: "col-sm-6", placeholder: Date.current %> + <%= form.text_field :person_address, wrapper_class: "col-sm-6" %> +
    + <% end %> + + <%= card_field_set_tag t(".photo.heading") do %> +
    <%= t ".photo.instructions" %>
    +
    +
    +
    + <%= t ".photo.no_photo" %> +
    +
    +
    + <% if @appearance_release.person_photo.attached? %> + <%= javascript_tag nonce: true do %> + App.PhotoPreview.set("[data-behavior=person-photo-preview]", "<%= url_for(@appearance_release.person_photo.variant(auto_orient: true, resize: '200x200')) %>"); + <% end %> + <% end %> +
    + <%= form.hidden_field :person_photo, value: form.object.person_photo.signed_id if @appearance_release.person_photo.attached? %> + <%= form.file_field :person_photo, hide_label: true, data: { ujs_target: "person-photo-input" }, accept: @appearance_release.class.face_photo_acceptable_content_types.join(","), direct_upload: true %> +
    + <%= button_tag t(".photo.take_photo"), type: "button", class: "btn btn-lg btn-primary take-photo-button", data: { behavior: "take-person-photo" } %> +
    +

    + <%= fa_icon "arrow-up", text: t(".photo.camera_instructions_html") %>
    + <%= t ".photo.warning" %> +

    +
    + <% end %> + +
    + + <% unless @contract_template.guardian_clause.blank? %> +
    !@appearance_release.minor?) %>" data-ujs-target="guardian-fields"> + <%= card_field_set_tag t(".guardian_info.heading") do %> +
    + <%= form.text_field :guardian_first_name, required: @appearance_release.minor?, wrapper_class: "col-sm-3" %> + <%= form.text_field :guardian_last_name, required: @appearance_release.minor?, wrapper_class: "col-sm-3" %> + <%= form.phone_field :guardian_phone, wrapper_class: "col-sm-6" %> +
    +
    + <%= form.text_field :guardian_address, wrapper_class: "col-sm-6" %> +
    + <% end %> + +
    + + <%= card_field_set_tag t(".guardian_photo.heading") do %> +
    <%= t ".guardian_photo.instructions" %>
    +
    +
    +
    + <%= t ".photo.no_photo" %> +
    +
    +
    + <% if @appearance_release.guardian_photo.attached? %> + <%= javascript_tag nonce: true do %> + App.PhotoPreview.set("[data-behavior=guardian-photo-preview]", "<%= url_for(@appearance_release.guardian_photo.variant(auto_orient: true, resize: '200x200')) %>"); + <% end %> + <% end %> +
    + <%= form.hidden_field :guardian_photo, value: form.object.guardian_photo.signed_id if @appearance_release.guardian_photo.attached? %> + <%= form.file_field :guardian_photo, hide_label: true, data: { ujs_target: "guardian-photo-input" }, accept: @appearance_release.class.face_photo_acceptable_content_types.join(","), direct_upload: true %> +
    + <%= button_tag t(".photo.take_photo"), type: "button", class: "btn btn-lg btn-primary take-photo-button", data: { behavior: "take-guardian-photo" } %> +
    +

    + <%= fa_icon "arrow-up", text: t(".photo.camera_instructions_html") %>
    + <%= t ".photo.warning" %> +

    +
    + <% end %> + +
    + +
    + <% end %> + + <%= card_field_set_tag t(".signature.heading") do %> + <%= render "shared/signature_fields", form: form %> + <% end %> + +
    + <%= form.button t("shared.submit_release_long"), class: "btn btn-block btn-lg btn-success", data: { disable_with: t("shared.disable_with") } %> +
    + <% end %> +
    +
    diff --git a/app/views/public/broadcasts/_file_form.html.erb b/app/views/public/broadcasts/_file_form.html.erb new file mode 100644 index 0000000..3935c63 --- /dev/null +++ b/app/views/public/broadcasts/_file_form.html.erb @@ -0,0 +1,4 @@ +<%= bootstrap_form_for model, url: broadcast_url(token: token), layout: :inline, remote: true do |form| %> + <%= form.file_field :files, direct_upload: true, multiple: true, accept: "*", hide_label: true, wrapper_class: "w-65 mr-2" %> + <%= form.button fa_icon("upload", text: "Add File"), class: "btn btn-primary", type: :submit, data: { disable_with: fa_icon("spinner", text: "Adding File") } %> +<% end %> \ No newline at end of file diff --git a/app/views/public/broadcasts/update.js.erb b/app/views/public/broadcasts/update.js.erb new file mode 100644 index 0000000..5497ff5 --- /dev/null +++ b/app/views/public/broadcasts/update.js.erb @@ -0,0 +1,9 @@ +$("#broadcast_file_form").html("<%= j render(partial: "public/broadcasts/file_form", locals: { model: [@project, @broadcast], token: @broadcast.token }) %>"); + +var file_id = "#<%= dom_id(@files.first) %>" +if ($("#broadcast_file_list").has(file_id).length == 0) { + $("#broadcast_file_list").html("<%= j render(partial: "broadcasts/file", collection: @files) %>"); + $("#broadcast_files_pagination").html("<%= j will_paginate(@files) %>"); +} + +bsCustomFileInput.init(); \ No newline at end of file diff --git a/app/views/public/location_releases/create.html.erb b/app/views/public/location_releases/create.html.erb new file mode 100644 index 0000000..e9384ee --- /dev/null +++ b/app/views/public/location_releases/create.html.erb @@ -0,0 +1 @@ +

    Your release was successfully submitted. Thank you.

    diff --git a/app/views/public/location_releases/new.html.erb b/app/views/public/location_releases/new.html.erb new file mode 100644 index 0000000..2e3625e --- /dev/null +++ b/app/views/public/location_releases/new.html.erb @@ -0,0 +1,60 @@ +
    +
    + <%= errors_summary_for @location_release %> + <%= bootstrap_form_with model: [@account, @project, @contract_template, @location_release], local: true, validation_context: :native do |form| %> + <%= card_field_set_tag t(".legal.heading") do %> +

    <%= @contract_template.body %>

    + <% if @contract_template.fee? %> +

    + Fee <%= number_to_currency @contract_template.fee %> +

    + <% end %> + <% end %> + +
    + + <%= card_field_set_tag t(".location_info.heading") do %> +
    + <%= form.text_field :name, required: true, wrapper_class: "col-12" %> +
    + <%= render "shared/address_fields", form: form, subject: "" %> + <% end %> + +
    + + <%= card_field_set_tag t(".contact_info.heading") do %> +
    + <%= form.text_field :person_first_name, wrapper_class: "col-sm-6" %> + <%= form.text_field :person_last_name, wrapper_class: "col-sm-6" %> + <%= form.phone_field :person_phone, wrapper_class: "col-sm-6" %> +
    +
    + <%= form.email_field :person_email, wrapper_class: "col-sm-6" %> +
    +
    + <%= form.text_field :person_company, wrapper_class: "col-sm-6" %> + <%= form.text_field :person_title, wrapper_class: "col-sm-6" %> +
    + <%= render "shared/address_fields", form: form, subject: "person" %> + <% end %> + +
    + + + <%= card_field_set_tag t(".filming_info.heading") do %> +
    + <%= form.text_field :filming_started_on, wrapper_class: "col-sm-6", class: "datepicker-control", readonly: true %> + <%= form.text_field :filming_ended_on, wrapper_class: "col-sm-6", class: "datepicker-control", readonly: true %> +
    + <% end %> + + <%= card_field_set_tag t(".signature.heading") do %> + <%= render "shared/signature_fields", form: form, instruction: 'An Authorized Signatory' %> + <% end %> + +
    + <%= form.button t("shared.submit_release_long"), class: "btn btn-block btn-lg btn-success", data: { disable_with: t("shared.disable_with") } %> +
    + <% end %> +
    +
    diff --git a/app/views/public/material_releases/create.html.erb b/app/views/public/material_releases/create.html.erb new file mode 100644 index 0000000..e9384ee --- /dev/null +++ b/app/views/public/material_releases/create.html.erb @@ -0,0 +1 @@ +

    Your release was successfully submitted. Thank you.

    diff --git a/app/views/public/material_releases/new.html.erb b/app/views/public/material_releases/new.html.erb new file mode 100644 index 0000000..9396ddd --- /dev/null +++ b/app/views/public/material_releases/new.html.erb @@ -0,0 +1,52 @@ +
    +
    + <%= errors_summary_for @material_release %> + <%= bootstrap_form_with model: [@account, @project, @contract_template, @material_release], local: true, validation_context: :native do |form| %> + <%= card_field_set_tag t(".legal.heading") do %> +

    <%= @contract_template.body %>

    + <% if @contract_template.fee? %> +

    + Fee <%= number_to_currency @contract_template.fee %> +

    + <% end %> + <% end %> + +
    + + <%= card_field_set_tag t(".release_info.heading") do %> +
    + <%= form.text_field :name, required: true, wrapper_class: "col-12" %> +
    +
    + <%= form.text_area :description, placeholder: true, wrapper_class: "col-sm-12", rows: 6 %> +
    + <% end %> + + <%= card_field_set_tag t(".contact_info.heading") do %> +
    + <%= form.text_field :person_first_name, wrapper_class: "col-sm-6" %> + <%= form.text_field :person_last_name, wrapper_class: "col-sm-6" %> + <%= form.phone_field :person_phone, wrapper_class: "col-sm-6" %> +
    +
    + <%= form.email_field :person_email, wrapper_class: "col-sm-6" %> +
    +
    + <%= form.text_field :person_company, wrapper_class: "col-sm-6" %> + <%= form.text_field :person_title, wrapper_class: "col-sm-6" %> +
    + <%= render "shared/address_fields", form: form, subject: "person" %> + <% end %> + +
    + + <%= card_field_set_tag t(".signature.heading") do %> + <%= render "shared/signature_fields", form: form, instruction: 'For Owner or Authorized Signatory' %> + <% end %> + +
    + <%= form.button t("shared.submit_release_long"), class: "btn btn-block btn-lg btn-success", data: { disable_with: t("shared.disable_with") } %> +
    + <% end %> +
    +
    diff --git a/app/views/public/talent_releases/create.html.erb b/app/views/public/talent_releases/create.html.erb new file mode 100644 index 0000000..e9384ee --- /dev/null +++ b/app/views/public/talent_releases/create.html.erb @@ -0,0 +1 @@ +

    Your release was successfully submitted. Thank you.

    diff --git a/app/views/public/talent_releases/new.html.erb b/app/views/public/talent_releases/new.html.erb new file mode 100644 index 0000000..17c5239 --- /dev/null +++ b/app/views/public/talent_releases/new.html.erb @@ -0,0 +1,104 @@ +
    +
    + <%= errors_summary_for @talent_release %> + <%= bootstrap_form_with model: [@account, @project, @contract_template, @talent_release], local: true, validation_context: :native do |form| %> +
    <%= t ".instructions_html", name: @project.name %>
    + <%= card_field_set_tag t(".legal.heading") do %> +

    <%= @contract_template.body %>

    + <% if @contract_template.fee? %> +

    + Fee <%= number_to_currency @contract_template.fee %> +

    + <% end %> + <% end %> + +
    + + <% unless @contract_template.guardian_clause.blank? %> + <%= form.form_group :minor do %> + <%= form.check_box :minor, label: t("helpers.label.appearance_release.minor"), data: { target: "[data-ujs-target=guardian-fields]", toggle: "collapse" } %> + <% end %> + + <%= card_field_set_tag t(".guardian_clause.heading") do %> +

    <%= @contract_template.guardian_clause %>

    + <% end %> +
    + <% end %> + + <%= card_field_set_tag t(".personal_info.heading") do %> +
    <%= t ".personal_info.instructions" %>
    +
    + <%= form.text_field :person_first_name, required: true, wrapper_class: "col-sm-6" %> + <%= form.text_field :person_last_name, required: true, wrapper_class: "col-sm-6" %> + <%= form.phone_field :person_phone, wrapper_class: "col-sm-6" %> +
    +
    + <%= form.email_field :person_email, wrapper_class: "col-sm-6" %> +
    + <%= render "shared/address_fields", form: form, subject: "person" %> + <% end %> + +
    + + <%= card_field_set_tag t(".photo.heading") do %> + <%= render "shared/photos_dropzone_fields", form: form, release: @talent_release %> + <% end %> + + <% unless @contract_template.guardian_clause.blank? %> +
    !@talent_release.minor?) %>" data-ujs-target="guardian-fields"> + <%= card_field_set_tag t(".guardian_info.heading") do %> +
    + <%= form.text_field :guardian_first_name, required: @talent_release.minor?, wrapper_class: "col-sm-3" %> + <%= form.text_field :guardian_last_name, required: @talent_release.minor?, wrapper_class: "col-sm-3" %> + <%= form.phone_field :guardian_phone, wrapper_class: "col-sm-6" %> +
    +
    + <%= form.text_field :guardian_email, wrapper_class: "col-sm-6" %> +
    + <%= render "shared/address_fields", form: form, subject: "guardian" %> + <% end %> + +
    + + <%= card_field_set_tag t(".guardian_photo.heading") do %> +
    <%= t ".guardian_photo.instructions" %>
    +
    +
    +
    + <%= t ".guardian_photo.no_photo" %> +
    +
    +
    + <% if @talent_release.guardian_photo.attached? %> + <%= javascript_tag nonce: true do %> + App.PhotoPreview.set("[data-behavior=guardian-photo-preview]", "<%= url_for(@talent_release.guardian_photo.variant(auto_orient: true, resize: '200x200')) %>"); + <% end %> + <% end %> +
    + <%= form.hidden_field :guardian_photo, value: form.object.guardian_photo.signed_id if @talent_release.guardian_photo.attached? %> + <%= form.file_field :guardian_photo, hide_label: true, data: { ujs_target: "guardian-photo-input" }, accept: @talent_release.class.face_photo_acceptable_content_types.join(","), direct_upload: true %> +
    + <%= button_tag t(".guardian_photo.take_photo"), type: "button", class: "btn btn-lg btn-primary take-photo-button", data: { behavior: "take-guardian-photo" } %> +
    +

    + <%= fa_icon "arrow-up", text: t(".guardian_photo.camera_instructions_html") %>
    + <%= t ".guardian_photo.warning" %> +

    +
    + <% end %> + + +
    +
    + <% end %> + + <%= card_field_set_tag t(".signature.heading") do %> + <%= render "shared/signature_fields", form: form %> + <% end %> + +
    + <%= form.button t("shared.submit_release_long"), class: "btn btn-block btn-lg btn-success", data: { disable_with: t("shared.disable_with") } %> +
    + <% end %> +
    +
    \ No newline at end of file diff --git a/app/views/release_template_imports/_release_template.html.erb b/app/views/release_template_imports/_release_template.html.erb new file mode 100644 index 0000000..622c77f --- /dev/null +++ b/app/views/release_template_imports/_release_template.html.erb @@ -0,0 +1,20 @@ +<% template, imported = release_template.template, release_template.already_imported? %> + + imported) %>"> + + <% if imported %> +
    <%= fa_icon('ban') %>
    + <% else %> + <%= check_box_tag "template_ids[]", template.id, false %> + <% end %> + + + <%= template.name %> + + + <%= release_type_title(template.release_type) %> + + + <%= template.project.name %> + + diff --git a/app/views/release_template_imports/new.html.erb b/app/views/release_template_imports/new.html.erb new file mode 100644 index 0000000..9ca5089 --- /dev/null +++ b/app/views/release_template_imports/new.html.erb @@ -0,0 +1,44 @@ +<%= product_wordmark :release_me, class: "small mb-3" %> + +
    + <%= bootstrap_form_with url: new_project_release_template_imports_path, project: @project, method: :get, remote: true, layout: :inline, id: "search" do |form| %> + <%= form.search_field :query, hide_label: true, placeholder: t(".actions.search"), class: "rounded-pill-right", value: params[:query], prepend: form.button(fa_icon("search"), id: "search-button", class: "btn btn-light border rounded-pill-left") %> + <% end%> +
    + +<%= bootstrap_form_with url: project_release_template_imports_path, project: @project do |form| %> +
    + + + + + + + + + + + <% if @importable_templates.any? %> + <%= render collection: @importable_templates, partial: 'release_template_imports/release_template' %> + <% else %> + + + + <% end %> + +
    <%= t(".table_headers.selection") %><%= ContractTemplate.human_attribute_name(:name) %><%= t(".table_headers.release_type") %><%= ContractTemplate.human_attribute_name(:project) %>
    <%= t(".empty") %>
    + +
    + <%= link_to t("shared.cancel"), [@project, :contract_templates], class: "col-3 text-reset" %> + <% if @importable_templates.any? { |it| !it.already_imported? } %> +
    + <%= form.submit t(".actions.import"), class: "btn btn-success btn-block" %> +
    + <% end %> +
    +
    + +
    + <%= will_paginate @importable_templates %> +
    +<% end %> diff --git a/app/views/release_template_imports/new.js.erb b/app/views/release_template_imports/new.js.erb new file mode 100644 index 0000000..04be794 --- /dev/null +++ b/app/views/release_template_imports/new.js.erb @@ -0,0 +1,3 @@ +$("#contract-templates").html("<%= j render collection: @importable_templates, partial: 'release_template_imports/release_template' %>"); +$("form input[type='search']").val("<%= params[:query] %>"); +$("#importable_templates_pagination").html("<%= j will_paginate @importable_templates %>"); diff --git a/app/views/reports/_report.html.erb b/app/views/reports/_report.html.erb new file mode 100644 index 0000000..1f03c71 --- /dev/null +++ b/app/views/reports/_report.html.erb @@ -0,0 +1,19 @@ + + + <%= report.video_name %> + + + <%= report.video_number %> + + + <%= report.name %> + + + <% if report.published_at %> + <%= time_ago_in_words(report.published_at) %> ago + <% end %> + + + <%= link_to t(".actions.download"), report.url, class: "btn btn-sm btn-primary" %> + + diff --git a/app/views/reports/index.html.erb b/app/views/reports/index.html.erb new file mode 100644 index 0000000..5e431c5 --- /dev/null +++ b/app/views/reports/index.html.erb @@ -0,0 +1,34 @@ +
    + <%= link_to "Download All Reports", [@project, :report_downloads], method: :post, remote: true, class: "btn btn-light border" %> + + <%= bootstrap_form_with url: [@project, :reports], method: :get, remote: true, layout: :inline, id: "search" do |form| %> + <%= form.search_field :query, hide_label: true, placeholder: t("shared.search"), class: "rounded-pill-right", value: params[:query], prepend: form.button(fa_icon("search"), id: "search-button", class: "btn btn-light border rounded-pill-left") %> + <% end %> +
    + +
    + + + + + + + + + + + + <% if @reports.any? %> + <%= render @reports %> + <% else %> + + + + <% end %> + +
    <%= t(".table_headers.video_name") %><%= t(".table_headers.video_number") %><%= t(".table_headers.report_name") %><%= t(".table_headers.report_published_at") %>
    <%= t(".empty") %>
    +
    + +
    + <%= will_paginate @reports %> +
    diff --git a/app/views/reports/index.js.erb b/app/views/reports/index.js.erb new file mode 100644 index 0000000..8f1523c --- /dev/null +++ b/app/views/reports/index.js.erb @@ -0,0 +1,3 @@ +$("#reports").html("<%= j render(@reports) %>"); +$("form input[type='search']").val("<%= params[:query] %>"); +$("#reports_pagination").html("<%= j will_paginate @reports %>"); \ No newline at end of file diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb new file mode 100644 index 0000000..3cad16e --- /dev/null +++ b/app/views/sessions/new.html.erb @@ -0,0 +1,13 @@ +<% content_for :page_title, t(".title") %> + +
    + <%= bootstrap_form_with scope: :session, url: session_path, local: true, html: { autocorrect: :off, autocapitalize: :none, autocomplete: :off, spellcheck: false } do |form| %> + <%= form.text_field :email, required: true %> + <%= form.password_field :password, required: true %> + <%= form.check_box "remember_me", name: "remember_me", wrapper_class: "mb-3" %> + <%= form.button t(".submit"), class: "btn btn-block btn-lg btn-primary" %> +
    + <%= link_to t(".forgot_password"), [:new, :password_reset] %> +
    + <% end %> +
    diff --git a/app/views/shared/_address_fields.html.erb b/app/views/shared/_address_fields.html.erb new file mode 100644 index 0000000..56c08ee --- /dev/null +++ b/app/views/shared/_address_fields.html.erb @@ -0,0 +1,16 @@ +<% field_name_prefix = subject.present? ? "#{subject}_" : "" %> + +
    + <%= form.text_field "#{field_name_prefix}address_street1", wrapper_class: "col-sm-6" %> + <%= form.text_field "#{field_name_prefix}address_street2", wrapper_class: "col-sm-6" %> +
    +
    + <%= form.text_field "#{field_name_prefix}address_city", wrapper_class: "col-sm-6" %> + <%= form.text_field "#{field_name_prefix}address_state", wrapper_class: "col-sm-3" %> + <%= form.text_field "#{field_name_prefix}address_zip", wrapper_class: "col-sm-3" %> +
    +<%= form.form_group "#{field_name_prefix}address_country" do %> + <%= form.label "#{field_name_prefix}address_country" %> + <%= form.country_select "#{field_name_prefix}address_country", { priority: %w(US CA), prompt: true }, class: "form-control custom-select" %> +<% end %> + diff --git a/app/views/shared/_contract_fields.html.erb b/app/views/shared/_contract_fields.html.erb new file mode 100644 index 0000000..1a9fca4 --- /dev/null +++ b/app/views/shared/_contract_fields.html.erb @@ -0,0 +1,12 @@ +
    + <%= form.file_field :contract, wrapper_class: "col-sm-6 required", accept: "application/pdf", direct_upload: true %> + <%= form.hidden_field :contract, value: form.object.contract.signed_id if release.contract.attached? %> +
    +<% if release.contract.attached? %> +

    + <%= link_to release.contract do %> + <%= fa_icon "file-text-o" %> <%= release.contract.filename %> + <% end %> + <%= fa_icon "long-arrow-left" %> Current contract +

    +<% end %> diff --git a/app/views/shared/_exploitable_rights_fields.html.erb b/app/views/shared/_exploitable_rights_fields.html.erb new file mode 100644 index 0000000..d8563b2 --- /dev/null +++ b/app/views/shared/_exploitable_rights_fields.html.erb @@ -0,0 +1,23 @@ +
    +
    + <%= form.collection_select :applicable_medium_id, ApplicableMedium.all.reverse, :id, :label, { label: "Applicable Media", include_blank: true }, class: "form-control custom-select" %> + <%= form.text_field :applicable_medium_text, hide_label: true, placeholder: "Describe other applicable media" %> +
    + +
    + <%= form.collection_select :territory_id, Territory.all.reverse, :id, :label, { include_blank: true }, class: "form-control custom-select" %> + <%= form.text_field :territory_text, hide_label: true, placeholder: "Describe other territory" %> +
    +
    + +
    +
    + <%= form.collection_select :term_id, Term.all.reverse, :id, :label, { include_blank: true }, class: "form-control custom-select" %> + <%= form.text_field :term_text, hide_label: true, placeholder: "Describe other term" %> +
    + +
    + <%= form.collection_select :restriction_id, Restriction.all.reverse, :id, :label, { include_blank: true }, class: "form-control custom-select" %> + <%= form.text_field :restriction_text, hide_label: true, placeholder: "Describe other restrictions" %> +
    +
    diff --git a/app/views/shared/_file_infos_dropzone.html.erb b/app/views/shared/_file_infos_dropzone.html.erb new file mode 100644 index 0000000..1f7d695 --- /dev/null +++ b/app/views/shared/_file_infos_dropzone.html.erb @@ -0,0 +1,18 @@ +
    + <%= form.fields_for :file_infos do |file_info_form| %> + <% unless file_info_form.object.persisted? %> + <%= file_info_form.hidden_field :filename, value: file_info_form.object.filename %> + <%= file_info_form.hidden_field :byte_size, value: file_info_form.object.byte_size %> + <%= file_info_form.hidden_field :content_type, value: file_info_form.object.content_type %> + <% end %> + <% end %> +
    +
    +
    + diff --git a/app/views/shared/_files_dropzone_fields.html.erb b/app/views/shared/_files_dropzone_fields.html.erb new file mode 100644 index 0000000..faf6035 --- /dev/null +++ b/app/views/shared/_files_dropzone_fields.html.erb @@ -0,0 +1,17 @@ +
    + <%= form.label :files %> + <%= form.file_field :files, disable: true, direct_upload: true, multiple: true, id: "directory_files", hide_label: true %> + <% directory.files.each do |file| %> + <% unless file.persisted? %> + <%= hidden_field_tag "#{directory.model_name.param_key}[files][]", file.signed_id %> + <% end %> + <% end %> +
    + +
    \ No newline at end of file diff --git a/app/views/shared/_photos_dropzone_fields.html.erb b/app/views/shared/_photos_dropzone_fields.html.erb new file mode 100644 index 0000000..203bc2e --- /dev/null +++ b/app/views/shared/_photos_dropzone_fields.html.erb @@ -0,0 +1,17 @@ +
    + <%= form.label :photos %> + <%= form.file_field :photos, disable: true, direct_upload: true, multiple: true, id: "release_photos", hide_label: true %> + <% release.photos.each do |photo| %> + <% unless photo.persisted? %> + <%= hidden_field_tag "#{release.model_name.param_key}[photos][]", photo.signed_id %> + <% end %> + <% end %> +
    +
    +
    diff --git a/app/views/shared/_signature_fields.html.erb b/app/views/shared/_signature_fields.html.erb new file mode 100644 index 0000000..620274a --- /dev/null +++ b/app/views/shared/_signature_fields.html.erb @@ -0,0 +1,15 @@ + + +<% if local_assigns[:instruction] %> + + <%= instruction %> + +<% end %> + +<%= form.hidden_field :signature_base64, data: { ujs_target: "signature-input" } %> +
    + <%= button_tag class: "btn btn-sm btn-danger", data: { behavior: "clear-digital-signature" } do %> + <%= fa_icon "refresh" %> <%= t "shared.clear" %> + <% end %> +
    + diff --git a/app/views/tags/_form.html.erb b/app/views/tags/_form.html.erb new file mode 100644 index 0000000..1e5e26a --- /dev/null +++ b/app/views/tags/_form.html.erb @@ -0,0 +1,39 @@ +<%= bootstrap_form_with model: [releasable, tag] do |form| %> + +<% end %> \ No newline at end of file diff --git a/app/views/tags/_new_tag_modal.html.erb b/app/views/tags/_new_tag_modal.html.erb new file mode 100644 index 0000000..df5a91e --- /dev/null +++ b/app/views/tags/_new_tag_modal.html.erb @@ -0,0 +1,3 @@ + diff --git a/app/views/tags/create.js.erb b/app/views/tags/create.js.erb new file mode 100644 index 0000000..d26554e --- /dev/null +++ b/app/views/tags/create.js.erb @@ -0,0 +1,6 @@ +// Refresh the tag preview for the releasable in the table +$("#<%= dom_id(@releasable, "tags_preview") %>").html("<%= j tags_preview(@releasable, @releasable.tags) %>"); + +// Refresh the modal +var $modal = $("#<%= dom_id @releasable.tags.new, dom_id(@releasable) %>") +$modal.html("<%= j render(partial: "form", locals: { releasable: @releasable, tag: @releasable.tags.new }) %>") diff --git a/app/views/tags/destroy.js.erb b/app/views/tags/destroy.js.erb new file mode 100644 index 0000000..3d50709 --- /dev/null +++ b/app/views/tags/destroy.js.erb @@ -0,0 +1,5 @@ +// Refresh the tag preview for the releasable in the table +$("#<%= dom_id(@releasable, "tags_preview") %>").html("<%= j tags_preview(@releasable, @releasable.reload.tags) %>"); + +// Refresh the modal +$("#<%= dom_id @releasable.tags.new, dom_id(@releasable) %> .tag-list tr#<%= dom_id(@tag) %>").remove(); diff --git a/app/views/tags/new.js.erb b/app/views/tags/new.js.erb new file mode 100644 index 0000000..e555948 --- /dev/null +++ b/app/views/tags/new.js.erb @@ -0,0 +1,11 @@ +var modalSelector = "#<%= dom_id @tag, dom_id(@releasable) %>" +var modal = "<%= j render("new_tag_modal", releasable: @releasable, tag: @tag) %>" + +// If the modal already exists, replace it. Otherwise, create it +if ($(modalSelector).length > 0) { + $(modalSelector).replaceWith(modal) +} else { + $('body').append(modal) +} + +$(modalSelector).modal("toggle") \ No newline at end of file diff --git a/app/views/talent_releases/_form.html.erb b/app/views/talent_releases/_form.html.erb new file mode 100644 index 0000000..76fbff9 --- /dev/null +++ b/app/views/talent_releases/_form.html.erb @@ -0,0 +1,72 @@ +<%= errors_summary_for talent_release %> +<%= bootstrap_form_with model: model, local: true do |form| %> + <%= field_set_tag content_tag(:span, t(".talent_details.heading"), class: "h6 text-muted text-uppercase") do %> + <%= form.form_group :minor do %> + <%= form.check_box :minor, label: t("helpers.label.talent_release.minor"), data: { target: "[data-ujs-target=guardian-fields]", toggle: "collapse" } %> + <% end %> + +
    + <%= form.text_field :person_first_name, required: true, wrapper_class: "col-sm-3" %> + <%= form.text_field :person_last_name, required: true, wrapper_class: "col-sm-3" %> + <%= form.phone_field :person_phone, wrapper_class: "col-sm-6" %> +
    +
    + <%= form.email_field :person_email, wrapper_class: "col-sm-6" %> +
    + <%= render "shared/address_fields", form: form, subject: "person" %> + +
    !talent_release.minor?) %>" data-ujs-target="guardian-fields"> +
    + <%= form.text_field :guardian_first_name, required: talent_release.minor?, wrapper_class: "col-sm-3" %> + <%= form.text_field :guardian_last_name, required: talent_release.minor?, wrapper_class: "col-sm-3" %> + <%= form.phone_field :guardian_phone, wrapper_class: "col-sm-6" %> +
    +
    + <%= form.text_field :guardian_email, wrapper_class: "col-sm-6" %> +
    + <%= render "shared/address_fields", form: form, subject: "guardian" %> +
    + <% end %> + +
    + + <%= field_set_tag content_tag(:span, t(".contract_and_rights.heading"), class: "h6 text-muted text-uppercase") do %> + <%= render "shared/contract_fields", form: form, release: talent_release %> + <%= render "shared/exploitable_rights_fields", form: form %> + <% end %> + +
    + + <%= field_set_tag content_tag(:span, t(".photos.heading"), class: "h6 text-muted text-uppercase") do %> + <%= render "shared/photos_dropzone_fields", form: form, release: talent_release %> + +
    !talent_release.minor?) %>" data-ujs-target="guardian-fields"> +
    +
    +

    <%= t(".photos.guardian_photo.heading") %>

    +
    +
    + No photo yet +
    +
    + <% if talent_release.guardian_photo.attached? %> + <%= javascript_tag nonce: true do %> + App.PhotoPreview.set("[data-behavior=guardian-photo-preview]", "<%= url_for(talent_release.guardian_photo.variant(auto_orient: true, resize: '200x200')) %>"); + <% end %> + <% end %> +
    + <%= form.hidden_field :guardian_photo, value: form.object.guardian_photo.signed_id if talent_release.guardian_photo.attached?%> + <%= form.file_field :guardian_photo, hide_label: true, data: { ujs_target: "guardian-photo-input" }, help: "PNG or JPG only", accept: talent_release.class.face_photo_acceptable_content_types.join(",") %> +
    +
    +
    +
    + <% end %> + +
    + <%= link_to t("shared.cancel"), [talent_release.project, :talent_releases], class: "col-3 text-reset" %> +
    + <%= form.button id: "submit_release", class: class_string("btn btn-block", ["btn-success", "btn-primary"] => talent_release.new_record?), data: { disable_with: t("shared.disable_with") } %> +
    +
    +<% end %> diff --git a/app/views/talent_releases/_talent_release.html.erb b/app/views/talent_releases/_talent_release.html.erb new file mode 100644 index 0000000..a729f6b --- /dev/null +++ b/app/views/talent_releases/_talent_release.html.erb @@ -0,0 +1,55 @@ + + <%= check_box_tag "talent_release_ids[]", talent_release.id, false %> + + <% if talent_release.photo.attached? %> + <%= image_tag medium_variant(talent_release.photo), class: "img-fluid" %> + <% end %> + + + <%= talent_release.name %> + + + <%= number_to_phone talent_release.person_phone %> + + + <%= mail_to talent_release.person_email %> + + + <%= notes_preview talent_release.notes.order_by_recent %> + + "> + <%= tags_preview talent_release, talent_release.tags %> + + + + <%= talent_release.signed_on %> + + + + +
    + <%= button_tag t(".actions.manage"), class: "btn btn-light btn-sm dropdown-toggle border", data: { toggle: "dropdown", boundary: "window" }, aria: { haspopup: true, expanded: false } %> + +
    + + + diff --git a/app/views/talent_releases/edit.html.erb b/app/views/talent_releases/edit.html.erb new file mode 100644 index 0000000..bcd6fca --- /dev/null +++ b/app/views/talent_releases/edit.html.erb @@ -0,0 +1,6 @@ +
    + <%= card_header text: t(".heading"), close_action_path: [@project, :talent_releases] %> +
    + <%= render "form", model: @talent_release, talent_release: @talent_release %> +
    +
    diff --git a/app/views/talent_releases/index.html.erb b/app/views/talent_releases/index.html.erb new file mode 100644 index 0000000..e47dc9b --- /dev/null +++ b/app/views/talent_releases/index.html.erb @@ -0,0 +1,54 @@ +
    +
    +
    + <% if policy(TalentRelease).new? %> + <%= link_to fa_icon("plus", text: t(".actions.new")), [:new, @project, :talent_release], class: "btn btn-primary mr-2 mb-2" %> + <% end %> + + <% if @talent_releases.any? && policy(TalentRelease).tag_multiple? %> + <%= button_to_bulk_tagging(@project) %> + <% end %> + + <% if @talent_releases.any? && policy(TalentRelease).download_multiple? %> + <%= link_to "Download All", [@project, :contract_downloads, release_type: @talent_releases.name], method: :post, remote: true, class: "btn btn-light border ml-auto mr-2 mb-2", data: { + disable_with: "Please wait..." } %> + <% end %> + + <%= bootstrap_form_with url: [@project, :talent_releases], method: :get, remote: true, layout: :inline, id: "search" do |form| %> + <%= form.search_field :query, hide_label: true, placeholder: t(".actions.search"), class: "rounded-pill-right", value: params[:query], prepend: form.button(fa_icon("search"), class: "btn btn-light border mb-2 rounded-pill-left") %> + <% end %> +
    +
    +
    + +
    + + + + + + + + + + + + + + + + + <% if @talent_releases.any? %> + <%= render @talent_releases %> + <% else %> + + + + <% end %> + +
    <%= check_box_tag "talent_release_ids[]", false, false %><%= TalentRelease.human_attribute_name(:person_name) %><%= TalentRelease.human_attribute_name(:person_phone) %><%= TalentRelease.human_attribute_name(:person_email) %><%= t(".table_headers.notes") %><%= t(".table_headers.tags") %><%= t(".table_headers.signed_at") %>
    <%= t(".empty") %>
    +
    + +
    + <%= will_paginate @talent_releases %> +
    diff --git a/app/views/talent_releases/index.js.erb b/app/views/talent_releases/index.js.erb new file mode 100644 index 0000000..e396385 --- /dev/null +++ b/app/views/talent_releases/index.js.erb @@ -0,0 +1,3 @@ +$("#talent_releases").html("<%= j render(@talent_releases) %>"); +$("form input[type='search']").val("<%= params[:query] %>"); +$("#talent_releases_pagination").html("<%= j will_paginate(@talent_releases) %>"); diff --git a/app/views/talent_releases/new.html.erb b/app/views/talent_releases/new.html.erb new file mode 100644 index 0000000..0416b86 --- /dev/null +++ b/app/views/talent_releases/new.html.erb @@ -0,0 +1,6 @@ +
    + <%= card_header text: t(".heading"), close_action_path: [@project, :talent_releases] %> +
    + <%= render "form", model: [@project, @talent_release], talent_release: @talent_release %> +
    +
    diff --git a/app/views/user_mailer/existing_account.html.erb b/app/views/user_mailer/existing_account.html.erb new file mode 100644 index 0000000..69254f1 --- /dev/null +++ b/app/views/user_mailer/existing_account.html.erb @@ -0,0 +1,13 @@ +

    +Hi! +

    + +

    +You have been added as an Account Manager for <%= @account.name %>.

    +

    To switch between accounts, tap the account name at the top of the page, and select the appropriate account in the drop-down. +

    + + +

    + - BiG Team +

    diff --git a/app/views/user_mailer/existing_account.text.erb b/app/views/user_mailer/existing_account.text.erb new file mode 100644 index 0000000..5d5f50e --- /dev/null +++ b/app/views/user_mailer/existing_account.text.erb @@ -0,0 +1,6 @@ +Hi! + +You have been added as an Account Manager for <%= @account.name %> To switch between accounts, tap the account name at the top of the page, and select the appropriate account in the drop-down. + +- BiG Team + diff --git a/app/views/user_mailer/password_reset.html.erb b/app/views/user_mailer/password_reset.html.erb new file mode 100644 index 0000000..6fdfbbe --- /dev/null +++ b/app/views/user_mailer/password_reset.html.erb @@ -0,0 +1,15 @@ +

    + Hello, +

    + +

    + To reset your password, please click on the following link: +

    + +

    + <%= link_to "Reset Password", edit_password_reset_url(id: @user.password_reset_token, host: AppHost.new.domain_with_port) %> +

    + +

    + - BiG Team +

    diff --git a/app/views/user_mailer/password_reset.text.erb b/app/views/user_mailer/password_reset.text.erb new file mode 100644 index 0000000..ce30dd8 --- /dev/null +++ b/app/views/user_mailer/password_reset.text.erb @@ -0,0 +1,7 @@ +Hello, + +To reset your password, please paste the following link into your browser: + +<%= edit_password_reset_url(id: @user.password_reset_token, host: AppHost.new.domain_with_port) %> + +- BiG Team diff --git a/app/views/user_mailer/project_invitation.html.erb b/app/views/user_mailer/project_invitation.html.erb new file mode 100644 index 0000000..e17fd22 --- /dev/null +++ b/app/views/user_mailer/project_invitation.html.erb @@ -0,0 +1,14 @@ +

    + Welcome to the ME Suite. +

    + +

    + <%= @project.account.name %> has invited you to <%= @project.name %>. + <% if @user_is_new %> + Please <%= link_to "click here", edit_password_reset_url(id: @user.password_reset_token, host: AppHost.new.domain_with_port) %> to set your password. + <% end %> +

    + +

    + If you have questions about how to use the software, please visit <%= link_to "BiGMedia.ai", "https://www.bigmedia.ai/contact" %>. +

    diff --git a/app/views/user_mailer/project_invitation.text.erb b/app/views/user_mailer/project_invitation.text.erb new file mode 100644 index 0000000..217fbd7 --- /dev/null +++ b/app/views/user_mailer/project_invitation.text.erb @@ -0,0 +1,10 @@ +Welcome to the ME Suite. + +<%= @project.account.name %> has invited you to <%= @project.name %>. + +<% if @user_is_new %> +Please click below to set your password. +<%= edit_password_reset_url(id: @user.password_reset_token, host: AppHost.new.domain_with_port) %> +<% end %> + +If you have questions about how to use the software, please visit https://www.bigmedia.ai/contact. diff --git a/app/views/user_mailer/welcome.html.erb b/app/views/user_mailer/welcome.html.erb new file mode 100644 index 0000000..b8c78e7 --- /dev/null +++ b/app/views/user_mailer/welcome.html.erb @@ -0,0 +1,15 @@ +

    + Welcome to BiG! +

    + +

    + You have been added to the <%= @account.name %> account. +

    + +

    + To access your account, <%= link_to "click here", edit_password_reset_url(id: @user.password_reset_token, host: AppHost.new.domain_with_port) %> and set your password. Then use your email and new password to sign in. +

    + +

    + - BiG Team +

    diff --git a/app/views/user_mailer/welcome.text.erb b/app/views/user_mailer/welcome.text.erb new file mode 100644 index 0000000..c6cd314 --- /dev/null +++ b/app/views/user_mailer/welcome.text.erb @@ -0,0 +1,9 @@ +Welcome to BiG! + +You have been added to the "<%= @account.name %>" account. + +To access your account click the following URL and set your password. Then use your email and new password to sign in. + +<%= edit_password_reset_url(id: @user.password_reset_token, host: AppHost.new.domain_with_port) %> + +- BiG Team diff --git a/app/views/video_analyses/_acquired_media_file_infos.html.erb b/app/views/video_analyses/_acquired_media_file_infos.html.erb new file mode 100644 index 0000000..a9e4891 --- /dev/null +++ b/app/views/video_analyses/_acquired_media_file_infos.html.erb @@ -0,0 +1,18 @@ +<% if acquired_media_file_infos.any? %> + <% acquired_media_file_infos.each do |file_info| %> +
  • + <%= render "video_analyses/file_info_releasable", releasable: file_info.releasable, video: video, file_info: file_info %> +
  • + <% end %> +<% else %> +
  • +
    + +
    + No File Uploaded +
    + +
    No releases found
    +
    +
  • +<% end %> diff --git a/app/views/video_analyses/_acquired_media_release.html.erb b/app/views/video_analyses/_acquired_media_release.html.erb new file mode 100644 index 0000000..cf46b6f --- /dev/null +++ b/app/views/video_analyses/_acquired_media_release.html.erb @@ -0,0 +1,18 @@ +<% if acquired_media_release.file_infos.any? %> + <% acquired_media_release.file_infos.each do |file_info| %> +
  • " data-confirmed="<%= video.has_confirmed_release?(acquired_media_release) && video.video_release_confirmations.any? { |video_release_confirmation| video_release_confirmation.file_info_id == file_info.id } %>" data-hidden="false"> + <%= render "video_analyses/file_info_releasable", releasable: acquired_media_release, video: video, file_info: file_info %> +
  • + <% end %> +<% else %> +
  • +
    + +
    + No File Uploaded +
    + +
    <%= truncate acquired_media_release.name, length: 25 %>
    +
    +
  • +<% end %> diff --git a/app/views/video_analyses/_actions.html.erb b/app/views/video_analyses/_actions.html.erb new file mode 100644 index 0000000..feb2b26 --- /dev/null +++ b/app/views/video_analyses/_actions.html.erb @@ -0,0 +1,33 @@ +
    +
    +
    + <%= button_to [:new, video, :bookmark], class: "btn btn-sm btn-primary", data: { toggle: "tooltip", disable_with: fa_icon("spinner spin") }, form: { data: { ujs_target: "bookmark-form" } }, params: { bookmark: { time_elapsed: "" } }, method: :get, remote: true, title: t(".create_bookmark") do %> + <%= fa_icon "bookmark" %> + <% end %> +
    +
    + <%= button_to new_video_video_analyses_unreleased_appearance_path(video), class: "btn btn-sm btn-danger", data: { toggle: "tooltip", disable_with: fa_icon("spinner spin") }, form: { data: { ujs_target: "unreleased-appearance-form" } }, params: { unreleased_appearance: { time_elapsed: "" } }, method: :get, remote: true, title: t(".create_unreleased_appearance") do %> + <%= fa_icon "warning" %> + <% end %> +
    +
    + <%= button_to new_video_video_analyses_graphics_element_path(video), class: "btn btn-sm btn-primary", data: { toggle: "tooltip", disable_with: fa_icon("spinner spin") }, form: { data: { ujs_target: "graphics-element-form" } }, params: { graphics_element: { text: "", time_elapsed: "", edl_type: "all_tracks" } }, method: :get, remote: true, title: t(".create_graphics_element") do %> + <%= fa_icon "picture-o" %> + <% end %> +
    +
    + <%= button_to new_video_video_analyses_audio_confirmation_path(video), class: "btn btn-sm btn-primary", data: { toggle: "tooltip", disable_with: fa_icon("spinner spin") }, form: { data: { ujs_target: "audio-confirmation-form" } }, params: { audio_confirmation: { confirmation_type: "library_music", time_elapsed: "", edl_type: "all_tracks" } }, method: :get, remote: true, title: "Add to Music Cue Sheet" do %> + <%= fa_icon "music" %> + <% end %> +
    +
    + <%= button_to video_video_analyses_edl_events_path(video), class: "btn btn-sm btn-primary", data: { toggle: "tooltip", disable_with: fa_icon("spinner spin") }, form: { data: { ujs_target: "edl-event-form" } }, params: { edl_event: { time_elapsed: "" } }, remote: true, title: t(".create_edl_event") do %> + <%= fa_icon "info-circle" %> + <% end %> +
    +
    +
    + <%= text_field_tag "seek", "", id: "seek_input", class: "col-6 d-inline-block form-control form-control-sm", placeholder: "00:00:00:00" %> + <%= button_tag "Seek", id: "seek_button", class: "d-inline-block btn btn-sm btn-primary" %> +
    +
    \ No newline at end of file diff --git a/app/views/video_analyses/_appearance_release.html.erb b/app/views/video_analyses/_appearance_release.html.erb new file mode 100644 index 0000000..7ac617a --- /dev/null +++ b/app/views/video_analyses/_appearance_release.html.erb @@ -0,0 +1,3 @@ +
  • + <%= render "video_analyses/releasable", releasable: appearance_release, video: video %> +
  • diff --git a/app/views/video_analyses/_audio_confirmations.html.erb b/app/views/video_analyses/_audio_confirmations.html.erb new file mode 100644 index 0000000..9954209 --- /dev/null +++ b/app/views/video_analyses/_audio_confirmations.html.erb @@ -0,0 +1,36 @@ +<% if audio_confirmations.any? %> +
    + + + + + + + + + + + + + <% audio_confirmations.each do |audio_confirmation| %> + + + + + + + + + <% end %> + +
    EDL #EDL TCVID TCFilename
    <%= audio_confirmation.timecode_in %><%= audio_confirmation.appears_at %><%= audio_confirmation.source_file_name %> + <%= link_to fa_icon("trash fw"), audio_confirmation_path(audio_confirmation), method: :delete, class: "btn btn-danger btn-sm", remote: true %> +
    +
    +<% else %> +
    +
    + Music Releases that have been added to the report will appear here +
    +
    +<% end %> diff --git a/app/views/video_analyses/_file_info_releasable.html.erb b/app/views/video_analyses/_file_info_releasable.html.erb new file mode 100644 index 0000000..036e87f --- /dev/null +++ b/app/views/video_analyses/_file_info_releasable.html.erb @@ -0,0 +1,8 @@ +<%= button_to_video_release_confirmation video, releasable, additional_video_release_params: { file_info_id: file_info.id } do %> +
    +
    + <%= file_info.filename %> +
    +
    <%= truncate releasable.name, length: 25 %>
    +
    +<% end %> diff --git a/app/views/video_analyses/_graphics_elements.html.erb b/app/views/video_analyses/_graphics_elements.html.erb new file mode 100644 index 0000000..45ff532 --- /dev/null +++ b/app/views/video_analyses/_graphics_elements.html.erb @@ -0,0 +1,37 @@ +<% if graphics_elements.any? %> +
    + + + + + + + + + + + + + <% graphics_elements.each do |graphics_element| %> + + + + + + + + + <% end %> + +
    EDL #EDL TCVID TCFilename
    <%= graphics_element.timecode_in %><%= graphics_element.appears_at %><%= graphics_element.source_file_name %> + <%= link_to fa_icon("pencil fw"), [:edit, graphics_element], method: :get, class: "btn btn-primary btn-sm", remote: true, data: { disable_with: fa_icon("spinner spin", text: "Edit") } %> + <%= link_to fa_icon("trash fw"), graphics_element, method: :delete, class: "btn btn-danger btn-sm", remote: true %> +
    +
    +<% else %> +
    +
    + GFX Cue Elements that have been added to the report will appear here +
    +
    +<% end %> diff --git a/app/views/video_analyses/_keyboard_shortcuts.html.erb b/app/views/video_analyses/_keyboard_shortcuts.html.erb new file mode 100644 index 0000000..1b51f7c --- /dev/null +++ b/app/views/video_analyses/_keyboard_shortcuts.html.erb @@ -0,0 +1,49 @@ +
    + <%= link_to "#keyboard_shortcuts", data: { toggle: "collapse" } do %> + <%= fa_icon "info-circle" %> View Keyboard Shortcuts + <% end %> +
    +
    +
      +
    • +
      +
      + <%= fa_icon "play" %> <%= fa_icon "stop" %> +
      +
      + space +
      +
      +
    • +
    • +
      +
      + <%= fa_icon "step-backward" %> Back 5s +
      +
      + +
      +
      +
    • +
    • +
      +
      + <%= fa_icon "step-forward" %> Forward 5s +
      +
      + +
      +
      +
    • +
    • +
      +
      + <%= fa_icon "trash" %> Delete Marker +
      +
      + d +
      +
      +
    • +
    +
    diff --git a/app/views/video_analyses/_location_release.html.erb b/app/views/video_analyses/_location_release.html.erb new file mode 100644 index 0000000..637681a --- /dev/null +++ b/app/views/video_analyses/_location_release.html.erb @@ -0,0 +1,3 @@ +
  • + <%= render "video_analyses/releasable", releasable: location_release, video: video %> +
  • diff --git a/app/views/video_analyses/_material_release.html.erb b/app/views/video_analyses/_material_release.html.erb new file mode 100644 index 0000000..f12c03b --- /dev/null +++ b/app/views/video_analyses/_material_release.html.erb @@ -0,0 +1,3 @@ +
  • + <%= render "video_analyses/releasable", releasable: material_release, video: video %> +
  • diff --git a/app/views/video_analyses/_music_release.html.erb b/app/views/video_analyses/_music_release.html.erb new file mode 100644 index 0000000..0d46fe1 --- /dev/null +++ b/app/views/video_analyses/_music_release.html.erb @@ -0,0 +1,30 @@ +<% if music_release.file_infos.any? %> + <% music_release.file_infos.each do |file_info| %> +
  • + <%= button_to new_video_video_analyses_audio_confirmation_path(video), + class: "btn-no-style border cursor-copy p-2", + form: { data: { ujs_target: "audio-confirmation-form" } }, + params: { audio_confirmation: { confirmation_type: "original_music", time_elapsed: "", composer_info: music_release.composer_info, publisher_info: music_release.publisher_info, catalog: "", title: "", edl_type: video.audio_only_edl_file.attached? ? "audio" : "all_tracks" }, matched_file_name: file_info.filename }, + method: :get, + remote: true do %> +
    +
    + <%= file_info.filename %> +
    +
    <%= truncate music_release.name, length: 25 %>
    +
    + <% end %> +
  • + <% end %> +<% else %> +
  • +
    + +
    + No File Uploaded +
    + +
    <%= truncate music_release.name, length: 25 %>
    +
    +
  • +<% end %> diff --git a/app/views/video_analyses/_music_release_file_infos.html.erb b/app/views/video_analyses/_music_release_file_infos.html.erb new file mode 100644 index 0000000..b6c48db --- /dev/null +++ b/app/views/video_analyses/_music_release_file_infos.html.erb @@ -0,0 +1,30 @@ +<% if music_release_file_infos.any? %> + <% music_release_file_infos.each do |file_info| %> +
  • + <%= button_to new_video_video_analyses_audio_confirmation_path(video), + class: "btn-no-style border cursor-copy p-2", + form: { data: { ujs_target: "audio-confirmation-form" } }, + params: { audio_confirmation: { confirmation_type: "original_music", time_elapsed: "", composer_info: file_info.releasable.composer_info, publisher_info: file_info.releasable.publisher_info, catalog: "", title: "", edl_type: video.audio_only_edl_file.attached? ? "audio" : "all_tracks" }, matched_file_name: file_info.filename }, + method: :get, + remote: true do %> +
    +
    + <%= file_info.filename %> +
    +
    <%= truncate file_info.releasable.name, length: 25 %>
    +
    + <% end %> +
  • + <% end %> +<% else %> +
  • +
    + +
    + No File Uploaded +
    + +
    No releases found
    +
    +
  • +<% end %> diff --git a/app/views/video_analyses/_releasable.html.erb b/app/views/video_analyses/_releasable.html.erb new file mode 100644 index 0000000..5e6c74f --- /dev/null +++ b/app/views/video_analyses/_releasable.html.erb @@ -0,0 +1,20 @@ +<%= button_to_video_release_confirmation video, releasable do %> +
    + + <% if releasable.photos.size > 1 %> + <%= carousel_for releasable.photos, indicators: false, class: "carousel-bg-black" do |photo| %> + <%= image_tag large_variant(photo), class: "figure-img img-fluid d-block w-100" %> + <% end %> + + <% elsif releasable.photos.size == 1 %> + <%= image_tag large_variant(releasable.photo), class: "img-fluid figure-img" %> + + <% else %> +
    + No Photo +
    + <% end %> + +
    <%= truncate releasable.name, length: 25 %>
    +
    +<% end %> diff --git a/app/views/video_analyses/_releasables.html.erb b/app/views/video_analyses/_releasables.html.erb new file mode 100644 index 0000000..e990ca2 --- /dev/null +++ b/app/views/video_analyses/_releasables.html.erb @@ -0,0 +1,7 @@ +<% if releasables.any? %> + <% releasables.each do |releasable| %> + <%= render "video_analyses/#{releasable.class.to_s.underscore}", releasable.class.to_s.underscore.to_sym => releasable, video: video %> + <% end %> +<% else %> +
  • There are no releases.
  • +<% end %> diff --git a/app/views/video_analyses/_show_hide_edl_events.html.erb b/app/views/video_analyses/_show_hide_edl_events.html.erb new file mode 100644 index 0000000..303e225 --- /dev/null +++ b/app/views/video_analyses/_show_hide_edl_events.html.erb @@ -0,0 +1,18 @@ +

    <%= edl_events_data[:info_message] %>

    +<% if edl_events_data[:edl_events].any? %> +
    + <%= link_to "Show/Hide EDL Events", "javascript:void(0)", class: "btn btn-sm btn-primary mb-1", data: { toggle: "collapse", target: "#edl_events" } %> +
    +
      + <% edl_events_data[:edl_events].each do |edl_event| %> +
    • +
      + <% edl_event.public_attributes.each do |name, value| %> +
      <%= name.to_s.titleize %>
      +
      <%= value %>
      + <% end %> +
      +
    • + <% end %> +
    +<% end %> diff --git a/app/views/video_analyses/_talent_release.html.erb b/app/views/video_analyses/_talent_release.html.erb new file mode 100644 index 0000000..a2cfecd --- /dev/null +++ b/app/views/video_analyses/_talent_release.html.erb @@ -0,0 +1,3 @@ +
  • + <%= render "video_analyses/releasable", releasable: talent_release, video: video %> +
  • diff --git a/app/views/video_analyses/_unreleased_appearances.html.erb b/app/views/video_analyses/_unreleased_appearances.html.erb new file mode 100644 index 0000000..9d21632 --- /dev/null +++ b/app/views/video_analyses/_unreleased_appearances.html.erb @@ -0,0 +1,35 @@ +<% if unreleased_appearances.any? %> +
    + + + + + + + + + + + + <% unreleased_appearances.each do |unreleased_appearance| %> + + + + + + + + <% end %> + +
    NotesChannelEDL TCVideo TC
    <%= unreleased_appearance.note_text %><%= unreleased_appearance.channel %><%= unreleased_appearance.timecode_in %><%= unreleased_appearance.appears_at %> + <%= link_to fa_icon("pencil fw", text: "Edit"), [:edit, unreleased_appearance], method: :get, class: "btn btn-primary btn-sm", remote: true, data: { disable_with: fa_icon("spinner spin", text: "Edit") } %> + <%= link_to fa_icon("trash fw"), unreleased_appearance, method: :delete, class: "btn btn-danger btn-sm", remote: true %> +
    +
    +<% else %> +
    +
    + Issues and Concerns that have been added to the report will appear here +
    +
    +<% end %> diff --git a/app/views/video_analyses/_video_release_confirmations.html.erb b/app/views/video_analyses/_video_release_confirmations.html.erb new file mode 100644 index 0000000..a4d3199 --- /dev/null +++ b/app/views/video_analyses/_video_release_confirmations.html.erb @@ -0,0 +1,50 @@ +<% if video_release_confirmations.any? %> +
    + + + + + + + + + + + + + + <% video_release_confirmations.group_by(&:releasable_type).each do |group, confirmations| %> + <% confirmations.sort_by(&:appears_at).each do |confirmation| %> + + + + + + + + + + <% end %> + <% end %> + +
    EDL #EDL TCVID TCNamePhoto
    <%= confirmation.timecode_in %><%= confirmation.appears_at %><%= confirmation.releasable.name %> + <% if confirmation.file_info.present? %> + <%= confirmation.file_info.filename %> + <% elsif confirmation.releasable.respond_to?(:photo) && confirmation.releasable.photo.attached? %> + <%= image_tag thumbnail_variant(confirmation.releasable.photo), class: "img-fluid figure-img" %> + <% else %> + No Photo + <% end %> + + <%= link_to polymorphic_path([confirmation.video, confirmation.releasable, confirmation]), class: "btn btn-danger btn-sm", data: { toggle: "tooltip", template: tooltip_template(css_class: "tooltip-danger") }, method: :delete, remote: true do %> + <%= fa_icon("trash fw") %> + <% end %> +
    +
    +<% else %> +
    +
    + Releases that have been added to the report will appear here +
    +
    +<% end %> diff --git a/app/views/video_analyses/_video_status_updated.html.erb b/app/views/video_analyses/_video_status_updated.html.erb new file mode 100644 index 0000000..6ba01e2 --- /dev/null +++ b/app/views/video_analyses/_video_status_updated.html.erb @@ -0,0 +1,13 @@ +<% # TODO: Is there a way to consolidate this with the main template? %> +<% if video.analysis_pending? %> +
    + <%= fa_icon "cog spin" %> Video is still being analyzed +
    +<% else %> +
    +

    Video analysis has completed!

    + <%= link_to "", class: "btn btn-sm btn-success" do %> + <%= fa_icon "refresh" %> Refresh + <% end %> +
    +<% end %> diff --git a/app/views/video_analyses/acquired_media_releases/index.js.erb b/app/views/video_analyses/acquired_media_releases/index.js.erb new file mode 100644 index 0000000..651a8f3 --- /dev/null +++ b/app/views/video_analyses/acquired_media_releases/index.js.erb @@ -0,0 +1,8 @@ +// Hide any open toolitps before removing elements from the DOM +$("#acquired_media_releases [data-toggle=tooltip]").tooltip("dispose"); + +// Update the release list +$("#acquired_media_releases").html("<%= j render("video_analyses/acquired_media_file_infos", acquired_media_file_infos: @acquired_media_file_infos, video: @video) %>"); + +// # Reset the search form +$("#acquired_media_releases_section form input[name='query']").val("<%= params[:query] %>"); diff --git a/app/views/video_analyses/appearance_releases/index.js.erb b/app/views/video_analyses/appearance_releases/index.js.erb new file mode 100644 index 0000000..92f6e4a --- /dev/null +++ b/app/views/video_analyses/appearance_releases/index.js.erb @@ -0,0 +1,8 @@ +// Hide any open toolitps before removing elements from the DOM +$("#appearance_releases [data-toggle=tooltip]").tooltip("dispose"); + +// Update the release list +$("#appearance_releases").html("<%= j render("video_analyses/releasables", releasables: @appearance_releases, video: @video) %>"); + +// # Reset the search form +$("#appearance_releases_section form input[name='query']").val("<%= params[:query] %>"); diff --git a/app/views/video_analyses/audio_confirmations/_new_audio_confirmation_modal.html.erb b/app/views/video_analyses/audio_confirmations/_new_audio_confirmation_modal.html.erb new file mode 100644 index 0000000..22001d8 --- /dev/null +++ b/app/views/video_analyses/audio_confirmations/_new_audio_confirmation_modal.html.erb @@ -0,0 +1,47 @@ +<%= content_tag :div, class: "modal modal-right", id: "new_audio_confirmation_modal", aria: { labelledby: "modalLabel", hidden: true }, role: "dialog", tabindex: -1 do %> + +<% end %> diff --git a/app/views/video_analyses/audio_confirmations/create.js.erb b/app/views/video_analyses/audio_confirmations/create.js.erb new file mode 100644 index 0000000..b663be5 --- /dev/null +++ b/app/views/video_analyses/audio_confirmations/create.js.erb @@ -0,0 +1,12 @@ +<% if @audio_confirmation_data.is_valid %> +$("#audio_confirmations").html("<%= j render("video_analyses/audio_confirmations", audio_confirmations: @audio_confirmations_data.audio_confirmations) %>"); + +<% if @audio_confirmation_data.should_toggle_checkmark %> + $('#audio_matches .releasable-match').has('td:contains("<%= @audio_confirmation_data.presented_source_file_name %>")').has('td:contains("<%= @audio_confirmation_data.timecode_in %>")').attr("data-confirmed", true).data("confirmed", true) + $('#music_releases .music-releasable').has('figure:contains("<%= @audio_confirmation_data.source_file_name %>")').attr("data-confirmed", true).data("confirmed", true) +<% end %> + +$("#new_audio_confirmation_modal").modal("toggle"); +$('.modal-backdrop').remove(); +<% else %> +<% end %> \ No newline at end of file diff --git a/app/views/video_analyses/audio_confirmations/destroy.js.erb b/app/views/video_analyses/audio_confirmations/destroy.js.erb new file mode 100644 index 0000000..e9fad7f --- /dev/null +++ b/app/views/video_analyses/audio_confirmations/destroy.js.erb @@ -0,0 +1,3 @@ +$("#audio_confirmations").html("<%= j render("video_analyses/audio_confirmations", audio_confirmations: @audio_confirmations_data.audio_confirmations) %>"); +$('#audio_matches .releasable-match').has('td:contains("<%= @audio_confirmation_data.presented_source_file_name %>")').has('td:contains("<%= @audio_confirmation_data.timecode_in %>")').attr("data-confirmed", false).data("confirmed", false) +$('#music_releases .music-releasable').has('figure:contains("<%= @audio_confirmation_data.source_file_name %>")').attr("data-confirmed", false).data("confirmed", false) diff --git a/app/views/video_analyses/audio_confirmations/new.js.erb b/app/views/video_analyses/audio_confirmations/new.js.erb new file mode 100644 index 0000000..b81e70c --- /dev/null +++ b/app/views/video_analyses/audio_confirmations/new.js.erb @@ -0,0 +1,5 @@ +$("[data-toggle=tooltip]").tooltip("dispose"); +$("#new_audio_confirmation_modal").remove(); + +$("body").append("<%= j render('new_audio_confirmation_modal', audio_confirmation: @audio_confirmation, matched_file_name: @matched_file_name) %>"); +$("#new_audio_confirmation_modal").modal("toggle"); diff --git a/app/views/video_analyses/edl_events/_edl_event_modal.html.erb b/app/views/video_analyses/edl_events/_edl_event_modal.html.erb new file mode 100644 index 0000000..982e683 --- /dev/null +++ b/app/views/video_analyses/edl_events/_edl_event_modal.html.erb @@ -0,0 +1,32 @@ +<%= content_tag :div, class: "modal modal-right", id: "edl_event_modal", aria: { labelledby: "modalLabel", hidden: true }, role: "dialog", tabindex: -1 do %> + +<% end %> diff --git a/app/views/video_analyses/edl_events/create.js.erb b/app/views/video_analyses/edl_events/create.js.erb new file mode 100644 index 0000000..0566908 --- /dev/null +++ b/app/views/video_analyses/edl_events/create.js.erb @@ -0,0 +1,7 @@ +$("[data-toggle=tooltip]").tooltip("dispose"); +<% # Remove the modal if it already exists %> +$("#edl_event_modal").remove(); + +<% # Create and show the modal %> +$("body").append("<%= j render('edl_event_modal', edl_events: @edl_events) %>"); +$("#edl_event_modal").modal("toggle"); diff --git a/app/views/video_analyses/graphics_elements/_edit_graphics_element_modal.html.erb b/app/views/video_analyses/graphics_elements/_edit_graphics_element_modal.html.erb new file mode 100644 index 0000000..f178e08 --- /dev/null +++ b/app/views/video_analyses/graphics_elements/_edit_graphics_element_modal.html.erb @@ -0,0 +1,38 @@ +<%= content_tag :div, class: "modal modal-right", id: "edit_graphics_element_modal", aria: { labelledby: "modalLabel", hidden: true }, role: "dialog", tabindex: -1 do %> + +<% end %> diff --git a/app/views/video_analyses/graphics_elements/_new_graphics_element_modal.html.erb b/app/views/video_analyses/graphics_elements/_new_graphics_element_modal.html.erb new file mode 100644 index 0000000..65973a6 --- /dev/null +++ b/app/views/video_analyses/graphics_elements/_new_graphics_element_modal.html.erb @@ -0,0 +1,39 @@ +<%= content_tag :div, class: "modal modal-right", id: "new_graphics_element_modal", aria: { labelledby: "modalLabel", hidden: true }, role: "dialog", tabindex: -1 do %> + +<% end %> diff --git a/app/views/video_analyses/graphics_elements/create.js.erb b/app/views/video_analyses/graphics_elements/create.js.erb new file mode 100644 index 0000000..86f7fd8 --- /dev/null +++ b/app/views/video_analyses/graphics_elements/create.js.erb @@ -0,0 +1,10 @@ +<% if @graphics_element_data.is_valid %> +$("#graphics_elements_list").html("<%= j render("video_analyses/graphics_elements", graphics_elements: @graphics_elements_data.graphics_elements) %>"); + +<% if @graphics_element_data.should_toggle_checkmark %> + $('.graphics-match').has('td:contains("<%= @graphics_element_data.source_file_name %>")').has('td:contains("<%= @graphics_element_data.timecode_in %>")').attr("data-confirmed", true).data("confirmed", true) +<% end %> + +$("#new_graphics_element_modal").modal("toggle"); +<% else %> +<% end %> \ No newline at end of file diff --git a/app/views/video_analyses/graphics_elements/destroy.js.erb b/app/views/video_analyses/graphics_elements/destroy.js.erb new file mode 100644 index 0000000..7df0377 --- /dev/null +++ b/app/views/video_analyses/graphics_elements/destroy.js.erb @@ -0,0 +1,2 @@ +$("#graphics_elements_list").html("<%= j render("video_analyses/graphics_elements", graphics_elements: @graphics_elements_data.graphics_elements) %>"); +$('.graphics-match').has('td:contains("<%= @graphics_element_data.source_file_name %>")').has('td:contains("<%= @graphics_element_data.timecode_in %>")').attr("data-confirmed", false).data("confirmed", false) diff --git a/app/views/video_analyses/graphics_elements/edit.js.erb b/app/views/video_analyses/graphics_elements/edit.js.erb new file mode 100644 index 0000000..c30b069 --- /dev/null +++ b/app/views/video_analyses/graphics_elements/edit.js.erb @@ -0,0 +1,4 @@ +$("#edit_graphics_element_modal").remove(); + +$("body").append("<%= j render('edit_graphics_element_modal', graphics_element: @graphics_element) %>"); +$("#edit_graphics_element_modal").modal("toggle"); diff --git a/app/views/video_analyses/graphics_elements/new.js.erb b/app/views/video_analyses/graphics_elements/new.js.erb new file mode 100644 index 0000000..8b59d3c --- /dev/null +++ b/app/views/video_analyses/graphics_elements/new.js.erb @@ -0,0 +1,5 @@ +$("[data-toggle=tooltip]").tooltip("dispose"); +$("#new_graphics_element_modal").remove(); + +$("body").append("<%= j render('new_graphics_element_modal', graphics_element: @graphics_element, matched_file_name: @matched_file_name) %>"); +$("#new_graphics_element_modal").modal("toggle"); diff --git a/app/views/video_analyses/graphics_elements/update.js.erb b/app/views/video_analyses/graphics_elements/update.js.erb new file mode 100644 index 0000000..53d10ba --- /dev/null +++ b/app/views/video_analyses/graphics_elements/update.js.erb @@ -0,0 +1,10 @@ +<% if @graphics_element_data.is_valid %> +$("#graphics_elements_list").html("<%= j render("video_analyses/graphics_elements", graphics_elements: @graphics_elements_data.graphics_elements) %>"); + +<% if @graphics_element_data.should_toggle_checkmark %> + $('.graphics-match').has('span:contains("<%= @graphics_element_data.source_file_name %>")').has('span:contains("<%= @graphics_element_data.timecode_in %>")').attr("data-confirmed", true).data("confirmed", true) +<% end %> + +$("#edit_graphics_element_modal").modal("toggle"); +<% else %> +<% end %> diff --git a/app/views/video_analyses/location_releases/index.js.erb b/app/views/video_analyses/location_releases/index.js.erb new file mode 100644 index 0000000..ae93381 --- /dev/null +++ b/app/views/video_analyses/location_releases/index.js.erb @@ -0,0 +1,8 @@ +// Hide any open toolitps before removing elements from the DOM +$("#location_releases [data-toggle=tooltip]").tooltip("dispose"); + +// Update the release list +$("#location_releases").html("<%= j render("video_analyses/releasables", releasables: @location_releases, video: @video) %>"); + +// # Reset the search form +$("#location_releases_section form input[name='query']").val("<%= params[:query] %>"); diff --git a/app/views/video_analyses/material_releases/index.js.erb b/app/views/video_analyses/material_releases/index.js.erb new file mode 100644 index 0000000..2f4ab57 --- /dev/null +++ b/app/views/video_analyses/material_releases/index.js.erb @@ -0,0 +1,8 @@ +// Hide any open toolitps before removing elements from the DOM +$("#material_releases [data-toggle=tooltip]").tooltip("dispose"); + +// Update the release list +$("#material_releases").html("<%= j render("video_analyses/releasables", releasables: @material_releases, video: @video) %>"); + +// # Reset the search form +$("#material_releases_section form input[name='query']").val("<%= params[:query] %>"); diff --git a/app/views/video_analyses/music_releases/index.js.erb b/app/views/video_analyses/music_releases/index.js.erb new file mode 100644 index 0000000..6f2bfe5 --- /dev/null +++ b/app/views/video_analyses/music_releases/index.js.erb @@ -0,0 +1,8 @@ +// Hide any open toolitps before removing elements from the DOM +$("#music_releases [data-toggle=tooltip]").tooltip("dispose"); + +// Update the release list +$("#music_releases").html("<%= j render("video_analyses/music_release_file_infos", music_release_file_infos: @music_release_file_infos, video: @video) %>"); + +// # Reset the search form +$("#music_releases_section form input[name='query']").val("<%= params[:query] %>"); diff --git a/app/views/video_analyses/show.html.erb b/app/views/video_analyses/show.html.erb new file mode 100644 index 0000000..e13afbd --- /dev/null +++ b/app/views/video_analyses/show.html.erb @@ -0,0 +1,555 @@ +<%= content_for :meta do %> + +<% end %> + +
    +
    +
    + <% if @video.report_published? %> + <%= link_to fa_icon("file-text", text: "Unpublish"), [@video, :report_publications], method: :delete, class: "btn btn-danger btn-sm" %> + <% else %> + <%= link_to fa_icon("file-text", text: "Publish"), [@video, :report_publications], method: :post, class: "btn btn-primary btn-sm" %> + <% end %> + <%= button_to_release_report @video, content: fa_icon("print", text: t(".actions.create_release_report")) %> +
    +
    + <% if @video_analysis_presenter.stale? %> +
    + <%= fa_icon "warning" %> Contracts have been added or imported since this video file has been uploaded. You + must <%= link_to "re-analyze this video", [@video, :video_analyses], method: :post %> otherwise the new contracts + will not be included in the analysis. +
    + <% end %> + +
    +
    +
    +
    +
    <%= @video.name ? @video.name : @video.filename %> (<%= @video.project.name %>)
    +
    + <%= button_tag fa_icon("download", text: "EDLs"), class: "btn btn-primary btn-sm dropdown-toggle border", data: { toggle: "dropdown", boundary: "window" }, aria: { haspopup: true, expanded: false } %> + +
    +
    +
    + <%= video_tag @video_analysis_presenter.video_url, data: { behavior: "video-player-with-analysis", video_id: @video.id } %> +
    + <%= render "video_analyses/actions", video: @video %> + <%= render "video_analyses/keyboard_shortcuts" %> +
    +
    +
    +
    +
    +
    +
    EDL Events
    +
    +
    +
    +
    + <%= text_field_tag "go_to", "", id: "go_to", class: "form-control form-control-sm", placeholder: "00:00:00:00" %> +
    + <%= button_tag "Go", id: "go_button", class: "btn btn-primary btn-sm" %> +
    +
    +
    +
    + + + + + + + + + + + + <% @video_analysis_presenter.all_tracks_edl_events.each do |edl_event| %> + + + + + + + + <% end %> + +
    ChannelSourceClipTC InTC Out
    <%= edl_event.channel %><%= edl_event.source_file_name %><%= edl_event.clip_name %><%= edl_event.timecode_in %><%= edl_event.timecode_out %>
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    AI Matches
    +
    +
    +
    + + + + + + + + + + + + + + <% @video_analysis_presenter.chronological_appearances.each do |match| %> + <% if match.respond_to?(:file_info) && match.file_info.present? %> + + + + + + + + + + <% elsif match.photo.attached? %> + + + + + + + + + + <% end %> + <% end %> + +
    EDL #EDL TCVID TCNamePhoto
    <%= fa_icon("check-circle", style: "font-size:24px;color:green", "data-ujs-target" => "match-confirmed-check") %><%= match.timecode_in %><%= match.appears_at_timecode %> + <%= button_to_video_release_confirmation @video, match, additional_video_release_params: { file_info_id: match.file_info.id }, classes: "btn btn-success btn-sm" do %> + <%= fa_icon "plus" %> + <% end %> +
    <%= fa_icon("check-circle", style: "font-size:24px;color:green", "data-ujs-target" => "match-confirmed-check") %> N/A<%= match.appears_at_timecode %><%= match.name %><%= image_tag thumbnail_variant(match.photo), class: "img-fluid figure-img" %> + <%= button_to_video_release_confirmation @video, match, classes: "btn btn-success btn-sm" do %> + <%= fa_icon "plus" %> + <% end %> +
    +
    +
    + <% # Init the video player and the recommendations %> + <%= javascript_tag nonce: true do %> + $(document).ready(function() { + var bookmarks = <%= raw @video_analysis_presenter.bookmarks.to_json %>; + var player = $("[data-behavior=video-player-with-analysis]"); + App.VideoAnalysis.initPlayerWithAnalysis(player, bookmarks); + }); + <% end %> + <% if @video.analysis_pending? %> +
    + <%= fa_icon "cog spin" %> Video is still being analyzed +
    + <% elsif @video.analysis_not_started? %> +
    + <%= link_to "Analyze Video", "#", class: "btn btn-primary btn-sm", method: :post %> +
    + <% end %> +
    +
    +
    +
    +
    +
    +
    +
    Added to Report
    +
    +
    + <%= render "video_analyses/video_release_confirmations", video_release_confirmations: @video_release_confirmations %> +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    AI Matches
    +
    + +
    +
    + + + + + + + + + + + + + <% @video_analysis_presenter.chronological_graphics_matches.each do |match| %> + + + + + + + + + <% end %> + +
    EDL #EDL TCVID TCFilename
    <%= fa_icon("check-circle", style: "font-size:24px;color:green", "data-ujs-target" => "graphics-confirmed-check") %><%= match.timecode_in %><%= match.appears_at_timecode %><%= match.filename %> + <%= button_to new_video_video_analyses_graphics_element_path(@video), + class: "btn btn-success btn-sm", + form: { data: { ujs_target: "graphics-element-form" } }, + params: { graphics_element: { text: "", time_elapsed: match.start_time, edl_type: "graphics" }, matched_file_name: match.filename }, + method: :get, + form_class: "text-center", + remote: true do %> + <%= fa_icon "plus" %> + <% end %> +
    +
    + +
    + <% if @video.analysis_pending? %> +
    + <%= fa_icon "cog spin" %> Video is still being analyzed +
    + <% end %> +
    +
    +
    +
    +
    +
    +
    +
    Added to Report
    +
    +
    + <%= render "video_analyses/graphics_elements", graphics_elements: @graphics_elements_data.graphics_elements %> +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    AI Matches
    +
    + +
    +
    + + + + + + + + + + + + + <% @video_analysis_presenter.chronological_audio_matches.each do |match| %> + + + + + + + + + <% end %> + +
    EDL #EDL TCVID TCFilename
    <%= fa_icon("check-circle", style: "font-size:24px;color:green", "data-ujs-target" => "match-confirmed-check") %><%= match.timecode_in %><%= match.appears_at_timecode %><%= match.presented_filename %> + <%= button_to new_video_video_analyses_audio_confirmation_path(@video), + class: "btn btn-success btn-sm", + form: { data: { ujs_target: "audio-confirmation-form" } }, + params: { audio_confirmation: { confirmation_type: match.confirmation_type, time_elapsed: match.start_time, composer_info: match.composer_info, publisher_info: match.publisher_info, catalog: match.catalog, title: match.title, edl_type: match.edl_type }, matched_file_name: match.filename }, + method: :get, + remote: true, + data: { disable_with: t("shared.disable_with") } do %> + <%= fa_icon "plus" %> + <% end %> +
    +
    + +
    + <% if @video.analysis_pending? %> +
    + <%= fa_icon "cog spin" %> Video is still being analyzed +
    + <% end %> +
    +
    +
    +
    +
    +
    +
    +
    Added to Report
    +
    +
    + <%= render "video_analyses/audio_confirmations", audio_confirmations: @audio_confirmations %> +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    Added to Report
    +
    +
    + <%= render "video_analyses/unreleased_appearances", unreleased_appearances: @unreleased_appearances_data.unreleased_appearances %> +
    +
    +
    +
    +
    +
    +
    +
    +
    Notes
    +
    +
    + + + + + + + + + + + <% if @bookmarks.any? %> + <%= render partial: "bookmarks/bookmark", collection: @bookmarks %> + <% else %> + <%= render partial: "bookmarks/empty_bookmarks" %> + <% end %> + +
    <%= Bookmark.human_attribute_name(:category) %>TextVideo TC
    +
    +
    +
    +
    +
    +
    +
    + + <% project_feature @video.project, :talent_release do %> +
    +
    +
    +
    +
    + + Talent Releases +
    +
    +
    +
    +
    + <%= bootstrap_form_tag url: video_video_analyses_talent_releases_path(@video), layout: :inline, method: :get, remote: true do |form| %> + <%= form.search_field :query, class: "mt-1 mt-sm-0", hide_label: true, wrapper: false, placeholder: "Search...", value: params[:query] %> + <%= form.button fa_icon("search", text: "Search"), class: "btn btn-primary mt-1 mt-sm-0 ml-sm-1", data: { disable_with: fa_icon("spinner spin", text: "Searching") } %> + <%= link_to fa_icon("eraser", text: "Clear"), video_video_analyses_talent_releases_path(@video), class: "btn btn-danger mt-1 mt-sm-0 ml-1", data: { disable_with: fa_icon("spinner spin", text: "Clearing") }, remote: true %> + <% end %> +
    + +
      + <%= render "releasables", releasables: @talent_releases, video: @video %> +
    +
    +
    +
    +
    +
    + <% end %> + + <% project_feature @video.project, :appearance_release do %> +
    +
    +
    +
    +
    + + Appearance Releases +
    +
    +
    +
    +
    + <%= bootstrap_form_tag url: video_video_analyses_appearance_releases_path(@video), layout: :inline, method: :get, remote: true do |form| %> + <%= form.search_field :query, class: "mt-1 mt-sm-0", hide_label: true, wrapper: false, placeholder: "Search...", value: params[:query] %> + <%= form.button fa_icon("search", text: "Search"), class: "btn btn-primary mt-1 mt-sm-0 ml-sm-1", data: { disable_with: fa_icon("spinner spin", text: "Searching") } %> + <%= link_to fa_icon("eraser", text: "Clear"), video_video_analyses_appearance_releases_path(@video), class: "btn btn-danger mt-1 mt-sm-0 ml-1", data: { disable_with: fa_icon("spinner spin", text: "Clearing") }, remote: true %> + <% end %> +
    + +
      + <%= render "releasables", releasables: @appearance_releases, video: @video %> +
    +
    +
    +
    +
    +
    + <% end %> + + <% project_feature @video.project, :location_release do %> +
    +
    +
    +
    +
    + + Location Releases +
    +
    +
    +
    +
    + <%= bootstrap_form_tag url: video_video_analyses_location_releases_path(@video), layout: :inline, method: :get, remote: true do |form| %> + <%= form.search_field :query, class: "mt-1 mt-sm-0", hide_label: true, wrapper: false, placeholder: "Search...", value: params[:query] %> + <%= form.button fa_icon("search", text: "Search"), class: "btn btn-primary mt-1 mt-sm-0 ml-sm-1", data: { disable_with: fa_icon("spinner spin", text: "Searching") } %> + <%= link_to fa_icon("eraser", text: "Clear"), video_video_analyses_location_releases_path(@video), class: "btn btn-danger mt-1 mt-sm-0 ml-1", data: { disable_with: fa_icon("spinner spin", text: "Clearing") }, remote: true %> + <% end %> +
    + +
      + <%= render "releasables", releasables: @location_releases, video: @video %> +
    +
    +
    +
    +
    +
    + <% end %> + + <% project_feature @video.project, :acquired_media_release do %> +
    +
    +
    +
    +
    + + Acquired Media Releases (Footage & Stills) +
    +
    +
    +
    +
    + <%= bootstrap_form_tag url: video_video_analyses_acquired_media_releases_path(@video), layout: :inline, method: :get, remote: true do |form| %> + <%= form.search_field :query, class: "mt-1 mt-sm-0", hide_label: true, wrapper: false, placeholder: "Search...", value: params[:query] %> + <%= form.button fa_icon("search", text: "Search"), class: "btn btn-primary mt-1 mt-sm-0 ml-sm-1", data: { disable_with: fa_icon("spinner spin", text: "Searching") } %> + <%= link_to fa_icon("eraser", text: "Clear"), video_video_analyses_acquired_media_releases_path(@video), class: "btn btn-danger mt-1 mt-sm-0 ml-1", data: { disable_with: fa_icon("spinner spin", text: "Clearing") }, remote: true %> + <% end %> +
    + +
      + <%= render "releasables", releasables: @acquired_media_releases, video: @video %> +
    +
    +
    +
    +
    +
    + <% end %> + + <% project_feature @video.project, :music_release do %> +
    +
    +
    +
    +
    + + Music Releases (Original Music Release) +
    +
    +
    +
    +
    + <%= bootstrap_form_tag url: [@video, :video_analyses, :music_releases], layout: :inline, method: :get, remote: true do |form| %> + <%= form.search_field :query, class: "mt-1 mt-sm-0", hide_label: true, wrapper: false, placeholder: "Search...", value: params[:query] %> + <%= form.button fa_icon("search", text: "Search"), class: "btn btn-primary mt-1 mt-sm-0 ml-sm-1", data: { disable_with: fa_icon("spinner spin", text: "Searching") } %> + <%= link_to fa_icon("eraser", text: "Clear"), [@video, :video_analyses, :music_releases], class: "btn btn-danger mt-1 mt-sm-0 ml-1", data: { disable_with: fa_icon("spinner spin", text: "Clearing") }, remote: true %> + <% end %> +
    + +
      + <%= render "releasables", releasables: @music_releases, video: @video %> +
    +
    +
    +
    +
    +
    + <% end %> + + <% project_feature @video.project, :material_release do %> +
    +
    +
    +
    +
    + + Material Releases (Products / Logos) +
    +
    +
    +
    +
    + <%= bootstrap_form_tag url: video_video_analyses_material_releases_path(@video), layout: :inline, method: :get, remote: true do |form| %> + <%= form.search_field :query, class: "mt-1 mt-sm-0", hide_label: true, wrapper: false, placeholder: "Search...", value: params[:query] %> + <%= form.button fa_icon("search", text: "Search"), class: "btn btn-primary mt-1 mt-sm-0 ml-sm-1", data: { disable_with: fa_icon("spinner spin", text: "Searching") } %> + <%= link_to fa_icon("eraser", text: "Clear"), video_video_analyses_material_releases_path(@video), class: "btn btn-danger mt-1 mt-sm-0 ml-1", data: { disable_with: fa_icon("spinner spin", text: "Clearing") }, remote: true %> + <% end %> +
    + +
      + <%= render "releasables", releasables: @material_releases, video: @video %> +
    +
    +
    +
    +
    +
    + <% end %> +
    diff --git a/app/views/video_analyses/talent_releases/index.js.erb b/app/views/video_analyses/talent_releases/index.js.erb new file mode 100644 index 0000000..0178172 --- /dev/null +++ b/app/views/video_analyses/talent_releases/index.js.erb @@ -0,0 +1,8 @@ +// Hide any open toolitps before removing elements from the DOM +$("#talent_releases [data-toggle=tooltip]").tooltip("dispose"); + +// Update the release list +$("#talent_releases").html("<%= j render("video_analyses/releasables", releasables: @talent_releases, video: @video) %>"); + +// # Reset the search form +$("#talent_releases_section form input[name='query']").val("<%= params[:query] %>"); diff --git a/app/views/video_analyses/unreleased_appearances/_edit_unreleased_appearance_modal.html.erb b/app/views/video_analyses/unreleased_appearances/_edit_unreleased_appearance_modal.html.erb new file mode 100644 index 0000000..a7a71af --- /dev/null +++ b/app/views/video_analyses/unreleased_appearances/_edit_unreleased_appearance_modal.html.erb @@ -0,0 +1,36 @@ +<%= content_tag :div, class: "modal modal-right", id: "edit_unreleased_appearance_modal", aria: { labelledby: "modalLabel", hidden: true }, role: "dialog", tabindex: -1 do %> + +<% end %> diff --git a/app/views/video_analyses/unreleased_appearances/_new_unreleased_appearance_modal.html.erb b/app/views/video_analyses/unreleased_appearances/_new_unreleased_appearance_modal.html.erb new file mode 100644 index 0000000..1524ff5 --- /dev/null +++ b/app/views/video_analyses/unreleased_appearances/_new_unreleased_appearance_modal.html.erb @@ -0,0 +1,36 @@ +<%= content_tag :div, class: "modal modal-right", id: "new_unreleased_appearance_modal", aria: { labelledby: "modalLabel", hidden: true }, role: "dialog", tabindex: -1 do %> + +<% end %> diff --git a/app/views/video_analyses/unreleased_appearances/create.js.erb b/app/views/video_analyses/unreleased_appearances/create.js.erb new file mode 100644 index 0000000..861a99d --- /dev/null +++ b/app/views/video_analyses/unreleased_appearances/create.js.erb @@ -0,0 +1,4 @@ +$("#issues_and_concerns_list").html("<%= j render("video_analyses/unreleased_appearances", unreleased_appearances: @unreleased_appearances_data.unreleased_appearances) %>"); + +<% # Close the modal %> +$("#new_unreleased_appearance_modal").modal("toggle"); diff --git a/app/views/video_analyses/unreleased_appearances/destroy.js.erb b/app/views/video_analyses/unreleased_appearances/destroy.js.erb new file mode 100644 index 0000000..65918c4 --- /dev/null +++ b/app/views/video_analyses/unreleased_appearances/destroy.js.erb @@ -0,0 +1 @@ +$("#issues_and_concerns_list").html("<%= j render("video_analyses/unreleased_appearances", unreleased_appearances: @unreleased_appearances_data.unreleased_appearances) %>"); diff --git a/app/views/video_analyses/unreleased_appearances/edit.js.erb b/app/views/video_analyses/unreleased_appearances/edit.js.erb new file mode 100644 index 0000000..32290bb --- /dev/null +++ b/app/views/video_analyses/unreleased_appearances/edit.js.erb @@ -0,0 +1,11 @@ +<% # Remove the modal if it already exists %> +<% # Remove modals if it already exists %> + $("#new_unreleased_appearance_modal").remove(); +$("#edit_unreleased_appearance_modal").remove(); + +<% # Create and show the modal %> +$("body").append("<%= j render('edit_unreleased_appearance_modal', unreleased_appearance: @unreleased_appearance) %>"); +$("#edit_unreleased_appearance_modal").modal("toggle"); + +handleNotesFieldVisibility(); + diff --git a/app/views/video_analyses/unreleased_appearances/new.js.erb b/app/views/video_analyses/unreleased_appearances/new.js.erb new file mode 100644 index 0000000..bf5b6be --- /dev/null +++ b/app/views/video_analyses/unreleased_appearances/new.js.erb @@ -0,0 +1,9 @@ +$("[data-toggle=tooltip]").tooltip("dispose"); +<% # Remove modals if it already exists %> + $("#new_unreleased_appearance_modal").remove(); +$("#edit_unreleased_appearance_modal").remove(); + +<% # Create and show the modal %> +$("body").append("<%= j render('new_unreleased_appearance_modal', unreleased_appearance: @unreleased_appearance) %>"); +$("#new_unreleased_appearance_modal").modal("toggle"); + diff --git a/app/views/video_analyses/unreleased_appearances/update.js.erb b/app/views/video_analyses/unreleased_appearances/update.js.erb new file mode 100644 index 0000000..81234d6 --- /dev/null +++ b/app/views/video_analyses/unreleased_appearances/update.js.erb @@ -0,0 +1,4 @@ +$("#issues_and_concerns_list").html("<%= j render("video_analyses/unreleased_appearances", unreleased_appearances: @unreleased_appearances_data.unreleased_appearances) %>"); + +<% # Close the modal %> +$("#edit_unreleased_appearance_modal").modal("toggle"); diff --git a/app/views/video_release_confirmations/_new_video_release_confirmation_modal.html.erb b/app/views/video_release_confirmations/_new_video_release_confirmation_modal.html.erb new file mode 100644 index 0000000..400c64b --- /dev/null +++ b/app/views/video_release_confirmations/_new_video_release_confirmation_modal.html.erb @@ -0,0 +1,35 @@ +<%= content_tag :div, class: "modal modal-right", id: "#{dom_id(video_release_confirmation)}_modal", aria: { labelledby: "modalLabel", hidden: true }, role: "dialog", tabindex: -1 do %> + +<% end %> diff --git a/app/views/video_release_confirmations/create.js.erb b/app/views/video_release_confirmations/create.js.erb new file mode 100644 index 0000000..898acf3 --- /dev/null +++ b/app/views/video_release_confirmations/create.js.erb @@ -0,0 +1,20 @@ +var hideConfirmed = $("input[name=hide_confirmed]:checked").length > 0; + +// Mark the release as confirmed +if (<%= @releasable.respond_to?(:file_infos) %>) { + $("[data-ujs-target=<%= dom_id(@releasable, "file_info_#{@video_release_confirmation.file_info_id}") %>]").attr("data-confirmed", true).data("confirmed", true) +} else { + $("#<%= dom_id(@releasable) %>").attr("data-confirmed", true).data("confirmed", true) +} + +// Hide if necessary +$("#appearance_releases_section #<%= dom_id(@releasable) %>").attr("data-hidden", hideConfirmed).data("hidden", hideConfirmed); + +// Refresh the confirmed releases list +$("#video_release_confirmations").html("<%= j render("video_analyses/video_release_confirmations", video_release_confirmations: @video_release_confirmations) %>"); + +// Close and remove the modal +$("#new_video_release_confirmation_modal").on("hidden.bs.modal", function (e) { + $("#new_video_release_confirmation_modal").remove(); +}); +$("#new_video_release_confirmation_modal").modal("hide"); diff --git a/app/views/video_release_confirmations/destroy.js.erb b/app/views/video_release_confirmations/destroy.js.erb new file mode 100644 index 0000000..17d41dc --- /dev/null +++ b/app/views/video_release_confirmations/destroy.js.erb @@ -0,0 +1,20 @@ +// Mark the release as no longer confirmed and show +if (<%= @releasable.respond_to?(:file_infos) && @video_release_confirmation.respond_to?(:file_info_id) %>) { + if (<%= @video_release_confirmations.none? { |video_release_confirmation| video_release_confirmation.respond_to?(:file_info_id) && video_release_confirmation.file_info_id == @video_release_confirmation.file_info_id } %>) { + $("[data-ujs-target=<%= dom_id(@releasable, "file_info_#{@video_release_confirmation.file_info_id}") %>]") + .attr("data-confirmed", false).data("confirmed", false) + .attr("data-hidden", false).data("hidden", false); + } +} else { + if (<%= @video_release_confirmations.none? { |video_release_confirmation| video_release_confirmation.releasable_id == @video_release_confirmation.releasable_id && video_release_confirmation.releasable_type == @video_release_confirmation.releasable_type } %>) { + $("#<%= dom_id(@releasable) %>") + .attr("data-confirmed", false).data("confirmed", false) + .attr("data-hidden", false).data("hidden", false); + } +} + +// Hide any open tooltips before removing elements from the DOM +$("#<%= dom_id(@video_release_confirmation) %> [data-toggle=tooltip]").tooltip("dispose"); + +// Refresh the confirmed releases list +$("#video_release_confirmations").html("<%= j render("video_analyses/video_release_confirmations", video_release_confirmations: @video_release_confirmations) %>"); diff --git a/app/views/video_release_confirmations/new.js.erb b/app/views/video_release_confirmations/new.js.erb new file mode 100644 index 0000000..31566f0 --- /dev/null +++ b/app/views/video_release_confirmations/new.js.erb @@ -0,0 +1,7 @@ +$("[data-toggle=tooltip]").tooltip("dispose"); +<% # Remove the modal if it already exists %> +$("#new_video_release_confirmation_modal").remove(); + +<% # Create and show the modal %> +$("body").append("<%= j render('new_video_release_confirmation_modal', video_release_confirmation: @video_release_confirmation) %>"); +$("#new_video_release_confirmation_modal").modal("toggle"); diff --git a/app/views/videos/_adobe_premiere_fields.html.erb b/app/views/videos/_adobe_premiere_fields.html.erb new file mode 100644 index 0000000..6185796 --- /dev/null +++ b/app/views/videos/_adobe_premiere_fields.html.erb @@ -0,0 +1,33 @@ +<% if video.edl_file.attached? %> +
    +
    Current EDL file
    + <%= link_to video.edl_file do %> + <%= fa_icon "file-text-o" %> <%= video.edl_file.filename %> + <% end %> +
    +<% end %> +<%= form.file_field :edl_file, required: video.new_record?, accept: '.edl', label_class: "required", label: "Video Only EDL", help: "Make sure the EDL features the video track containing all video elements needed for the Production Elements Log. Failure to do so may result in inaccurate and incomplete reporting." %> + +<% if video.graphics_only_edl_file.attached? %> +
    +
    Current Graphics Only EDL file
    + <%= link_to video.graphics_only_edl_file do %> + <%= fa_icon "file-text-o" %> <%= video.graphics_only_edl_file.filename %> + <% end %> +
    +<% end %> +<%= form.file_field :graphics_only_edl_file, accept: '.edl', label: "Graphics Only EDL", help: "Make sure the EDL features the video track containing all text and graphic elements needed for the Graphics Cue List. Failure to do so may result in inaccurate or incomplete reporting." %> + +<% if video.audio_only_edl_file.attached? %> +
    +
    Current Audio Only EDL file
    + <%= link_to video.audio_only_edl_file do %> + <%= fa_icon "file-text-o" %> <%= video.audio_only_edl_file.filename %> + <% end %> +
    +<% end %> +<%= form.file_field :audio_only_edl_file, required: video.new_record?, accept: '.edl', label_class: "required", label: "Audio Only EDL", help: "Make sure the EDL features the 4 audio tracks containing all the music elements needed for the Music Cue Sheet. Failure to do so may result in inaccurate or incomplete reporting." %> +

    + If your video project requires additional EDLs, + <%= mail_to_for_multiple_edls "click here", video.project %> to email them to the BiG support team. +

    diff --git a/app/views/videos/_avid_fields.html.erb b/app/views/videos/_avid_fields.html.erb new file mode 100644 index 0000000..2fba1dc --- /dev/null +++ b/app/views/videos/_avid_fields.html.erb @@ -0,0 +1,19 @@ +<% if video.edl_file.attached? %> +
    +
    Current EDL file
    + <%= link_to video.edl_file do %> + <%= fa_icon "file-text-o" %> <%= video.edl_file.filename %> + <% end %> +
    +<% end %> +<%= form.file_field :edl_file, label: "All Tracks EDL file", accept: '.edl', required: video.new_record?, label_class: "required" %> + +<% if video.graphics_only_edl_file.attached? %> +
    +
    Current Graphics Only EDL file
    + <%= link_to video.graphics_only_edl_file do %> + <%= fa_icon "file-text-o" %> <%= video.graphics_only_edl_file.filename %> + <% end %> +
    +<% end %> +<%= form.file_field :graphics_only_edl_file, accept: '.edl', label: "Graphics Only EDL file" %> diff --git a/app/views/videos/_form.html.erb b/app/views/videos/_form.html.erb new file mode 100644 index 0000000..36b8573 --- /dev/null +++ b/app/views/videos/_form.html.erb @@ -0,0 +1,33 @@ +<%= bootstrap_form_with local: true, model: model, id: "video_form" do |form| %> + <% if video.new_record? %> + <%= form.hidden_field :video_editing_system %> + <% end %> +
    + <%= form.text_field :name, wrapper_class: "col-sm-6" %> + <%= form.text_field :number, wrapper_class: "col-sm-6" %> +
    + + <% if video.new_record? %> + <%= form.file_field :file, required: true, label_class: "required", data: { direct_upload_url: rails_direct_uploads_url, aws_bucket: ENV['AWS_BUCKET'], aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'], signer_url: multipart_signatures_url } %> + <% end %> + + <% if video.adobe_premiere? %> + <%= render "videos/adobe_premiere_fields", form: form, video: video %> + <% else %> + <%= render "videos/avid_fields", form: form, video: video %> + <% end %> + +
    + <% if video.new_record? %> + <%= link_to t("shared.back"), [:landing, video.project, :videos], class: "col-3 text-reset" %> +
    + <%= form.button class: "btn btn-success btn-block", id: "video_submit_button", data: { disable_with: fa_icon("spinner spin", text: t("shared.disable_with")) } %> +
    + <% else %> + <%= link_to t("shared.cancel"), [video.project, :videos], class: "col-3 text-reset" %> +
    + <%= form.button class: "btn btn-primary btn-block", id: "video_submit_button", data: { disable_with: fa_icon("spinner spin", text: t("shared.disable_with")), confirm: "Warning: Re-uploading the EDLs will restart the reporting process. Expect delays in receiving your reports." } %> +
    + <% end %> +
    +<% end %> diff --git a/app/views/videos/_video.html.erb b/app/views/videos/_video.html.erb new file mode 100644 index 0000000..c37f6bc --- /dev/null +++ b/app/views/videos/_video.html.erb @@ -0,0 +1,14 @@ + + <%= time_ago_in_words(video.created_at) %> <%= t "shared.ago" %> + <%= video.name %> + <%= video.number %> + <%= video.file.filename %> + + <%= button_to_release_report video, content: t(".actions.report"), disabled: video.report_unpublished? %> + + + <% if policy(video).edit? %> + <%= link_to t(".actions.edit"), [:edit, video], class: "btn btn-light btn-sm border" %> + <% end %> + + diff --git a/app/views/videos/edit.html.erb b/app/views/videos/edit.html.erb new file mode 100644 index 0000000..c399cdf --- /dev/null +++ b/app/views/videos/edit.html.erb @@ -0,0 +1,7 @@ +
    + <%= card_header text: t(".heading"), subtext: t(".subheading"), close_action_path: [@project, :talent_releases] %> +
    + <%= render "form", video: @video, model: @video %> +
    +
    + diff --git a/app/views/videos/index.html.erb b/app/views/videos/index.html.erb new file mode 100644 index 0000000..b79c08a --- /dev/null +++ b/app/views/videos/index.html.erb @@ -0,0 +1,40 @@ +<%= product_wordmark :deliver_me, class: "small mb-3" %> + +
    + <%= bootstrap_form_with url: [@project, :videos], method: :get, remote: true, layout: :inline, id: "search" do |form| %> + <%= form.search_field :query, hide_label: true, placeholder: t("shared.search"), class: "rounded-pill-right", value: params[:query], prepend: form.button(fa_icon("search"), id: "search-button", class: "btn btn-light border rounded-pill-left") %> + <% end %> + <% if policy(Video).new? %> + <%= link_to fa_icon("plus", text: t(".actions.new")), [:landing, @project, :videos], class: "btn btn-primary" %> + <% end %> +
    + +
    + + + + + + + + + + + + + <% if @videos.any? %> + <%= render @videos %> + <% else %> + + + + <% end %> + +
    <%= t(".table_headers.upload_date") %><%= Video.human_attribute_name(:name) %><%= Video.human_attribute_name(:number) %><%= t(".table_headers.file") %><%= t(".table_headers.reports") %>
    <%= t(".empty") %>
    +
    + +
    + <%= will_paginate @videos %> +
    + +

    <%= t(".turnaround_time_message") %>

    diff --git a/app/views/videos/index.js.erb b/app/views/videos/index.js.erb new file mode 100644 index 0000000..9eb0941 --- /dev/null +++ b/app/views/videos/index.js.erb @@ -0,0 +1,2 @@ +$("#videos").html("<%= j render @videos %>"); +$("#videos_pagination").html("<%= j will_paginate @videos %>"); \ No newline at end of file diff --git a/app/views/videos/landing.html.erb b/app/views/videos/landing.html.erb new file mode 100644 index 0000000..db7711d --- /dev/null +++ b/app/views/videos/landing.html.erb @@ -0,0 +1,14 @@ +
    + <%= card_header text: t(".heading"), subtext: t(".subheading"), close_action_path: [@project, :videos] %> +
    +

    <%= t(".instructions") %>

    +
    +
    + <%= link_to t(".avid"), new_project_video_path(@project, video_editing_system: "avid"), class: "btn btn-light btn-block border p-4" %> +
    +
    + <%= link_to t(".adobe"), new_project_video_path(@project, video_editing_system: "adobe_premiere"), class: "btn btn-light btn-block border p-4" %> +
    +
    +
    +
    diff --git a/app/views/videos/new.html.erb b/app/views/videos/new.html.erb new file mode 100644 index 0000000..1622afc --- /dev/null +++ b/app/views/videos/new.html.erb @@ -0,0 +1,20 @@ +
    + <%= card_header text: t(".heading"), subtext: t(".subheading"), close_action_path: [@project, @video] %> +
    + <%= errors_summary_for @video %> + <%= render "form", video: @video, model: [@project, @video] %> +
    +
    + +<%= javascript_tag nonce: true do %> + $(document).off("turbolinks:before-visit"); + $(document).on("turbolinks:before-visit", function() { + if ($(".progress-bar").length > 0) { + let result = confirm("Leaving this page while uploading will terminate any file uploads. Are you sure you want to leave?"); + if (result === true) { + $(document).off("turbolinks:before-visit"); + } + return result; + } + }); +<% end %> diff --git a/app/views/zoom_meetings/index.html.erb b/app/views/zoom_meetings/index.html.erb new file mode 100644 index 0000000..6f19f12 --- /dev/null +++ b/app/views/zoom_meetings/index.html.erb @@ -0,0 +1,3 @@ +<%= form_with url: ZoomMeeting.new, action: :post do |form| %> + <%= form.submit "Create meeting" %> +<% end %> \ No newline at end of file diff --git a/app/views/zoom_meetings/show.html.erb b/app/views/zoom_meetings/show.html.erb new file mode 100644 index 0000000..f659ee7 --- /dev/null +++ b/app/views/zoom_meetings/show.html.erb @@ -0,0 +1 @@ +<%= link_to "Join meeting", @meeting.start_url, target: "_blank", class: "btn btn-primary" %> \ No newline at end of file diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..84888b6 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,72 @@ +module.exports = function(api) { + var validEnv = ['development', 'test', 'production'] + var currentEnv = api.env() + var isDevelopmentEnv = api.env('development') + var isProductionEnv = api.env('production') + var isTestEnv = api.env('test') + + if (!validEnv.includes(currentEnv)) { + throw new Error( + 'Please specify a valid `NODE_ENV` or ' + + '`BABEL_ENV` environment variables. Valid values are "development", ' + + '"test", and "production". Instead, received: ' + + JSON.stringify(currentEnv) + + '.' + ) + } + + return { + presets: [ + isTestEnv && [ + require('@babel/preset-env').default, + { + targets: { + node: 'current' + } + } + ], + (isProductionEnv || isDevelopmentEnv) && [ + require('@babel/preset-env').default, + { + forceAllTransforms: true, + useBuiltIns: 'entry', + corejs: 3, + modules: false, + exclude: ['transform-typeof-symbol'] + } + ] + ].filter(Boolean), + plugins: [ + require('babel-plugin-macros'), + require('@babel/plugin-syntax-dynamic-import').default, + isTestEnv && require('babel-plugin-dynamic-import-node'), + require('@babel/plugin-transform-destructuring').default, + [ + require('@babel/plugin-proposal-class-properties').default, + { + loose: true + } + ], + [ + require('@babel/plugin-proposal-object-rest-spread').default, + { + useBuiltIns: true + } + ], + [ + require('@babel/plugin-transform-runtime').default, + { + helpers: false, + regenerator: true, + corejs: false + } + ], + [ + require('@babel/plugin-transform-regenerator').default, + { + async: false + } + ] + ].filter(Boolean) + } +} diff --git a/bin/bundle b/bin/bundle new file mode 100755 index 0000000..524dfd3 --- /dev/null +++ b/bin/bundle @@ -0,0 +1,105 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 || ">= 0.a" + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../../Gemfile", __FILE__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_version + @bundler_version ||= begin + env_var_version || cli_arg_version || + lockfile_version || "#{Gem::Requirement.default}.a" + end + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + # must dup string for RG < 1.8 compatibility + activate_bundler(bundler_version.dup) + end + + def activate_bundler(bundler_version) + if Gem::Version.correct?(bundler_version) && Gem::Version.new(bundler_version).release < Gem::Version.new("2.0") + bundler_version = "< 2" + end + gem_error = activation_error_handling do + gem "bundler", bundler_version + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_version).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_version}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_version}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/bin/clockwork b/bin/clockwork new file mode 100755 index 0000000..5efea17 --- /dev/null +++ b/bin/clockwork @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'clockwork' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path("../bundle", __FILE__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("clockwork", "clockwork") diff --git a/bin/clockworkd b/bin/clockworkd new file mode 100755 index 0000000..1d416b8 --- /dev/null +++ b/bin/clockworkd @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'clockworkd' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path("../bundle", __FILE__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("clockwork", "clockworkd") diff --git a/bin/demo b/bin/demo new file mode 100755 index 0000000..d0a0621 --- /dev/null +++ b/bin/demo @@ -0,0 +1,35 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'pr_app' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path("../bundle", __FILE__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +require "parity" + +if ARGV.empty? + puts Parity::Usage.new +else + Parity::Environment.new("demo", ARGV).run +end \ No newline at end of file diff --git a/bin/development b/bin/development new file mode 100755 index 0000000..1aa9b5e --- /dev/null +++ b/bin/development @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'development' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path("../bundle", __FILE__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("parity", "development") diff --git a/bin/production b/bin/production new file mode 100755 index 0000000..3d04af7 --- /dev/null +++ b/bin/production @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'production' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path("../bundle", __FILE__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("parity", "production") diff --git a/bin/rails b/bin/rails new file mode 100755 index 0000000..0739660 --- /dev/null +++ b/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path('../config/application', __dir__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/bin/rake b/bin/rake new file mode 100755 index 0000000..1724048 --- /dev/null +++ b/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/bin/review b/bin/review new file mode 100755 index 0000000..5f1f98a --- /dev/null +++ b/bin/review @@ -0,0 +1,35 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'pr_app' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path("../bundle", __FILE__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +require "parity" + +if ARGV.empty? + puts Parity::Usage.new +else + Parity::Environment.new("review", ARGV).run +end \ No newline at end of file diff --git a/bin/rspec b/bin/rspec new file mode 100755 index 0000000..a6c7852 --- /dev/null +++ b/bin/rspec @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rspec' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path("../bundle", __FILE__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rspec-core", "rspec") diff --git a/bin/rubocopfix.sh b/bin/rubocopfix.sh new file mode 100755 index 0000000..af7c690 --- /dev/null +++ b/bin/rubocopfix.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# routes file is there because if no files were modified rubocop would fix all +git ls-files -m | xargs ls -1 2>/dev/null | grep '\.rb$' | xargs rubocop --fix ./config/routes.rb diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..8d5b93b --- /dev/null +++ b/bin/setup @@ -0,0 +1,44 @@ +#!/usr/bin/env ruby +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = File.expand_path('..', __dir__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a starting point to setup your application. + # Add necessary setup steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') + + # Install JavaScript dependencies if using Yarn + system('bin/yarn') + + puts "\n== Copying sample files ==" + unless File.exist?('.env') + cp '.env.sample', '.env' + puts "I have created the .env file for you. Please fill in the correct values inside it and run the setup again." + return + end + + puts "\n== Preparing database and seeding sample data ==" + system! 'bin/rails dev:prime' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' + + puts "\n== Adding deployment remotes ==" + system 'git remote add review https://git.heroku.com/easy-release-review.git' + system 'git remote add staging https://git.heroku.com/easy-release-staging.git' + system 'git remote add demo https://git.heroku.com/easy-release-demo.git' + system 'git remote add production https://git.heroku.com/easy-release-production.git' +end diff --git a/bin/spring b/bin/spring new file mode 100755 index 0000000..fb2ec2e --- /dev/null +++ b/bin/spring @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby + +# This file loads spring without using Bundler, in order to be fast. +# It gets overwritten when you run the `spring binstub` command. + +unless defined?(Spring) + require 'rubygems' + require 'bundler' + + lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) + spring = lockfile.specs.detect { |spec| spec.name == "spring" } + if spring + Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path + gem 'spring', spring.version + require 'spring/binstub' + end +end diff --git a/bin/staging b/bin/staging new file mode 100755 index 0000000..e37038b --- /dev/null +++ b/bin/staging @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'staging' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path("../bundle", __FILE__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("parity", "staging") diff --git a/bin/update b/bin/update new file mode 100755 index 0000000..6d73559 --- /dev/null +++ b/bin/update @@ -0,0 +1,31 @@ +#!/usr/bin/env ruby +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = File.expand_path('..', __dir__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a way to update your development environment automatically. + # Add necessary update steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') + + # Install JavaScript dependencies if using Yarn + system('bin/yarn') + + puts "\n== Updating database ==" + system! 'bin/rails db:migrate' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/bin/webpack b/bin/webpack new file mode 100755 index 0000000..008ecb2 --- /dev/null +++ b/bin/webpack @@ -0,0 +1,19 @@ +#!/usr/bin/env ruby + +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" +ENV["NODE_ENV"] ||= "development" + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require "rubygems" +require "bundler/setup" + +require "webpacker" +require "webpacker/webpack_runner" + +APP_ROOT = File.expand_path("..", __dir__) +Dir.chdir(APP_ROOT) do + Webpacker::WebpackRunner.run(ARGV) +end diff --git a/bin/webpack-dev-server b/bin/webpack-dev-server new file mode 100755 index 0000000..a931a9b --- /dev/null +++ b/bin/webpack-dev-server @@ -0,0 +1,19 @@ +#!/usr/bin/env ruby + +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" +ENV["NODE_ENV"] ||= "development" + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require "rubygems" +require "bundler/setup" + +require "webpacker" +require "webpacker/dev_server_runner" + +APP_ROOT = File.expand_path("..", __dir__) +Dir.chdir(APP_ROOT) do + Webpacker::DevServerRunner.run(ARGV) +end diff --git a/bin/yarn b/bin/yarn new file mode 100755 index 0000000..460dd56 --- /dev/null +++ b/bin/yarn @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +APP_ROOT = File.expand_path('..', __dir__) +Dir.chdir(APP_ROOT) do + begin + exec "yarnpkg", *ARGV + rescue Errno::ENOENT + $stderr.puts "Yarn executable was not detected in the system." + $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" + exit 1 + end +end diff --git a/blank_appearance_release.pdf b/blank_appearance_release.pdf new file mode 100644 index 0000000..755d973 Binary files /dev/null and b/blank_appearance_release.pdf differ diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..f7ba0b5 --- /dev/null +++ b/config.ru @@ -0,0 +1,5 @@ +# This file is used by Rack-based servers to start the application. + +require_relative 'config/environment' + +run Rails.application diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 0000000..43e7351 --- /dev/null +++ b/config/application.rb @@ -0,0 +1,42 @@ +require_relative 'boot' + +require "rails" +# Pick the frameworks you want: +require "active_model/railtie" +require "active_job/railtie" +require "active_record/railtie" +require "active_storage/engine" +require "action_controller/railtie" +require "action_mailer/railtie" +require "action_mailbox/engine" +require "action_view/railtie" +require "action_cable/engine" +require "sprockets/railtie" +require "action_text/engine" +# require "rails/test_unit/railtie" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module EasyRelease + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 6.0 + + # Settings in config/environments/* take precedence over those specified here. + # Application configuration can go into files in config/initializers + # -- all .rb files in that directory are automatically loaded after loading + # the framework and any gems in your application. + + # Don't generate system test files. + config.generators.system_tests = nil + + config.active_record.schema_format = :sql + + # Detect locale from HTTP headers + config.middleware.use Rack::Locale + + config.active_storage.replace_on_assign_to_many = false + end +end diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000..b9e460c --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,4 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. +require 'bootsnap/setup' # Speed up boot time by caching expensive operations. diff --git a/config/cable.yml b/config/cable.yml new file mode 100644 index 0000000..2067d87 --- /dev/null +++ b/config/cable.yml @@ -0,0 +1,15 @@ +development: + <% if ENV["REDIS_URL"] %> + adapter: redis + url: <%= ENV.fetch("REDIS_URL") %> + <% else %> + adapter: async + <% end %> + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: easy_release_production diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc new file mode 100644 index 0000000..d5bbdc4 --- /dev/null +++ b/config/credentials.yml.enc @@ -0,0 +1 @@ +0W15EiP+LwIYXJj9SuZmoQKFVYH7l/SWKnFaFzvLfuibNSxNtwBYj6H/jV8VMVuSE1eOZQDw+n10EPWR8jmAh2E0J5OlcxE4Q7SfsQoF0/srt5meYbyRb5QtWBx0csHZCgtQK/G2Ez8HoxILEYN01UyLeb5zNYlDbLuSUrmFxlzhcUD5f6T0cYEZ+c1s0ZaoT7kVpZf9nS89xDoaJf1XElVUgnSeTedgvx7j55dZZ+t6phl0kvSrJ2FbCe6UnBO+t8VWXPptp1W6Mv+5MlW2mNWC62g2XqMvivS8AYwL7+DvB3ZvngTlrBjl1rjpLpEppS4VgM0GUsJWAGbOFJqg9qiTtapiGKBfdWACMhFUsKgZJR0XNHFtpYGl5ysxR0rg+HC6Ao0PUBn5qEYmxHyrETERNjNfr5UX--SpJalQMTsugk97nB--/OBGVKNeOwJWnzp+Byx4yg== \ No newline at end of file diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 0000000..16f3c7d --- /dev/null +++ b/config/database.yml @@ -0,0 +1,91 @@ +# PostgreSQL. Versions 9.1 and up are supported. +# +# Install the pg driver: +# gem install pg +# On OS X with Homebrew: +# gem install pg -- --with-pg-config=/usr/local/bin/pg_config +# On OS X with MacPorts: +# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config +# On Windows: +# gem install pg +# Choose the win32 build. +# Install PostgreSQL and put its /bin directory on your path. +# +# Configure Using Gemfile +# gem 'pg' +# +default: &default + adapter: postgresql + encoding: unicode + # For details on connection pooling, see Rails configuration guide + # http://guides.rubyonrails.org/configuring.html#database-pooling + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + +development: + <<: *default + database: easy-release_development + + # The specified database role being used to connect to postgres. + # To create additional roles in postgres see `$ createuser --help`. + # When left blank, postgres will use the default role. This is + # the same name as the operating system user that initialized the database. + #username: easy-release + + # The password associated with the postgres role (username). + #password: + + # Connect on a TCP socket. Omitted by default since the client uses a + # domain socket that doesn't need configuration. Windows does not have + # domain sockets, so uncomment these lines. + #host: localhost + + # The TCP port the server listens on. Defaults to 5432. + # If your server runs on a different port number, change accordingly. + #port: 5432 + + # Schema search path. The server defaults to $user,public + #schema_search_path: myapp,sharedapp,public + + # Minimum log levels, in increasing order: + # debug5, debug4, debug3, debug2, debug1, + # log, notice, warning, error, fatal, and panic + # Defaults to warning. + #min_messages: notice + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: easy-release_test + +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%= ENV['DATABASE_URL'] %> +# +review: + <<: *default + database: easy-release_review + username: easy-release + password: <%= ENV['EASY-RELEASE_DATABASE_PASSWORD'] %> + +production: + <<: *default + database: easy-release_production + username: easy-release + password: <%= ENV['EASY-RELEASE_DATABASE_PASSWORD'] %> diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000..426333b --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative 'application' + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 0000000..53b58f2 --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,78 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable/disable caching. By default caching is disabled. + # Run rails dev:cache to toggle caching. + if Rails.root.join('tmp', 'caching-dev.txt').exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{2.days.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :amazon + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + config.action_mailer.perform_caching = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + config.assets.debug = true + + # Suppress logger output for asset requests. + config.assets.quiet = true + + # Raises error for missing translations + config.action_view.raise_on_missing_translations = true + + # Use an evented file watcher to asynchronously detect changes in source code, + # routes, locales, etc. This feature depends on the listen gem. + config.file_watcher = ActiveSupport::EventedFileUpdateChecker + + # Raises error for unpermitted controller parameters + config.action_controller.action_on_unpermitted_parameters = :raise + + # Set default values for the host and port used in URLs + config.action_controller.default_url_options = { + host: AppHost.new.domain, + port: AppHost.new.port, + protocol: AppHost.new.protocol, + } + + if ENV["REDIS_URL"] + config.active_job.queue_adapter = :sidekiq + end + + config.hosts << /[a-z0-9]+\.ngrok\.io/ +end diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 0000000..d00a722 --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,113 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] + # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? + + # Compress JavaScripts and CSS. + # Enable ES 6 compilation + # See: https://github.com/lautis/uglifier/issues/127 + config.assets.js_compressor = Uglifier.new(harmony: true) + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :amazon + + # Mount Action Cable outside main process or domain + # config.action_cable.mount_path = nil + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = AppHost.new.using_ssl? + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + config.log_tags = [:request_id] + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment) + config.active_job.queue_adapter = :sidekiq + # config.active_job.queue_name_prefix = "easy_release_#{Rails.env}" + + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Set default value for the host used in URLs + config.action_controller.default_url_options = { + host: AppHost.new.domain, + protocol: AppHost.new.protocol, + } + + # Configure ActionMailer to use SendGrid + ActionMailer::Base.smtp_settings = { + user_name: ENV['SENDGRID_USERNAME'], + password: ENV['SENDGRID_PASSWORD'], + domain: AppHost.new.domain, + address: 'smtp.sendgrid.net', + port: 587, + authentication: :plain, + enable_starttls_auto: true + } +end diff --git a/config/environments/review.rb b/config/environments/review.rb new file mode 100644 index 0000000..d00a722 --- /dev/null +++ b/config/environments/review.rb @@ -0,0 +1,113 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] + # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? + + # Compress JavaScripts and CSS. + # Enable ES 6 compilation + # See: https://github.com/lautis/uglifier/issues/127 + config.assets.js_compressor = Uglifier.new(harmony: true) + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :amazon + + # Mount Action Cable outside main process or domain + # config.action_cable.mount_path = nil + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = AppHost.new.using_ssl? + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + config.log_tags = [:request_id] + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment) + config.active_job.queue_adapter = :sidekiq + # config.active_job.queue_name_prefix = "easy_release_#{Rails.env}" + + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Set default value for the host used in URLs + config.action_controller.default_url_options = { + host: AppHost.new.domain, + protocol: AppHost.new.protocol, + } + + # Configure ActionMailer to use SendGrid + ActionMailer::Base.smtp_settings = { + user_name: ENV['SENDGRID_USERNAME'], + password: ENV['SENDGRID_PASSWORD'], + domain: AppHost.new.domain, + address: 'smtp.sendgrid.net', + port: 587, + authentication: :plain, + enable_starttls_auto: true + } +end diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 0000000..62c0971 --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,52 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{1.hour.to_i}" + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory + config.active_storage.service = :test + + config.action_mailer.perform_caching = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations + config.action_view.raise_on_missing_translations = true + + # Raises error for unpermitted controller parameters + config.action_controller.action_on_unpermitted_parameters = :raise + + ENV["ENABLE_ANALYTICS"] = "true" + ENV["BRAYNIAC_AI_API_ENDPOINT"] ||= "" +end diff --git a/config/initializers/allowed_host.rb b/config/initializers/allowed_host.rb new file mode 100644 index 0000000..b282cc4 --- /dev/null +++ b/config/initializers/allowed_host.rb @@ -0,0 +1,3 @@ +unless Rails.env.test? + Rails.application.config.hosts << AppHost.new.domain +end diff --git a/config/initializers/analytics_ruby.rb b/config/initializers/analytics_ruby.rb new file mode 100644 index 0000000..cef9709 --- /dev/null +++ b/config/initializers/analytics_ruby.rb @@ -0,0 +1,4 @@ +AnalyticsRuby = Segment::Analytics.new({ + write_key: ENV.fetch("SEGMENT_SERVER_KEY", ""), + on_error: Proc.new { |_status, msg| print msg } +}) diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb new file mode 100644 index 0000000..89d2efa --- /dev/null +++ b/config/initializers/application_controller_renderer.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# ActiveSupport::Reloader.to_prepare do +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) +# end diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb new file mode 100644 index 0000000..40271bf --- /dev/null +++ b/config/initializers/assets.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path +# Add Yarn node_modules folder to the asset load path. +Rails.application.config.assets.paths << Rails.root.join('node_modules') + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +Rails.application.config.assets.precompile += %w( contract_pdf.css ) diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb new file mode 100644 index 0000000..59385cd --- /dev/null +++ b/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/config/initializers/bootstrap_form.rb b/config/initializers/bootstrap_form.rb new file mode 100644 index 0000000..8758a53 --- /dev/null +++ b/config/initializers/bootstrap_form.rb @@ -0,0 +1,6 @@ +require_relative "../../lib/bootstrap_form_extensions" + +# Include patches to `bootstrap_form` gem +class BootstrapForm::FormBuilder + prepend BootstrapFormExtensions +end diff --git a/config/initializers/brayniac_ai.rb b/config/initializers/brayniac_ai.rb new file mode 100644 index 0000000..f4cd61f --- /dev/null +++ b/config/initializers/brayniac_ai.rb @@ -0,0 +1 @@ +require "brayniac_ai" diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb new file mode 100644 index 0000000..21b8bfe --- /dev/null +++ b/config/initializers/content_security_policy.rb @@ -0,0 +1,36 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy +# For further information see the following documentation +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + +Rails.application.config.content_security_policy do |policy| + policy.default_src :self, :https, AppHost.new.domain_with_port, :unsafe_inline + policy.font_src :self, :https, :data + policy.img_src :self, :https, :data + policy.object_src :self + policy.script_src :self, :https, AppHost.new.domain_with_port, "https://stream.mux.com", :blob, :unsafe_eval + policy.media_src :self, :https, AppHost.new.domain_with_port, "https://stream.mux.com", :data, :blob + # policy.style_src :self, :https, :unsafe_inline + # If you are using webpack-dev-server then specify webpack-dev-server host + # policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development? + policy.connect_src :self, :https, "ws://#{AppHost.new.domain_with_port}", "wss://#{AppHost.new.domain_with_port}" + + # Specify URI for violation reports + # policy.report_uri "/csp-violation-report-endpoint" +end + +# If you are using UJS then enable automatic nonce generation +Rails.application.config.content_security_policy_nonce_generator = -> (request) do + # Use the same CSP nonce for Turbolinks requests + if request.env["HTTP_TURBOLINKS_REFERRER"].present? + request.env["HTTP_X_TURBOLINKS_NONCE"] + else + SecureRandom.base64(16) + end +end + +# Report CSP violations to a specified URI +# For further information see the following documentation: +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only +# Rails.application.config.content_security_policy_report_only = true diff --git a/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb new file mode 100644 index 0000000..5a6a32d --- /dev/null +++ b/config/initializers/cookies_serializer.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Specify a serializer for the signed and encrypted cookie jars. +# Valid options are :json, :marshal, and :hybrid. +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb new file mode 100644 index 0000000..ea4814f --- /dev/null +++ b/config/initializers/cors.rb @@ -0,0 +1,13 @@ +# Be sure to restart your server when you modify this file. + +# Avoid CORS issues when API is called from the frontend app. +# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. + +# Read more: https://github.com/cyu/rack-cors + +Rails.application.config.middleware.insert_before 0, Rack::Cors do + allow do + origins 'https://stream.mux.com' + resource '*', headers: :any, methods: [:get, :post, :options] + end +end \ No newline at end of file diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000..46f8911 --- /dev/null +++ b/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] + +# Filter base64 image strings from logs +Rails.application.config.filter_parameters += ['person_photo.io', 'guardian_photo.io', 'photos.io', 'signature', 'signature_base64'] diff --git a/config/initializers/hubspot_ruby.rb b/config/initializers/hubspot_ruby.rb new file mode 100644 index 0000000..f5ce2e9 --- /dev/null +++ b/config/initializers/hubspot_ruby.rb @@ -0,0 +1,9 @@ +hubspot_api_key = ENV["HUBSPOT_API_KEY"] +hubspot_portal_id = ENV["HUBSPOT_PORTAL_ID"] + +if hubspot_api_key.present? && hubspot_portal_id.present? + Hubspot.configure( + hapikey: hubspot_api_key, + portal_id: hubspot_portal_id + ) +end \ No newline at end of file diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb new file mode 100644 index 0000000..ac033bf --- /dev/null +++ b/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/config/initializers/jsonapi.rb b/config/initializers/jsonapi.rb new file mode 100644 index 0000000..6dcade5 --- /dev/null +++ b/config/initializers/jsonapi.rb @@ -0,0 +1,83 @@ +require "jsonapi/rails" + +JSONAPI::Rails.configure do |config| + # # Set a default serializable class mapping. + # config.jsonapi_class = Hash.new { |h, k| + # names = k.to_s.split('::') + # klass = names.pop + # h[k] = [*names, "Serializable#{klass}"].join('::').safe_constantize + # } + # + # # Set a default serializable class mapping for errors. + # config.jsonapi_errors_class = Hash.new { |h, k| + # names = k.to_s.split('::') + # klass = names.pop + # h[k] = [*names, "Serializable#{klass}"].join('::').safe_constantize + # }.tap { |h| + # h[:'ActiveModel::Errors'] = JSONAPI::Rails::SerializableActiveModelErrors + # h[:Hash] = JSONAPI::Rails::SerializableErrorHash + # } + # + # # Set a default JSON API object. + # config.jsonapi_object = { + # version: '1.0' + # } + # + # # Set default cache. + # # A lambda/proc that will be eval'd in the controller context. + # config.jsonapi_cache = ->() { nil } + # + # # Uncomment the following to enable fragment caching. Make sure you + # # invalidate cache keys accordingly. + # config.jsonapi_cache = lambda { + # Rails.cache + # } + # + # # Set default exposures. + # # A lambda/proc that will be eval'd in the controller context. + # config.jsonapi_expose = lambda { + # { url_helpers: ::Rails.application.routes.url_helpers } + # } + # + # # Set default fields. + # # A lambda/proc that will be eval'd in the controller context. + # config.jsonapi_fields = ->() { nil } + # + # # Uncomment the following to have it default to the `fields` query + # # parameter. + # config.jsonapi_fields = lambda { + # fields_param = params.to_unsafe_hash.fetch(:fields, {}) + # Hash[fields_param.map { |k, v| [k.to_sym, v.split(',').map!(&:to_sym)] }] + # } + # + # # Set default include. + # # A lambda/proc that will be eval'd in the controller context. + # config.jsonapi_include = ->() { nil } + # + # # Uncomment the following to have it default to the `include` query + # # parameter. + # config.jsonapi_include = lambda { + # params[:include] + # } + # + # # Set default links. + # # A lambda/proc that will be eval'd in the controller context. + # config.jsonapi_links = ->() { {} } + # + # # Set default meta. + # # A lambda/proc that will be eval'd in the controller context. + # config.jsonapi_meta = ->() { nil } + # + # # Set a default pagination scheme. + # config.jsonapi_pagination = ->(_) { {} } + # + # # Set a logger. + # config.logger = Logger.new(STDOUT) + # + # # Uncomment the following to disable logging. + if Rails.env.test? + # too chatty on the tests, but useful in other envs + # because of the possible deserialization problems + config.logger = Logger.new('/dev/null') + end +end diff --git a/config/initializers/knock.rb b/config/initializers/knock.rb new file mode 100644 index 0000000..49f360a --- /dev/null +++ b/config/initializers/knock.rb @@ -0,0 +1,66 @@ +require "knock/version" +require "knock/authenticable" + +Knock.setup do |config| + + ## Expiration claim + ## ---------------- + ## + ## How long before a token is expired. If nil is provided, token will + ## last forever. + ## + ## Default: + config.token_lifetime = 10.years + + + ## Audience claim + ## -------------- + ## + ## Configure the audience claim to identify the recipients that the token + ## is intended for. + ## + ## Default: + # config.token_audience = nil + + ## If using Auth0, uncomment the line below + # config.token_audience = -> { Rails.application.secrets.auth0_client_id } + + ## Signature algorithm + ## ------------------- + ## + ## Configure the algorithm used to encode the token + ## + ## Default: + # config.token_signature_algorithm = 'HS256' + + ## Signature key + ## ------------- + ## + ## Configure the key used to sign tokens. + ## + ## Default: + # config.token_secret_signature_key = -> { Rails.application.secrets.secret_key_base } + # Default does not work in production, other does not work in dev + # When combined, they become: working configuration! + config.token_secret_signature_key = -> { + Rails.application.secrets.secret_key_base || Rails.application.credentials.secret_key_base + } + ## If using Auth0, uncomment the line below + # config.token_secret_signature_key = -> { JWT.base64url_decode Rails.application.secrets.auth0_client_secret } + + ## Public key + ## ---------- + ## + ## Configure the public key used to decode tokens, if required. + ## + ## Default: + # config.token_public_key = nil + + ## Exception Class + ## --------------- + ## + ## Configure the exception to be used when user cannot be found. + ## + ## Default: + # config.not_found_exception_class_name = 'ActiveRecord::RecordNotFound' +end diff --git a/config/initializers/locale.rb b/config/initializers/locale.rb new file mode 100644 index 0000000..16f0aa9 --- /dev/null +++ b/config/initializers/locale.rb @@ -0,0 +1,2 @@ +# Whitelist locales available for the application +I18n.available_locales = [:en, :es] diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb new file mode 100644 index 0000000..dc18996 --- /dev/null +++ b/config/initializers/mime_types.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/config/initializers/money.rb b/config/initializers/money.rb new file mode 100644 index 0000000..e9a59b1 --- /dev/null +++ b/config/initializers/money.rb @@ -0,0 +1,115 @@ +# encoding : utf-8 + +MoneyRails.configure do |config| + + # To set the default currency + # + # config.default_currency = :usd + + # Set default bank object + # + # Example: + # config.default_bank = EuCentralBank.new + + # Add exchange rates to current money bank object. + # (The conversion rate refers to one direction only) + # + # Example: + # config.add_rate "USD", "CAD", 1.24515 + # config.add_rate "CAD", "USD", 0.803115 + + # To handle the inclusion of validations for monetized fields + # The default value is true + # + # config.include_validations = true + + # Default ActiveRecord migration configuration values for columns: + # + # config.amount_column = { prefix: '', # column name prefix + # postfix: '_cents', # column name postfix + # column_name: nil, # full column name (overrides prefix, postfix and accessor name) + # type: :integer, # column type + # present: true, # column will be created + # null: false, # other options will be treated as column options + # default: 0 + # } + # + # config.currency_column = { prefix: '', + # postfix: '_currency', + # column_name: nil, + # type: :string, + # present: true, + # null: false, + # default: 'USD' + # } + + # Register a custom currency + # + # Example: + # config.register_currency = { + # priority: 1, + # iso_code: "EU4", + # name: "Euro with subunit of 4 digits", + # symbol: "€", + # symbol_first: true, + # subunit: "Subcent", + # subunit_to_unit: 10000, + # thousands_separator: ".", + # decimal_mark: "," + # } + + # Specify a rounding mode + # Any one of: + # + # BigDecimal::ROUND_UP, + # BigDecimal::ROUND_DOWN, + # BigDecimal::ROUND_HALF_UP, + # BigDecimal::ROUND_HALF_DOWN, + # BigDecimal::ROUND_HALF_EVEN, + # BigDecimal::ROUND_CEILING, + # BigDecimal::ROUND_FLOOR + # + # set to BigDecimal::ROUND_HALF_EVEN by default + # + # config.rounding_mode = BigDecimal::ROUND_HALF_UP + + # Set default money format globally. + # Default value is nil meaning "ignore this option". + # Example: + # + # config.default_format = { + # no_cents_if_whole: nil, + # symbol: nil, + # sign_before_symbol: nil + # } + + # If you would like to use I18n localization (formatting depends on the + # locale): + # config.locale_backend = :i18n + # + # Example (using default localization from rails-i18n): + # + # I18n.locale = :en + # Money.new(10_000_00, 'USD').format # => $10,000.00 + # I18n.locale = :es + # Money.new(10_000_00, 'USD').format # => $10.000,00 + # + # For the legacy behaviour of "per currency" localization (formatting depends + # only on currency): + config.locale_backend = :currency + # + # Example: + # Money.new(10_000_00, 'USD').format # => $10,000.00 + # Money.new(10_000_00, 'EUR').format # => €10.000,00 + # + # In case you don't need localization and would like to use default values + # (can be redefined using config.default_format): + # config.locale_backend = nil + + # Set default raise_error_on_money_parsing option + # It will be raise error if assigned different currency + # The default value is false + # + # Example: + # config.raise_error_on_money_parsing = false +end diff --git a/config/initializers/mux_ruby.rb b/config/initializers/mux_ruby.rb new file mode 100644 index 0000000..0c93816 --- /dev/null +++ b/config/initializers/mux_ruby.rb @@ -0,0 +1,4 @@ +MuxRuby.configure do |config| + config.username = ENV['MUX_TOKEN_ID'] + config.password = ENV['MUX_TOKEN_SECRET'] +end diff --git a/config/initializers/redis.rb b/config/initializers/redis.rb new file mode 100644 index 0000000..9f7214c --- /dev/null +++ b/config/initializers/redis.rb @@ -0,0 +1,3 @@ +if ENV["REDIS_URL"] + $redis = Redis.new(url: ENV["REDIS_URL"]) +end diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb new file mode 100644 index 0000000..50c6411 --- /dev/null +++ b/config/initializers/sentry.rb @@ -0,0 +1,6 @@ +if sentry_dsn = ENV['SENTRY_DSN'] + Raven.configure do |config| + config.dsn = sentry_dsn + config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s) + end +end diff --git a/config/initializers/will_paginate.rb b/config/initializers/will_paginate.rb new file mode 100644 index 0000000..fc3e63e --- /dev/null +++ b/config/initializers/will_paginate.rb @@ -0,0 +1 @@ +WillPaginate.per_page = 15 diff --git a/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb new file mode 100644 index 0000000..bbfc396 --- /dev/null +++ b/config/initializers/wrap_parameters.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/config/initializers/zoom.rb b/config/initializers/zoom.rb new file mode 100644 index 0000000..a07b9a1 --- /dev/null +++ b/config/initializers/zoom.rb @@ -0,0 +1,26 @@ +require 'zoom' +unless Rails.env.test? + Zoom.configure do |c| + c.api_key = ENV['ZOOM_API_KEY'] + c.api_secret = ENV['ZOOM_API_SECRET'] + end +end + +class Zoom::APIError < StandardError + attr_reader :status_code + def initialize(status_code) + super + @status_code = status_code + end +end + +module RaiseErrorsWithCodes + def raise_if_error!(response) + if response&.[]('code') && (response['code'] >= 300 || response['code'] == 124) + raise Zoom::APIError.new(response['code']), response['message'] + else + super + end + end +end +Zoom::Utils.singleton_class.send :prepend, RaiseErrorsWithCodes \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 0000000..6b64360 --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,995 @@ +en: + account_auths: + account_auth: + actions: + remove: Remove + confirm: Are you sure you want to remove this user from the account? + account_logo_form: + submit: Upload Logo + create: + notice: Account manager added + destroy: + alert: User has been removed from the account. + form: + submit: Add + index: + heading: Account + invitation: + heading: Add Account Manager + logo: + heading: Add Logo + update: + alert: Role could not be updated. + notice: Role has been updated. + accounts: + create: + error: Invalid sign up details. Please enter valid sign up details + notice: The account was created. Please Sign In + acquired_media_releases: + acquired_media_release: + actions: + manage: Manage + no_media: No Media + create: + notice: The acquired media release has been created + destroy: + alert: The acquired media release has been deleted + edit: + heading: Edit Acquired Media Release + form: + acquired_media_details: + heading: 1 of 3 Release Details + contract_and_rights: + heading: 3 of 3 Contract & Exploitable Rights + files: + heading: 2 of 3 Files + index: + actions: + new: Import Release + search: Search + empty: Acquired Media Releases will appear here + table_headers: + file_infos_count: No. Files + notes: Notes + signed_at: Date Signed + tags: Tags + new: + heading: Import Acquired Media Release + update: + notice: The acquired media release has been updated + activerecord: + attributes: + appearance_release: + person_address: Address + person_email: Email + person_name: Name + person_phone: Phone + person_photo: Photo + signed_on: Date + location_release: + person_company: Company + person_email: Email + person_name: Name + person_phone: Phone + person_title: Title + talent_release: + person_email: Email + person_name: Name + person_phone: Phone + errors: + messages: + attached: is not attached + content_type: is not a valid file format + admin: + accounts: + create: + notice: The account was created + index: + actions: + search: Search accounts. + empty: Accounts will appear here. + update: + notice: The account was updated + application: + header: + sign_out: Sign Out + users: + create: + notice: The user was created + destroy: + alert: The user was deleted + index: + actions: + new: New User + search: Search users + empty: Users will appear here + update: + notice: The user was updated + appearance_releases: + appearance_release: + actions: + manage: Manage + no_photos: Needs Photo + create: + failed_import: Failed to create appearance release for files listed below + no_attachments: Failed to import - no attachments + edit: + heading: Edit Appearance Release + form: + contract_and_rights: + heading: 2 of 3 Contract & Exploitable Rights + person_details: + heading: 1 of 3 Person Details + photos: + guardian_photo: + heading: Guardian Photo + heading: 3 of 3 Photo + person_photo: + heading: Person Photo + index: + actions: + new: Import Release + search: Search + empty: Appearance Releases will appear here + imported_appearance_release_missing_attachment: Person photo or contract missing for imported appearance release + table_headers: + notes: Notes + signed_at: Date Signed + tags: Tags + new: + heading: Import Appearance Release + shared: + imported_appearance_release_contract_name: Imported Contract + imported_appearance_release_headshot_name: Imported Headshot + type_filter_actions: + all_releases: All Releases + complete_releases: Complete Releases + incomplete_releases: Incomplete Releases + application: + header: + sign_out: Sign Out + sidebar: + files: Files + team_member: Team Member + blank_contracts: + new: + number_of_copies_label: Number of copies + preview_heading: Contract template preview + pdf: + do_not_copy_warning: "DO NOT COPY" + serial_number_label: "Serial Number" + signature_page: + heading: Signature Page + instructions: "By signing this signature page, as of the date listed below, I hereby agree, acknowledge and accept the terms and conditions listed in this %{releasable_name}." + bookmarks: + bookmark: + actions: + manage: Manage + empty_bookmarks: + empty: Notes will appear here + broadcasts: + broadcast: + actions: + manage: Manage + create: + notice: A live stream has been created + destroy: + alert: A live stream has been deleted + api_error: Something went wrong, please try again later after some time + index: + actions: + new: Create New Live Stream + empty: Live streams will appear here + table_headers: + broadcast_created_at: Created Date + broadcast_name: Name + broadcast_status: Status + new: + heading: + Create Live Stream + bulk_taggings: + new_bulk_tag_modal: + submit: Add + contract_downloads: + download: + failure: Your download could not be generated. + pending: "Your %{release_type} contracts are being prepared for download. You will be notified when it's ready." + success: "Your %{release_type} contracts are ready. Download now, or retrieve later in the %{downloads_folder_link} folder. %{download_button}" + contract_templates: + blank_contracts: + create: + number_of_copies_invalid_notice: Please enter valid number greater than 0 + contract_template: + actions: + copy_url: Copy Release URL + manage: Manage + qr_code: QR Code + sign: Sign + no_fee: No Fee + create: + notice: The release template has been created + destroy: + archived_failure: Failed to archive the release template + archived_notice: The release template has been archived + form: + exploitable_rights: + heading: 2 of 3 Exploitable Rights + legal: + heading: 3 of 3 Legal + release_info: + heading: 1 of 3 Release Info + index: + actions: + import: Import Release Template + new: Create New Release Template + empty: Release Templates will appear here + heading: Release Templates + table_headers: + release_type: Type of Release + signed_release_count: No. Signed Releases + new: + heading: New Release Template + contracts: + photos: + guardian_photo_heading: Guardian photo + heading: + one: Photo + other: Photos + minor_photos_heading: + one: Minor photo + other: Minor photos + signature_page: + heading: Signature Page + instructions: "By signing this signature page, as of the date listed below, I hereby agree, acknowledge and accept the terms and conditions listed in this %{releasable_name}." + directories: + destroy: + alert: The folder has been deleted + destroy_file: + alert: File deleted successfully + directories: + heading: Custom Folders + directory: + actions: + delete: Delete Folder + edit: Update Folder + confirm_delete: "WARNING: Are you sure you want to permanently delete this folder? Deleting this folder will also delete all the files stored in this folder." + edit: + heading: Update Folder + file: + manage: Manage + file_form: + heading: UPLOAD NEW FILES + manage: Manage + submit: Upload Files + form: + permissions_help: If you do not select permissions, the software will automatically set it to everyone. + new: + heading: Create New Folder + new_file: + heading: Upload new files + show: + actions: + new: Upload File + search: Search Files + empty: Uploaded files will appear here + heading: Uploaded Files + update: + notice: The folder has been updated + downloads: + download: + actions: + download: Download Zip + manage: Manage + index: + actions: + search: Search Downloads + empty: Downloads will appear here + table_headers: + download_created_at: Created At + download_name: Filename + download_status: Status + download_type: Type + errors_helper: + failure_message: "The following errors have prevented this %{model_name} from being submitted:" + file_infos: + edit: + heading: Add Media + update: + notice: The release has been updated + helpers: + help: + video: + audio_only_edl_file: If you do not upload an Audio Only EDL, the software will not generate a BiG Music Cue Sheet. + edl_file: Please follow our directions on exporting the All Tracks EDL. Failure to do so could result in inaccurate and incomplete reporting. + file: Preferred file type is .mov, however, .mp4 will be accepted. File size must be less than 5GB. + graphics_only_edl_file: If you do not upload a Graphics Only EDL, the software will not generate a Graphics Cue List. + label: + acquired_media_release: + person_address: Address + person_address_city: City + person_address_country: Country + person_address_state: State + person_address_street1: Address + person_address_street2: Address (Line 2) + person_address_zip: Zip code + person_company: Company + person_email: Email address + person_name: Name + person_phone: Phone number + person_title: Title + appearance_release: + minor: Is the person a minor? + person_address: Address + person_email: Email address + person_name: Name + person_phone: Phone number + location_release: + address_city: City + address_country: Country + address_state: State + address_street1: Address + address_street2: Address (Line 2) + address_zip: Zip code + person_address_city: City + person_address_country: Country + person_address_state: State + person_address_street1: Address + person_address_street2: Address (Line 2) + person_address_zip: Zip code + material_release: + person_address: Address + person_address_city: City + person_address_country: Country + person_address_state: State + person_address_street1: Address + person_address_street2: Address (Line 2) + person_address_zip: Zip code + person_company: Company + person_email: Email address + person_name: Name + person_phone: Phone number + person_title: Title + music_release: + person_address: Address + person_address_city: City + person_address_country: Country + person_address_state: State + person_address_street1: Address + person_address_street2: Address (Line 2) + person_address_zip: Zip code + person_company: Company + person_email: Email address + person_name: Name + person_phone: Phone number + person_title: Title + project: + predefined_client_name: Client + talent_release: + guardian_address_city: City + guardian_address_country: Guardian country + guardian_address_state: State + guardian_address_street1: Guardian address + guardian_address_street2: Guardian address (Line 2) + guardian_address_zip: Zip code + guardian_email: Guardian email address + guardian_name: Guardian name + guardian_phone: Guardian phone number + minor: Is the person a minor? + person_address_city: City + person_address_country: Country + person_address_state: State + person_address_street1: Address + person_address_street2: Address (Line 2) + person_address_zip: Zip code + person_email: Email address + person_name: Name + person_phone: Phone number + video: + edl_file: EDL file + file: Video file + name: Episode name + number: Episode number + placeholder: + acquired_media_release: + name: Collection Name + notes: Add notes here... + person_address_city: City + person_address_country: Please select a country + person_address_state: State + person_address_street1: Street + person_address_street2: Apt, Suite, etc. + person_address_zip: Zip / Postal Code + person_company: Company Name + person_email: Email + person_name: Name + person_phone: Phone + person_title: Title + appearance_release: + guardian_address: Street, Apt, City, State, Zip Code + guardian_name: Jane Doe + guardian_phone: 555-555-5555 + notes: Add notes here... + person_address: Street, Apt, City, State, Zip Code + person_email: jane.doe@example.com + person_name: Jane Doe + person_phone: 555-555-5555 + location_release: + address_city: City + address_country: Please select a country + address_state: State + address_street1: Street + address_street2: Apt, Suite, etc. + address_zip: Zip / Postal Code + name: Location Name + person_address_city: City + person_address_country: Please select a country + person_address_state: State + person_address_street1: Street + person_address_street2: Apt, Suite, etc. + person_address_zip: Zip / Postal Code + person_company: Company Name + person_email: Email + person_name: Name + person_phone: Phone + person_title: Title + material_release: + person_address_city: City + person_address_state: State + person_address_street1: Street + person_address_street2: Apt, Suite, etc. + person_address_zip: Zip / Postal Code + person_company: Company Name + person_email: Email + person_name: Name + person_phone: Phone + person_title: Title + music_release: + name: Collection Name + notes: Add notes here... + person_address_city: City + person_address_country: Please select a country + person_address_state: State + person_address_street1: Street + person_address_street2: Apt, Suite, etc. + person_address_zip: Zip / Postal Code + person_company: Company Name + person_email: Email + person_name: Name + person_phone: Phone + person_title: Title + project: + client_name: Which network is this project for? + description: A quick synopsis of the show + project_membership: + user_email: example@example.com + talent_release: + guardian_address_city: City + guardian_address_country: Please select a country + guardian_address_state: State + guardian_address_street1: Street + guardian_address_street2: Apt, Suite, etc. + guardian_address_zip: Zip / Postal Code + guardian_email: Email + guardian_name: Name + guardian_phone: Phone + notes: Add notes here... + person_address_city: City + person_address_country: Please select a country + person_address_state: State + person_address_street1: Street + person_address_street2: Apt, Suite, etc. + person_address_zip: Zip / Postal Code + person_email: Email + person_name: Name + person_phone: Phone + submit: + acquired_media_release: + create: Import Release + update: Save Changes + appearance_release: + create: Import Release + update: Save Changes + bookmark: + create: Create Note + update: Update Note + broadcast: + create: Create Live Stream + update: Save Changes + contract_template: + create: Create Release Template + directory: + create: Create Folder + new_file: Upload Files + update: Update Folder + location_release: + create: Import Release + update: Save Changes + material_release: + create: Import Release + update: Save Changes + music_release: + create: Import Music Release + update: Save Changes + talent_release: + create: Import Release + update: Save Changes + unreleased_appearance: + create: Create Issue/Concern + update: Update Issue/Concern + video: + create: Upload Video + update: Save Changes + imports: + form: + actions: + submit: Import + index: + cards: Cards + heading: Import Releases + list: List + location_releases: + create: + notice: The location release has been created + destroy: + alert: The location release has been deleted + edit: + heading: Edit Location Release + form: + contract_and_rights: + heading: 3 of 4 Contract & Exploitable Rights + filming_info: + heading: Filming Information + location_details: + heading: 1 of 4 Location Details + photos: + heading: 4 of 4 Photos + signer_details: + heading: 2 of 4 Signer Details + index: + actions: + new: Import Release + search: Search + empty: Location Releases will appear here + table_headers: + address: Address + notes: Notes + signed_at: Date Signed + tags: Tags + location_release: + actions: + manage: Manage + no_photos: Needs Photo + new: + heading: Import Location Release + update: + notice: The location release has been updated + material_releases: + create: + notice: The material release has been created + destroy: + alert: The material release has been deleted + edit: + heading: Edit Material Release (Products / Logos) + form: + contract_and_rights: + heading: 3 of 4 Contract & Exploitable Rights + material_details: + heading: 1 of 3 Material Details + photos: + heading: 4 of 4 Photos + signer_details: + heading: 2 of 4 Signer Details + index: + actions: + new: Import Release + search: Search + empty: Material Releases will appear here + table_headers: + notes: Notes + signed_at: Date Signed + tags: Tags + material_release: + actions: + manage: Manage + no_photos: Needs Photo + new: + heading: Import Material Release (Products / Logos) + update: + notice: The material release has been updated + music_releases: + create: + notice: The music release has been created + destroy: + alert: The music release has been deleted + edit: + heading: Edit Music Release + form: + composers: + heading: 4 of 6 Composers + contract_and_rights: + heading: 3 of 6 Contract & Exploitable Rights + files: + heading: 2 of 6 Files + music_details: + heading: 1 of 6 Release Details + publishers: + heading: 5 of 6 Publishers + signer_details: + heading: 6 of 6 Signer Details + index: + actions: + new: Import Release + search: Search + empty: Music Releases will appear here + table_headers: + composers_count: No. Composers + file_infos_count: No. Files + notes: Notes + publishers_count: No. Publishers + signed_at: Date Signed + tags: Tags + music_release: + actions: + manage: Manage + new: + heading: Import Music Release + update: + notice: The music release has been updated + password_resets: + create: + notice: Check your email for password reset instructions + edit: + notice: This password reset URL has expired or is invalid. Try again by clicking "Forgot your password?" below + submit: Save New Password + title: Password Reset + new: + submit: Reset Password + title: Password Reset + update: + alert: New password can't be blank + notice: Your password has been reset. You may sign in using your new password now. + photos: + edit: + heading: Add Photos + update: + notice: The release has been updated + profiles: + form: + submit: Update Profile + update: + notice: Profile has been updated successfully + project_memberships: + create: + alert: User could not be invited to the project. + notice: User has been invited to the project. + destroy: + alert: User has been removed from the project. + form: + submit: Send Invite + index: + heading: Team + invitation: + heading: Invite New Member + team_roster: + heading: + one: 1 Team Member + other: '%{count} Team Members' + zero: No Team Members + project_membership: + actions: + remove: Remove + confirm: Are you sure you want to remove this user from the project? + projects: + destroy: + alert: The project has been deleted + edit: + heading: Edit Project + empty_projects: + action: Create Your First Project + heading: Welcome + member_message: We are excited to help you organize and automate your media projects. Click on a project you are invited to and get started. + message: We are excited to help you organize and automate your media projects. Click below to create your first project and get started. + form: + confirm_delete: + one: > + "WARNING: Are you sure you want to permanently delete this project? Deleting this project will result in all release templates and all signed appearance, location, and material release contracts to be deleted. There is 1 release in this project that will be deleted by pressing OK. If you do not wish to delete the project, press cancel." + other: > + "WARNING: Are you sure you want to permanently delete this project? Deleting this project will result in all release templates and all signed appearance, location, and material release contracts to be deleted. There are %{count} releases in this project that will be deleted by pressing OK. If you do not wish to delete the project, press cancel." + features_settings: + acquired_media_release: Acquired Media Releases (Footage & Stills) + appearance_release: Appearance Releases + label: Which release categories do you require for this project? + location_release: Location Releases + material_release: Material Releases (Products / Logos) + music_release: Music Releases (Original Music) + talent_release: Talent Releases + index: + actions: + folder: Add Folder + new: Create New Project + heading: Open Projects + new: + heading: Create New Project + project: + actions: + delete: Delete + edit: Edit + team: Team + confirm_delete: + one: > + "WARNING: Are you sure you want to permanently delete this project? Deleting this project will result in all release templates and all signed appearance, talent, location, and material release contracts to be deleted. There is 1 release in this project that will be deleted by pressing OK. If you do not wish to delete the project, press cancel." + other: > + "WARNING: Are you sure you want to permanently delete this project? Deleting this project will result in all release templates and all signed appearance, talent, location, and material release contracts to be deleted. There are %{count} releases in this project that will be deleted by pressing OK. If you do not wish to delete the project, press cancel." + team_member: Team Member + show: + acquired_media_release: Acquired Media Releases (%{count}) + appearance_release: Appearance Releases (%{count}) + downloads: Downloads + location_release: Location Releases (%{count}) + material_release: Material Releases (%{count}) + music_release: Music Releases (%{count}) + report: Reports + talent_release: Talent Releases (%{count}) + public: + acquired_media_releases: + new: + acquired_media_info: + heading: Acquired Media Information + cancel: Cancel + files: + heading: File Information + legal: + heading: Legal + personal_info: + heading: Person Details + personal_info: + heading: Signer's Contact Information + signature: + heading: Signature + appearance_releases: + create: + notice: Your release has been signed. Thank you! + new: + cancel: Cancel + guardian_clause: + heading: Guardian Clause + guardian_info: + heading: Guardian Information + guardian_photo: + heading: Guardian Photo + instructions: > + Lastly, it's time for guardian to take a selfie photo! Please remove your hat and sunglasses (regular eyewear is ok), make sure that you are the only person in the photo, look straight into the camera, and say Cheese! + instructions_html: > + Congrats in appearing on %{name}. Below is the appearance release form. After scrolling down and reading the appearance release form, please enter your personal information, take a selfie photo, and press the "Submit Release" button. + legal: + heading: Legal + personal_info: + heading: Personal Information + instructions: Now, enter your personal information. + photo: + camera_instructions_html: Click Take Photo to Turn ON Camera + heading: Take a Photo + instructions: > + Lastly, it's time for you to take a selfie photo! Please remove your hat and sunglasses (regular eyewear is ok), make sure that you are the only person in the photo, look straight into the camera, and say Cheese! + no_photo: No photo yet + take_photo: Take Photo + warning: If your photo appears sideways, it will be autocorrected when you submit your release. + signature: + heading: Sign Below + location_releases: + create: + notice: Your release has been signed. Thank you! + new: + cancel: Cancel + contact_info: + heading: Signer Information + filming_info: + heading: Filming Information + legal: + heading: Legal + location_info: + heading: Location Information + signature: + heading: Sign Below + material_releases: + create: + notice: Your release has been signed. Thank you! + new: + cancel: Cancel + contact_info: + heading: Signer's Contact Information + legal: + heading: Legal + release_info: + heading: Release Information + signature: + heading: Sign Below + talent_releases: + create: + notice: Your release has been signed. Thank you! + new: + cancel: Cancel + guardian_clause: + heading: Guardian Clause + guardian_info: + heading: Guardian Information + guardian_photo: + camera_instructions_html: Click Take Photo to Turn ON Camera + heading: Guardian Photo + instructions: > + Lastly, it's time for guardian to take a selfie photo! Please remove your hat and sunglasses (regular eyewear is ok), make sure that you are the only person in the photo, look straight into the camera, and say Cheese! + no_photo: No photo yet + take_photo: Take Photo + warning: If your photo appears sideways, it will be autocorrected when you submit your release. + instructions_html: > + Congrats in appearing on %{name}. Below is the talent release form. After scrolling down and reading the talent release form, please enter your personal information, take a selfie photo, and press the "Submit Release" button. + legal: + heading: Legal + personal_info: + heading: Personal Information + instructions: Now, enter your personal information. + photo: + heading: Photos + signature: + heading: Signature + release_template_imports: + create: + error: There was a problem with importing selected templates + imported: Selected templates were imported with success + new: + actions: + import: Import Selected Templates + search: Search + empty: Release Templates from other projects will appear here + table_headers: + release_type: Type of Release + selection: Select Release + report_downloads: + download: + failure: "Your download could not be generated." + pending: "Your reports are being prepared for download. You will be notified when it's ready." + success: "Your reports are ready. Download now, or retrieve later in the %{downloads_folder_link} folder. %{download_button}" + reports: + index: + empty: Reports will appear here + heading: Reports + table_headers: + report_name: Report name + report_published_at: Published At + video_name: Episode name + video_number: Episode number + report: + actions: + download: Download + sessions: + create: + alert: "Email and/or Password is incorrect" + new: + forgot_password: Forgot your password? + submit: Sign In + title: Sign In + shared: + ago: ago + back: Back + cancel: Cancel + clear: Clear + close: Close + csv: CSV + deliver_me: Deliver + direct_me: Direct + disable_with: Loading... + edit_me: Edit + excel: Excel + files: Files + info_message: + one: An EDL event was found. Data is shown below + other: Multiple EDL events were found. Data for the first is shown below + zero: No EDL events were found. Please enter manually + me: Me + print: Print + release_me: Release + search: Search + submit_release: Submit Release + submit_release_long: I have read and agree to the above + suite: Suite + tag_multiple_releases: Add Tag + tag_multiple_releases_form: + submit: Add + tags: + form: + submit: Add + talent_releases: + create: + notice: The talent release has been created + destroy: + alert: The talent release has been deleted + edit: + heading: Edit Talent Release + form: + contract_and_rights: + heading: 2 of 3 Contract & Exploitable Rights + photos: + guardian_photo: + heading: Guardian Photo + heading: 3 of 3 Photos + talent_details: + heading: 1 of 3 Talent Details + index: + actions: + new: Import Release + search: Search + empty: Talent Releases will appear here + table_headers: + notes: Notes + signed_at: Date Signed + tags: Tags + new: + heading: Import Talent Release + talent_release: + actions: + manage: Manage + update: + notice: The talent release has been updated + user_mailer: + existing_account: + subject: You've been added as a ME Suite Account Manager + project_invitation: + subject: You've been added to a project in the ME Suite + welcome: + subject: Welcome to BiG + video_analyses: + actions: + create_bookmark: Create a Note + create_edl_event: EDL Info + create_graphics_element: Add to GFX Cue Sheet + create_release_report: Report + create_unreleased_appearance: Add Issue/Concern + create: + notice: Video and Audio analysis is re-run for the video + edl_events: + create: + info_message: + one: One EDL event for this timecode was found and is shown below + other: Multiple EDL events for this timecode were found and are shown below + zero: No EDL events for this timecode were found + show: + actions: + create_release_report: Reports + empty: Notes will appear here + video: + audio_only_edl_download: Download Audio Only EDL + edl_download: Download EDL + graphics_only_edl_download: Download Graphics Only EDL + videos: + create: + notice: The video has been created + edit: + heading: Edit Video + subheading: 2 of 2 Files + index: + actions: + new: Upload New Video + empty: Videos will appear here + heading: Video Analysis + table_headers: + file: File + reports: Reports + upload_date: Upload Date + turnaround_time_message: Please note, generating of reports can take up to 48 hours + landing: + adobe: Adobe Premiere + avid: Avid + heading: Upload Video + instructions: What video editing system are you using? + subheading: 1 of 2 Editing System + new: + heading: Upload Video + subheading: 2 of 2 Files + update: + notice: The video has been updated + video: + actions: + download: Download + edit: Edit + report: Report + generating: Generating... diff --git a/config/locales/es.yml b/config/locales/es.yml new file mode 100644 index 0000000..13abd12 --- /dev/null +++ b/config/locales/es.yml @@ -0,0 +1,184 @@ +es: + activerecord: + attributes: + appearance_release: + guardian_address: Dirección del tutor legal + guardian_name: Nómbre del tutor legal + guardian_phone: Número de teléfono del tutor legal + minor: Menor + person_address: Dirección de la persona + person_email: Dirección de correo electrónico de la persona + person_name: Nómbre de la persona + person_phone: Número de teléfono de la persona + person_photo: Foto de la persona + signature: Firma de la persona + signed_on: Fecha + project: + client_name: Nómbre del cliente del proyecto + description: Descripción del proyecto + details: Detalles adicionales del proyecto + name: Nómbre del proyecto de vídeo + producer_address: Dirección del productor + producer_name: Nómbre del productor + models: + appearance_release: Autorización de Aparacimiento + appearance_releases: + create: + failed_import: Failed to create appearance release for files listed below (ES) + no_attachments: Failed to import - no attachments (ES) + form: + photos: + guardian_photo: + heading: Guardian Photo (ES) + person_photo: + heading: Person Photo (ES) + index: + imported_appearance_release_missing_attachment: Person photo or contract missing for imported appearance release (ES) + shared: + imported_appearance_release_contract_name: Contrato Importado + imported_appearance_release_headshot_name: Retrato Importado + type_filter_actions: + all_releases: All Releases (ES) + complete_releases: Complete Releases (ES) + incomplete_releases: Incomplete Releases (ES) + blank_contracts: + new: + number_of_copies_label: Number of copies (ES) + preview_heading: Contract template preview + pdf: + do_not_copy_warning: "Do not copy (ES)" + serial_number_label: "Serial Number (ES)" + contract_templates: + blank_contracts: + create: + number_of_copies_invalid_notice: Please enter valid number greater than 0 (ES) + contracts: + photos: + guardian_photo_heading: Guardian photo (ES) + heading: + one: Photo (ES) + other: Photos (ES) + minor_photos_heading: + one: Minor photo (ES) + other: Minor photos (ES) + signature_page: + heading: Página de Firma de Autorización de Aparacimiento + instructions: Al firmar esta página de firma, a partir de la fecha indicada abajo, por la presente acepto, reconozco y acepto los términos y condiciones enumerados en esta autorización de aparacimiento. + errors: + messages: + accepted: debe ser aceptado + attached: no está conectado + blank: no puede estar en blanco + confirmation: no coincide con %{attribute} + content_type: no es un formato de archivo válido + empty: no puede estar vacío + equal_to: debe ser igual a %{count} + even: debe ser un número par + exclusion: está reservado + greater_than: debe ser mayor que %{count} + greater_than_or_equal_to: debe ser mayor o igual que %{count} + inclusion: no está incluido en la lista + invalid: es inválido + less_than: debe ser menor que %{count} + less_than_or_equal_to: debe ser menor o igual que %{count} + model_invalid: 'La validación falló: %{errors}' + not_a_number: no es un número + not_an_integer: debe ser un entero + odd: debe ser un número non + other_than: + present: debe ser en blanco + required: debe existir + taken: ya ha sido tomado + too_long: + one: es demasiado largo (máximo 1 caracter) + other: es demasiado largo (máximo %{count} caracteres) + too_short: + one: es demasiado corto (mínimo 1 caracter) + other: es demasiado corto (mínimo %{count} caracteres) + wrong_length: + one: longitud errónea (debe ser de 1 caracter) + other: longitud errónea (debe ser de %{count} caracteres) + errors_helper: + failure_message: "Los siguientes errores han impedido que se presente este %{model_name}:" + helpers: + label: + appearance_release: + guardian_address: Dirección del tutor legal + guardian_name: Nómbre del tutor legal + guardian_phone: Número de teléfono del tutor legal + minor: El firmante es un menor + person_address: Dirección + person_email: Dirección de correo electrónico + person_name: Nómbre + person_phone: Número de teléfono + project: + client_name: Nómbre del cliente del proyecto + description: Descripción del proyecto + details: Detalles adicionales del proyecto + name: Nómbre del proyecto de vídeo + producer_address: Dirección del productor + producer_name: Nómbre del productor + placeholder: + appearance_release: + person_address: Calle, Número de apartamento, Ciudad, Estado, Código Postal + person_email: jane.doe@example.com + person_name: Jane Doe + person_phone: 555-555-5555 + submit: + appearance_release: + create: Crear Autorización + broadcast: + create: Create Live Stream (ES) + update: Save Changes (ES) + create: 'Crear %{model}' + update: 'Actualizar %{model}' + public: + appearance_releases: + create: + notice: La autorización está firmada. ¡Gracias! + new: + cancel: Cancelar + guardian_clause: + heading: Guardian Clause (ES) + guardian_photo: + heading: Guardian Photo (ES) + instructions: (ES) Lastly, it's time for guardian to take a selfie photo! Please remove your hat and sunglasses (regular eyewear is ok), make sure that you are the only person in the photo, look straight into the camera, and say Cheese! (ES) + instructions_html: > + Felicitaciones por aparecer en %{name}. La autorіzación aparece a continuación. Después de leer la autorización, por favor rellena tu información personal, toma una foto, y presiona la botón "Crear Autorización". + legal: + heading: Legal + personal_info: + heading: Información Personal + instructions: Ahora, rellena su información personal. + photo: + camera_instructions_html: Haga clic en Take Photo para encender la cámara + heading: Toma una Foto + instructions: Por último, es hora de toma una foto. Por favor quítate el sombrero y las gafas del sol (las gafas regulares están bien), asegúrate de que eres la única persona en la foto, mira directamente a la cámera. + no_photo: No hay foto todavía + warning: Si su foto aparece de lado, se corregirá automáticamente cuando actualizar la autorización + signature: + clear: Despejar + heading: Firma + instructions: 'Firma Abajo:' + talent_releases: + new: + guardian_clause: + heading: Guardian Clause (ES) + guardian_photo: + camera_instructions_html: (ES) Click Take Photo to Turn ON Camera (ES) + heading: Guardian Photo (ES) + instructions: (ES) Lastly, it's time for guardian to take a selfie photo! Please remove your hat and sunglasses (regular eyewear is ok), make sure that you are the only person in the photo, look straight into the camera, and say Cheese! (ES) + no_photo: No hay foto todavía + take_photo: Take Photo (ES) + warning: (ES) If your photo appears sideways, it will be autocorrected when you submit your release. (ES) + teams: + show: + choose_project: ¿Qué proyecto de la lista de abajo asistirá? + welcome_html: Bienvenidos a la plataforma de firma autorizaciónes de %{name} + shared: + print: Print (ES) + talent_releases: + form: + photos: + guardian_photo: + heading: Guardian Photo (ES) diff --git a/config/locales/loaf.en.yml b/config/locales/loaf.en.yml new file mode 100644 index 0000000..7b2c520 --- /dev/null +++ b/config/locales/loaf.en.yml @@ -0,0 +1,6 @@ +en: + loaf: + breadcrumbs: + home: 'Home' + errors: + invalid_options: "Invalid option :%{invalid}. Valid options are: %{valid}, make sure these are the ones you are using." diff --git a/config/locales/oath.en.yml b/config/locales/oath.en.yml new file mode 100644 index 0000000..3eeebfa --- /dev/null +++ b/config/locales/oath.en.yml @@ -0,0 +1,5 @@ +en: + activerecord: + attributes: + user: + password_digest: "Password" diff --git a/config/puma.rb b/config/puma.rb new file mode 100644 index 0000000..5ed4437 --- /dev/null +++ b/config/puma.rb @@ -0,0 +1,38 @@ +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers: a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum; this matches the default thread size of Active Record. +# +max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } +threads min_threads_count, max_threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +# +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +# +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the `pidfile` that Puma will use. +pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked web server processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. +# +# preload_app! + +# Allow puma to be restarted by `rails restart` command. +plugin :tmp_restart diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..a844490 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,181 @@ +require 'oath/constraints/signed_in' +require 'sidekiq/web' + +Rails.application.routes.draw do + AVAILABLE_LOCALES_REGEX = /#{I18n.available_locales.join("|")}/ + + concern :confirmable do + resources :video_release_confirmations, only: [:new, :create, :destroy] + end + concern :contractable do + resource :contracts, only: [:show] + end + concern :notable do + resources :notes, only: [:index, :new, :create] + end + concern :photoable do + resource :photos, only: [:edit, :update] + end + concern :taggable do + resources :acts_as_taggable_on_tags, only: [:new, :create, :destroy], controller: "tags" + end + concern :file_infoable do + resource :file_infos, only: [:edit, :update] + end + + constraints AdminSignedInConstraint.new do + namespace :admin do + mount Sidekiq::Web => '/background_queue', as: :background_queue + + resources :accounts, only: [:index, :new, :create, :edit, :update, :show] + resources :users, only: [:index, :new, :create, :edit, :update, :destroy] do + resource :masquerade, only: :create + end + + root to: "accounts#index", as: :signed_in_root + end + end + + namespace :admin do + resource :masquerade, only: :destroy + end + + scope "(:locale)", locale: AVAILABLE_LOCALES_REGEX do + resource :account_session, only: [:update] + resource :session, only: [:destroy] + resource :account, only: [:new, :create, :update] + resources :account_auths, only: [:index, :create, :update, :destroy] + resources :projects, shallow: true do + resources :acquired_media_releases, except: [:show], concerns: [:contractable, :notable, :file_infoable] + resources :appearance_releases, except: [:show], concerns: [:contractable, :notable] + resources :appearance_release_imports, only: [:create] + resources :location_releases, except: [:show], concerns: [:contractable, :notable, :photoable] + resources :material_releases, except: [:show], concerns: [:contractable, :notable, :photoable] + resources :music_releases, except: [:show], concerns: [:contractable, :notable] + resources :talent_releases, except: [:show], concerns: [:contractable, :notable, :photoable] + resources :contract_templates, only: [:index, :new, :create, :destroy] do + resource :qr_codes, only: [:show], controller: "contract_templates/qr_codes" + resource :blank_contracts, only: [:show, :new, :create], controller: "contract_templates/blank_contracts" + end + resource :release_template_imports, only: [:new, :create] + resources :project_memberships, only: [:index, :create, :destroy] + resources :reports, only: [:index] + resource :contract_downloads, only: [:create] + resources :downloads, only: [:index, :destroy] + resource :report_downloads, only: [:create] + resources :videos, only: [:index, :new, :create, :edit, :update] do + collection do + get :landing + end + resources :bookmarks, only: [:new, :create, :destroy, :edit, :update] + resource :video_reports, only: [:show] + resource :graphic_reports, only: [:show] + resource :audio_reports, only: [:show] + resource :issues_and_concerns_reports, only: [:show] + resource :report_publications, only: [:create, :destroy], controller: "videos/report_publications" + resource :video_analyses, only: [:create, :show] do + resources :audio_confirmations, only: [:new, :create, :destroy], controller: "video_analyses/audio_confirmations" + resources :unreleased_appearances, only: [:new, :create, :edit, :update, :destroy], controller: "video_analyses/unreleased_appearances" + resources :edl_events, only: [:create], controller: "video_analyses/edl_events" + resources :graphics_elements, only: [:new, :create, :edit, :update, :destroy], controller: "video_analyses/graphics_elements" + resources :talent_releases, only: [:index], controller: "video_analyses/talent_releases" + resources :appearance_releases, only: [:index], controller: "video_analyses/appearance_releases" + resources :location_releases, only: [:index], controller: "video_analyses/location_releases" + resources :music_releases, only: [:index], controller: "video_analyses/music_releases" + resources :acquired_media_releases, only: [:index], controller: "video_analyses/acquired_media_releases" + resources :material_releases, only: [:index], controller: "video_analyses/material_releases" + end + end + end + resources :projects, only: [] do + resources :broadcasts, except: [:edit] do + resource :zoom_meeting, only: [:show] + end + resources :directories, except: [:index] do + member do + get :new_file + delete :destroy_file + end + end + end + resource :profile, only: [:show, :update] + resources :videos, only: [] do + resources :talent_releases, only: [], concerns: :confirmable + resources :appearance_releases, only: [], concerns: :confirmable + resources :location_releases, only: [], concerns: :confirmable + resources :acquired_media_releases, only: [], concerns: :confirmable + resources :music_releases, only: [], concerns: :confirmable + resources :material_releases, only: [], concerns: :confirmable + end + + scope module: :public do + resources :accounts, only: [] do + resources :projects, only: [] do + resources :contract_templates, only: [] do + resources :talent_releases, only: [:new, :create] + resources :appearance_releases, only: [:new, :create] + resources :acquired_media_releases, only: [:new, :create] + resources :location_releases, only: [:new, :create] + resources :material_releases, only: [:new, :create] + end + end + end + resources :broadcasts, param: :token, only: [:show, :update] do + resource :zoom_meeting, only: [:show] + end + end + + RELEASES = [:acquired_media_releases, :appearance_releases, :talent_releases, :material_releases, :location_releases] + ALL_RELEASES = RELEASES + [:music_releases] + + ALL_RELEASES.each do |release| + resources release, only: [], concerns: :taggable + end + + resources :bulk_taggings, only: [:new, :create] + + namespace :api do + scope 'v1' do + get 'sync' => 'sync#index' + post 'user_token' => 'user_token#create' + resource :profiles, only: [:show] + resources :projects, only: [:index] do + resources :broadcasts, only: [:index, :show, :update] + RELEASES.each do |release| + resources release, only: [:index] + end + resources :contract_templates, only: [:index] + end + resources :contract_templates, only: [:show] do + RELEASES.each do |release| + resources release, controller: release, only: [:create] + end + end + + (RELEASES - [:appearance_releases]).each do |release| + resources release, only: [:show, :update] do + resources :notes, controller: "notes", only: [:create, :index] + end + end + + resources :appearance_releases, only: [:show] do + resources :notes, controller: "notes", only: [:create, :index] + end + end + end + + # TODO: what's the format on this? JSON only? + resources :notifications, only: [:create] + resources :stream_notifications, only: [:create] + resources :zoom_notifications, only: [:create] + get '/multipart_signatures', to: 'multipart_signatures#create', as: :multipart_signatures + + get "cookies_disabled" => 'pages#show', id: "cookies_disabled", as: :cookies_disabled + get "accountless_user" => 'pages#show', id: "accountless_user", as: :accountless_user + + resource :session, only: [:new, :create] + resources :password_resets, only: [:new, :create, :edit, :update] + + root to: 'projects#index', as: :signed_in_root + end +end diff --git a/config/spring.rb b/config/spring.rb new file mode 100644 index 0000000..db5bf13 --- /dev/null +++ b/config/spring.rb @@ -0,0 +1,6 @@ +Spring.watch( + ".ruby-version", + ".rbenv-vars", + "tmp/restart.txt", + "tmp/caching-dev.txt" +) diff --git a/config/storage.yml b/config/storage.yml new file mode 100644 index 0000000..ca9fc81 --- /dev/null +++ b/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +amazon: + service: S3 + access_key_id: <%= ENV["AWS_ACCESS_KEY_ID"] %> + secret_access_key: <%= ENV["AWS_SECRET_ACCESS_KEY"] %> + region: <%= ENV.fetch("AWS_REGION") { "us-east-1" } %> + bucket: <%= ENV["AWS_BUCKET"] %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket + +# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# path: your_azure_storage_path +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/config/webpack/development.js b/config/webpack/development.js new file mode 100644 index 0000000..c5edff9 --- /dev/null +++ b/config/webpack/development.js @@ -0,0 +1,5 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'development' + +const environment = require('./environment') + +module.exports = environment.toWebpackConfig() diff --git a/config/webpack/environment.js b/config/webpack/environment.js new file mode 100644 index 0000000..d16d9af --- /dev/null +++ b/config/webpack/environment.js @@ -0,0 +1,3 @@ +const { environment } = require('@rails/webpacker') + +module.exports = environment diff --git a/config/webpack/production.js b/config/webpack/production.js new file mode 100644 index 0000000..be0f53a --- /dev/null +++ b/config/webpack/production.js @@ -0,0 +1,5 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'production' + +const environment = require('./environment') + +module.exports = environment.toWebpackConfig() diff --git a/config/webpack/test.js b/config/webpack/test.js new file mode 100644 index 0000000..c5edff9 --- /dev/null +++ b/config/webpack/test.js @@ -0,0 +1,5 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'development' + +const environment = require('./environment') + +module.exports = environment.toWebpackConfig() diff --git a/config/webpacker.yml b/config/webpacker.yml new file mode 100644 index 0000000..17788dd --- /dev/null +++ b/config/webpacker.yml @@ -0,0 +1,101 @@ +# Note: You must restart bin/webpack-dev-server for changes to take effect + +default: &default + source_path: app/javascript + source_entry_path: packs + public_root_path: public + public_output_path: packs + cache_path: tmp/cache/webpacker + check_yarn_integrity: false + webpack_compile_output: false + + # Additional paths webpack should lookup modules + # ['app/assets', 'engine/foo/app/assets'] + resolved_paths: [] + + # Reload manifest.json on all requests so we reload latest compiled packs + cache_manifest: false + + # Extract and emit a css file + extract_css: false + + static_assets_extensions: + - .jpg + - .jpeg + - .png + - .gif + - .tiff + - .ico + - .svg + - .eot + - .otf + - .ttf + - .woff + - .woff2 + + extensions: + - .mjs + - .js + - .sass + - .scss + - .css + - .module.sass + - .module.scss + - .module.css + - .png + - .svg + - .gif + - .jpeg + - .jpg + +development: + <<: *default + compile: true + + # Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules + check_yarn_integrity: true + + # Reference: https://webpack.js.org/configuration/dev-server/ + dev_server: + https: false + host: localhost + port: 3035 + public: localhost:3035 + hmr: false + # Inline should be set to true if using HMR + inline: true + overlay: true + compress: true + disable_host_check: true + use_local_ip: false + quiet: false + headers: + 'Access-Control-Allow-Origin': '*' + watch_options: + ignored: '**/node_modules/**' + + +test: + <<: *default + compile: true + + # Compile test packs to a separate directory + public_output_path: packs-test + +review: + <<: *default + compile: false + extract_css: true + cache_manifest: true + +production: + <<: *default + + # Production depends on precompilation of packs prior to booting for performance. + compile: false + + # Extract and emit a css file + extract_css: true + + # Cache manifest.json for performance + cache_manifest: true diff --git a/db/data_migrations/20190808191816_populate_releasable_type_for_file_infos.rb b/db/data_migrations/20190808191816_populate_releasable_type_for_file_infos.rb new file mode 100644 index 0000000..d1e21d1 --- /dev/null +++ b/db/data_migrations/20190808191816_populate_releasable_type_for_file_infos.rb @@ -0,0 +1,9 @@ +class PopulateReleasableTypeForFileInfos < ActiveRecord::DataMigration + def up + say "Populating FileInfo releasable type" + + FileInfo.update_all(releasable_type: "AcquiredMediaRelease") + + say "Done." + end +end \ No newline at end of file diff --git a/db/data_migrations/20190822215434_move_music_release_confirmations_to_audio_confirmations.rb b/db/data_migrations/20190822215434_move_music_release_confirmations_to_audio_confirmations.rb new file mode 100644 index 0000000..4d42ef9 --- /dev/null +++ b/db/data_migrations/20190822215434_move_music_release_confirmations_to_audio_confirmations.rb @@ -0,0 +1,28 @@ +class MoveMusicReleaseConfirmationsToAudioConfirmations < ActiveRecord::DataMigration + def up + say "Moving Music Release Confirmations to Audio Confirmations" + + VideoReleaseConfirmation.where(releasable_type: "MusicRelease").find_each do |music_release_confirmation| + AudioConfirmation.create!( + video_id: music_release_confirmation.video_id, + channel: music_release_confirmation.channel, + timecode_in: music_release_confirmation.timecode_in, + timecode_out: music_release_confirmation.timecode_out, + duration: music_release_confirmation.duration, + source_file_name: music_release_confirmation.source_file_name, + clip_name: music_release_confirmation.clip_name, + description: music_release_confirmation.description, + time_elapsed: music_release_confirmation.time_elapsed, + music_type: music_release_confirmation.music_type, + music_category: music_release_confirmation.music_category, + composer_info: music_release_confirmation.releasable.composers.map { |composer| "#{composer.name}, #{composer.affiliation}, #{composer.percentage}%" }.join("|"), + publisher_info: music_release_confirmation.releasable.publishers.map { |publisher| "#{publisher.name}, #{publisher.affiliation}, #{publisher.percentage}%" }.join("|"), + catalog: "", + title: "", + time_elapsed: music_release_confirmation.time_elapsed, + ) + end + + say "Done." + end +end \ No newline at end of file diff --git a/db/data_migrations/20190823155619_remove_music_release_confirmations.rb b/db/data_migrations/20190823155619_remove_music_release_confirmations.rb new file mode 100644 index 0000000..c7967bd --- /dev/null +++ b/db/data_migrations/20190823155619_remove_music_release_confirmations.rb @@ -0,0 +1,9 @@ +class RemoveMusicReleaseConfirmations < ActiveRecord::DataMigration + def up + say "Removing MusicReleaseConfirmations" + + VideoReleaseConfirmation.where(releasable_type: "MusicRelease").destroy_all + + say "Done." + end +end \ No newline at end of file diff --git a/db/data_migrations/20190905151654_update_plan_uids_to_me_suite.rb b/db/data_migrations/20190905151654_update_plan_uids_to_me_suite.rb new file mode 100644 index 0000000..03ea0e4 --- /dev/null +++ b/db/data_migrations/20190905151654_update_plan_uids_to_me_suite.rb @@ -0,0 +1,9 @@ +class UpdatePlanUidsToMeSuite < ActiveRecord::DataMigration + def up + say "Migrating Plan UIDs to all be me_suite" + + Account.update_all(plan_uid: "me_suite") + + say "Done." + end +end \ No newline at end of file diff --git a/db/data_migrations/20191007195849_set_graphic_type_for_graphic_elements.rb b/db/data_migrations/20191007195849_set_graphic_type_for_graphic_elements.rb new file mode 100644 index 0000000..7064a73 --- /dev/null +++ b/db/data_migrations/20191007195849_set_graphic_type_for_graphic_elements.rb @@ -0,0 +1,17 @@ +class SetGraphicTypeForGraphicElements < ActiveRecord::DataMigration + def up + say "Setting #graphic_type for GraphicElements.." + + GraphicsElement.update_all(graphic_type: "Other") + + say "Done." + end + + def down + say "Unsetting #graphic_type for GraphicElements.." + + GraphicsElement.update_all(graphic_type: nil) + + say "Done." + end +end diff --git a/db/data_migrations/20191011231236_set_territory_and_term_for_acquired_media_releases.rb b/db/data_migrations/20191011231236_set_territory_and_term_for_acquired_media_releases.rb new file mode 100644 index 0000000..8345108 --- /dev/null +++ b/db/data_migrations/20191011231236_set_territory_and_term_for_acquired_media_releases.rb @@ -0,0 +1,22 @@ +class SetTerritoryAndTermForAcquiredMediaReleases < ActiveRecord::DataMigration + def up + say "Setting AcquiredMediaRelease#term and #territory.." + + AcquiredMediaRelease.find_each do |acquired_media_release| + acquired_media_release.update( + term: Term.find_by(label: acquired_media_release.term_old), + territory: Territory.find_by(label: acquired_media_release.territory_old), + ) + end + + say "Done." + end + + def down + say "Un-setting AcquiredMediaRelease#term and #territory.." + + AcquiredMediaRelease.update_all(term: nil, territory: nil) + + say "Done." + end +end diff --git a/db/data_migrations/20191022200146_convert_releasable_notes_to_collection.rb b/db/data_migrations/20191022200146_convert_releasable_notes_to_collection.rb new file mode 100644 index 0000000..a5ee755 --- /dev/null +++ b/db/data_migrations/20191022200146_convert_releasable_notes_to_collection.rb @@ -0,0 +1,32 @@ +class ConvertReleasableNotesToCollection < ActiveRecord::DataMigration + RELEASABLE_TYPES = [ + TalentRelease, + AppearanceRelease, + LocationRelease, + AcquiredMediaRelease, + MaterialRelease, + MusicRelease, + ] + + def up + RELEASABLE_TYPES.each { |type| convert_notes_to_collection_for(type) } + say "Done." + end + + private + + def convert_notes_to_collection_for(releasable_type) + say "Converting #{releasable_type}#notes to collection..." + + releasable_type.where.not(notes_old: nil).find_each do |releasable| + content = releasable[:notes_old] + user = releasable.project.account.users.order(:created_at).first + Note.create(content: content, user: user, notable: releasable) + end + end + + class Note < ActiveRecord::Base + belongs_to :user + belongs_to :notable, polymorphic: true + end +end diff --git a/db/data_migrations/20200116153749_new_permission_system.rb b/db/data_migrations/20200116153749_new_permission_system.rb new file mode 100644 index 0000000..91f5a40 --- /dev/null +++ b/db/data_migrations/20200116153749_new_permission_system.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +class NewPermissionSystem < ActiveRecord::DataMigration + def up + say 'Creating AccountAuths and ProjectMemberships for existing Projects...' + + User.all.each do |user| + next if user.account_id_old.nil? + + role = user.role_old == 0 ? :associate : :account_manager + account = Account.find(user.account_id_old) + + # Create a membership to the account for the user + AccountAuth.create(user: user, account: account, role: role) + + # If the user is not an account manager, create a membership to each project in the account for the user + if role != :account_manager + account.projects.each do |project| + ProjectMembership.create!(project: project, user: user) + end + end + end + + say 'Done.' + end + + def down + say 'Destroying new permission system artifacts' + ProjectMembership.destroy_all + AccountAuth.destroy_all + say 'Done.' + end +end diff --git a/db/data_migrations/20200122142600_change_permissions_custom_folders.rb b/db/data_migrations/20200122142600_change_permissions_custom_folders.rb new file mode 100644 index 0000000..ea80903 --- /dev/null +++ b/db/data_migrations/20200122142600_change_permissions_custom_folders.rb @@ -0,0 +1,13 @@ +class ChangePermissionsCustomFolders < ActiveRecord::DataMigration + def up + say 'Changing permissions of custom folders for existing records...' + + project_manager_directory_ids = Directory.where(permissions: "Account Managers Only").map(&:id) + account_manager_directory_ids = Directory.where(permissions: "Account Managers & Project Managers").map(&:id) + + Directory.where(id: project_manager_directory_ids).update_all(permissions: "Account Managers & Project Managers") + Directory.where(id: account_manager_directory_ids).update_all(permissions: "Account Managers Only") + + say 'Done' + end +end \ No newline at end of file diff --git a/db/data_migrations/20200123133625_migrate_internal_tags.rb b/db/data_migrations/20200123133625_migrate_internal_tags.rb new file mode 100644 index 0000000..ccc65f4 --- /dev/null +++ b/db/data_migrations/20200123133625_migrate_internal_tags.rb @@ -0,0 +1,17 @@ +class MigrateInternalTags < ActiveRecord::DataMigration + def up + say 'Migrating tag context to internal tags list...' + + ActsAsTaggableOn::Tagging.update_all(context: "internal_tags") + + say 'Done' + end + + def down + say 'Migrating tag context back to default tags list...' + + ActsAsTaggableOn::Tagging.update_all(context: "tags") + + say 'Done' + end +end diff --git a/db/data_migrations/20200304105237_make_contract_templates_fees_valid.rb b/db/data_migrations/20200304105237_make_contract_templates_fees_valid.rb new file mode 100644 index 0000000..cf19d63 --- /dev/null +++ b/db/data_migrations/20200304105237_make_contract_templates_fees_valid.rb @@ -0,0 +1,5 @@ +class MakeContractTemplatesFeesValid < ActiveRecord::DataMigration + def up + ContractTemplate.where("fee_cents < 0 or fee_cents > 9999999999").update_all("fee_cents = 0") + end +end diff --git a/db/data_migrations/20200309103506_migrate_person_name_field_for_all_releases.rb b/db/data_migrations/20200309103506_migrate_person_name_field_for_all_releases.rb new file mode 100644 index 0000000..95ef721 --- /dev/null +++ b/db/data_migrations/20200309103506_migrate_person_name_field_for_all_releases.rb @@ -0,0 +1,17 @@ +class MigratePersonNameFieldForAllReleases < ActiveRecord::DataMigration + RELEASES = ["AcquiredMediaRelease", "AppearanceRelease", "LocationRelease", "MaterialRelease", "MusicRelease", "TalentRelease"] + + def up + say 'Migrating person name ...' + + RELEASES.each do |release| + release.constantize.find_each do |record| + if record.person_name_old.present? + record.update(person_name: record.person_name_old) + end + end + end + + say 'Done' + end +end \ No newline at end of file diff --git a/db/data_migrations/20200323221219_migrate_guardian_name_for_all_releases.rb b/db/data_migrations/20200323221219_migrate_guardian_name_for_all_releases.rb new file mode 100644 index 0000000..19e0e83 --- /dev/null +++ b/db/data_migrations/20200323221219_migrate_guardian_name_for_all_releases.rb @@ -0,0 +1,17 @@ +class MigrateGuardianNameForAllReleases < ActiveRecord::DataMigration + RELEASES = ["AppearanceRelease", "TalentRelease"] + + def up + say 'Migrating guardian name ...' + + RELEASES.each do |release| + release.constantize.find_each do |record| + if record.guardian_name_old.present? + record.update(guardian_name: record.guardian_name_old) + end + end + end + + say 'Done' + end +end \ No newline at end of file diff --git a/db/data_migrations/20200410183245_set_email_for_notes.rb b/db/data_migrations/20200410183245_set_email_for_notes.rb new file mode 100644 index 0000000..270ec32 --- /dev/null +++ b/db/data_migrations/20200410183245_set_email_for_notes.rb @@ -0,0 +1,21 @@ +class SetEmailForNotes < ActiveRecord::DataMigration + def up + say "Setting #email for Notes..." + + Note.find_each do |note| + if note.user.present? && note.user.email.present? + note.update_column(:email, note.user.email) + end + end + + say "Done." + end + + def down + say "Unsetting #email for Notes..." + + Note.update_all(email: nil) + + say "Done." + end +end diff --git a/db/data_migrations/20200414124922_generate_tokens_for_existing_broadcasts.rb b/db/data_migrations/20200414124922_generate_tokens_for_existing_broadcasts.rb new file mode 100644 index 0000000..0867f2b --- /dev/null +++ b/db/data_migrations/20200414124922_generate_tokens_for_existing_broadcasts.rb @@ -0,0 +1,7 @@ +class GenerateTokensForExistingBroadcasts < ActiveRecord::DataMigration + def up + Broadcast.where(token: nil).each do |broadcast| + broadcast.regenerate_token + end + end +end \ No newline at end of file diff --git a/db/data_migrations/20200420145535_give_names_to_imported_appearance_releases.rb b/db/data_migrations/20200420145535_give_names_to_imported_appearance_releases.rb new file mode 100644 index 0000000..741e5fa --- /dev/null +++ b/db/data_migrations/20200420145535_give_names_to_imported_appearance_releases.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class GiveNamesToImportedAppearanceReleases < ActiveRecord::DataMigration + def up + AppearanceRelease.where(person_first_name: nil).each do |ar| + contract_no = AppearanceRelease.random_contract_number.to_s + if ar.person_photo.attached? + ar.update(person_first_name: I18n.t('appearance_releases.shared.imported_appearance_release_headshot_name'), person_last_name: contract_no) + else + ar.update(person_first_name: I18n.t('appearance_releases.shared.imported_appearance_release_contract_name'), person_last_name: contract_no) + end + end + end +end diff --git a/db/data_migrations/20200424165527_assign_zoom_meetings_to_projects.rb b/db/data_migrations/20200424165527_assign_zoom_meetings_to_projects.rb new file mode 100644 index 0000000..270439f --- /dev/null +++ b/db/data_migrations/20200424165527_assign_zoom_meetings_to_projects.rb @@ -0,0 +1,16 @@ +class AssignZoomMeetingsToProjects < ActiveRecord::DataMigration + def up + ZoomMeeting.find_each do |zm| + if zm.respond_to?(:broadcast_id) && zm.broadcast_id.present? + broadcast = Broadcast.find_by(id: zm.broadcast_id) + if broadcast.present? + zm.update_column(:project_id, broadcast.project_id) + end + end + end + end + + def down + ZoomMeeting.update_all(project_id: nil) + end +end diff --git a/db/data_migrations/20200505164358_fix_invalid_broadcast_statuses.rb b/db/data_migrations/20200505164358_fix_invalid_broadcast_statuses.rb new file mode 100644 index 0000000..6429245 --- /dev/null +++ b/db/data_migrations/20200505164358_fix_invalid_broadcast_statuses.rb @@ -0,0 +1,10 @@ +class FixInvalidBroadcastStatuses < ActiveRecord::DataMigration + def up + say "Fixing invalid Broadcast#status..." + + broadcasts_with_broken_statuses = Broadcast.where.not(status: Broadcast.statuses.values) + broadcasts_with_broken_statuses.update_all(status: nil) + + say "Done." + end +end diff --git a/db/migrate/20171206201733_create_active_storage_tables.active_storage.rb b/db/migrate/20171206201733_create_active_storage_tables.active_storage.rb new file mode 100644 index 0000000..360e0d1 --- /dev/null +++ b/db/migrate/20171206201733_create_active_storage_tables.active_storage.rb @@ -0,0 +1,26 @@ +# This migration comes from active_storage (originally 20170806125915) +class CreateActiveStorageTables < ActiveRecord::Migration[5.2] + def change + create_table :active_storage_blobs do |t| + t.string :key, null: false + t.string :filename, null: false + t.string :content_type + t.text :metadata + t.bigint :byte_size, null: false + t.string :checksum, null: false + t.datetime :created_at, null: false + + t.index [ :key ], unique: true + end + + create_table :active_storage_attachments do |t| + t.string :name, null: false + t.references :record, null: false, polymorphic: true, index: false + t.references :blob, null: false + + t.datetime :created_at, null: false + + t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true + end + end +end diff --git a/db/migrate/20171226201818_create_users.rb b/db/migrate/20171226201818_create_users.rb new file mode 100644 index 0000000..0f53443 --- /dev/null +++ b/db/migrate/20171226201818_create_users.rb @@ -0,0 +1,12 @@ +class CreateUsers < ActiveRecord::Migration[5.2] + def change + create_table :users do |t| + t.string :email, null: false + t.string :password_digest, null: false + + t.timestamps null: false + end + + add_index :users, :email, unique: true + end +end diff --git a/db/migrate/20171231154902_create_projects.rb b/db/migrate/20171231154902_create_projects.rb new file mode 100644 index 0000000..6734def --- /dev/null +++ b/db/migrate/20171231154902_create_projects.rb @@ -0,0 +1,15 @@ +class CreateProjects < ActiveRecord::Migration[5.2] + def change + create_table :projects do |t| + t.string :name, null: false + t.string :client_name + t.string :producer_name + t.string :producer_address + t.text :description + t.text :details + t.belongs_to :user, foreign_key: true, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20180103145108_create_appearance_releases.rb b/db/migrate/20180103145108_create_appearance_releases.rb new file mode 100644 index 0000000..7b0b5af --- /dev/null +++ b/db/migrate/20180103145108_create_appearance_releases.rb @@ -0,0 +1,13 @@ +class CreateAppearanceReleases < ActiveRecord::Migration[5.2] + def change + create_table :appearance_releases do |t| + t.string :person_name, null: false + t.string :person_address, null: false + t.string :person_phone, null: false + t.string :person_ssn + t.belongs_to :project, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20180103154125_add_sample_to_projects.rb b/db/migrate/20180103154125_add_sample_to_projects.rb new file mode 100644 index 0000000..f7cb50e --- /dev/null +++ b/db/migrate/20180103154125_add_sample_to_projects.rb @@ -0,0 +1,5 @@ +class AddSampleToProjects < ActiveRecord::Migration[5.2] + def change + add_column :projects, :sample, :boolean, default: false + end +end diff --git a/db/migrate/20180110185920_add_guardian_fields_to_appearance_releases.rb b/db/migrate/20180110185920_add_guardian_fields_to_appearance_releases.rb new file mode 100644 index 0000000..c678962 --- /dev/null +++ b/db/migrate/20180110185920_add_guardian_fields_to_appearance_releases.rb @@ -0,0 +1,9 @@ +class AddGuardianFieldsToAppearanceReleases < ActiveRecord::Migration[5.2] + def change + add_column :appearance_releases, :minor, :boolean, default: false + add_column :appearance_releases, :guardian_address, :string + add_column :appearance_releases, :guardian_name, :string + add_column :appearance_releases, :guardian_phone, :string + add_column :appearance_releases, :guardian_ssn, :string + end +end diff --git a/db/migrate/20180118222351_add_encrypted_person_ssn_to_appearance_releases.rb b/db/migrate/20180118222351_add_encrypted_person_ssn_to_appearance_releases.rb new file mode 100644 index 0000000..fc884ca --- /dev/null +++ b/db/migrate/20180118222351_add_encrypted_person_ssn_to_appearance_releases.rb @@ -0,0 +1,43 @@ +class AddEncryptedPersonSsnToAppearanceReleases < ActiveRecord::Migration[5.2] + def up + add_column :appearance_releases, :encrypted_person_ssn, :string + add_column :appearance_releases, :encrypted_person_ssn_iv, :string + + migrate_data_up! + + remove_column :appearance_releases, :person_ssn, :string + end + + def down + add_column :appearance_releases, :person_ssn, :string + + migrate_data_down! + + remove_column :appearance_releases, :encrypted_person_ssn + remove_column :appearance_releases, :encrypted_person_ssn_iv + end + + private + + def migrate_data_up! + say "Encrypting person SSNs..." + + AppearanceRelease.find_each do |appearance_release| + # Access the value of the attribute directly using hash syntax then use the accessor to encrypt it + appearance_release.update!(person_ssn: appearance_release[:person_ssn]) + end + + say "Done." + end + + def migrate_data_down! + say "Decrypting person SSNs..." + + AppearanceRelease.find_each do |appearance_release| + appearance_release[:person_ssn] = appearance_release.person_ssn + appearance_release.save! + end + + say "Done." + end +end diff --git a/db/migrate/20180126234124_add_headshot_collection_uid_to_projects.rb b/db/migrate/20180126234124_add_headshot_collection_uid_to_projects.rb new file mode 100644 index 0000000..aaa123a --- /dev/null +++ b/db/migrate/20180126234124_add_headshot_collection_uid_to_projects.rb @@ -0,0 +1,5 @@ +class AddHeadshotCollectionUidToProjects < ActiveRecord::Migration[5.2] + def change + add_column :projects, :headshot_collection_uid, :string + end +end diff --git a/db/migrate/20180207174905_create_videos.rb b/db/migrate/20180207174905_create_videos.rb new file mode 100644 index 0000000..fcf9e4f --- /dev/null +++ b/db/migrate/20180207174905_create_videos.rb @@ -0,0 +1,9 @@ +class CreateVideos < ActiveRecord::Migration[5.2] + def change + create_table :videos do |t| + t.belongs_to :project, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20180207175033_add_analysis_fields_to_videos.rb b/db/migrate/20180207175033_add_analysis_fields_to_videos.rb new file mode 100644 index 0000000..49856be --- /dev/null +++ b/db/migrate/20180207175033_add_analysis_fields_to_videos.rb @@ -0,0 +1,6 @@ +class AddAnalysisFieldsToVideos < ActiveRecord::Migration[5.2] + def change + add_column :videos, :analysis_uid, :string + add_column :videos, :analysis_status, :integer, default: 0 + end +end diff --git a/db/migrate/20180213203308_add_index_for_analysis_uid_to_videos.rb b/db/migrate/20180213203308_add_index_for_analysis_uid_to_videos.rb new file mode 100644 index 0000000..b266ec8 --- /dev/null +++ b/db/migrate/20180213203308_add_index_for_analysis_uid_to_videos.rb @@ -0,0 +1,5 @@ +class AddIndexForAnalysisUidToVideos < ActiveRecord::Migration[5.2] + def change + add_index :videos, :analysis_uid + end +end diff --git a/db/migrate/20180326025014_add_notes_to_appearance_releases.rb b/db/migrate/20180326025014_add_notes_to_appearance_releases.rb new file mode 100644 index 0000000..9480b16 --- /dev/null +++ b/db/migrate/20180326025014_add_notes_to_appearance_releases.rb @@ -0,0 +1,5 @@ +class AddNotesToAppearanceReleases < ActiveRecord::Migration[5.2] + def change + add_column :appearance_releases, :notes, :text + end +end diff --git a/db/migrate/20180418183903_add_team_name_to_users.rb b/db/migrate/20180418183903_add_team_name_to_users.rb new file mode 100644 index 0000000..1da50e9 --- /dev/null +++ b/db/migrate/20180418183903_add_team_name_to_users.rb @@ -0,0 +1,6 @@ +class AddTeamNameToUsers < ActiveRecord::Migration[5.2] + def change + add_column :users, :team_name, :string + add_index :users, :team_name, unique: true + end +end diff --git a/db/migrate/20180517183044_add_analysis_started_at_to_videos.rb b/db/migrate/20180517183044_add_analysis_started_at_to_videos.rb new file mode 100644 index 0000000..0fd1a94 --- /dev/null +++ b/db/migrate/20180517183044_add_analysis_started_at_to_videos.rb @@ -0,0 +1,5 @@ +class AddAnalysisStartedAtToVideos < ActiveRecord::Migration[5.2] + def change + add_column :videos, :analysis_started_at, :datetime + end +end diff --git a/db/migrate/20180531175610_change_column_null_for_appearance_releases.rb b/db/migrate/20180531175610_change_column_null_for_appearance_releases.rb new file mode 100644 index 0000000..a5b2648 --- /dev/null +++ b/db/migrate/20180531175610_change_column_null_for_appearance_releases.rb @@ -0,0 +1,6 @@ +class ChangeColumnNullForAppearanceReleases < ActiveRecord::Migration[5.2] + def change + change_column_null(:appearance_releases, :person_address, true) + change_column_null(:appearance_releases, :person_phone, true) + end +end diff --git a/db/migrate/20180531202216_add_person_email_to_appearance_releases.rb b/db/migrate/20180531202216_add_person_email_to_appearance_releases.rb new file mode 100644 index 0000000..164588a --- /dev/null +++ b/db/migrate/20180531202216_add_person_email_to_appearance_releases.rb @@ -0,0 +1,5 @@ +class AddPersonEmailToAppearanceReleases < ActiveRecord::Migration[5.2] + def change + add_column :appearance_releases, :person_email, :string + end +end diff --git a/db/migrate/20180609203232_add_locale_to_appearance_releases.rb b/db/migrate/20180609203232_add_locale_to_appearance_releases.rb new file mode 100644 index 0000000..2ca9a72 --- /dev/null +++ b/db/migrate/20180609203232_add_locale_to_appearance_releases.rb @@ -0,0 +1,5 @@ +class AddLocaleToAppearanceReleases < ActiveRecord::Migration[5.2] + def change + add_column :appearance_releases, :locale, :string + end +end diff --git a/db/migrate/20180723225854_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb b/db/migrate/20180723225854_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb new file mode 100644 index 0000000..461eae4 --- /dev/null +++ b/db/migrate/20180723225854_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb @@ -0,0 +1,36 @@ +# This migration comes from acts_as_taggable_on_engine (originally 1) +if ActiveRecord.gem_version >= Gem::Version.new('5.0') + class ActsAsTaggableOnMigration < ActiveRecord::Migration[4.2]; end +else + class ActsAsTaggableOnMigration < ActiveRecord::Migration; end +end +ActsAsTaggableOnMigration.class_eval do + def self.up + create_table :tags do |t| + t.string :name + end + + create_table :taggings do |t| + t.references :tag + + # You should make sure that the column created is + # long enough to store the required class names. + t.references :taggable, polymorphic: true + t.references :tagger, polymorphic: true + + # Limit is created to prevent MySQL error on index + # length for MyISAM table type: http://bit.ly/vgW2Ql + t.string :context, limit: 128 + + t.datetime :created_at + end + + add_index :taggings, :tag_id + add_index :taggings, [:taggable_id, :taggable_type, :context] + end + + def self.down + drop_table :taggings + drop_table :tags + end +end diff --git a/db/migrate/20180723225855_add_missing_unique_indices.acts_as_taggable_on_engine.rb b/db/migrate/20180723225855_add_missing_unique_indices.acts_as_taggable_on_engine.rb new file mode 100644 index 0000000..514ac57 --- /dev/null +++ b/db/migrate/20180723225855_add_missing_unique_indices.acts_as_taggable_on_engine.rb @@ -0,0 +1,26 @@ +# This migration comes from acts_as_taggable_on_engine (originally 2) +if ActiveRecord.gem_version >= Gem::Version.new('5.0') + class AddMissingUniqueIndices < ActiveRecord::Migration[4.2]; end +else + class AddMissingUniqueIndices < ActiveRecord::Migration; end +end +AddMissingUniqueIndices.class_eval do + def self.up + add_index :tags, :name, unique: true + + remove_index :taggings, :tag_id if index_exists?(:taggings, :tag_id) + remove_index :taggings, [:taggable_id, :taggable_type, :context] + add_index :taggings, + [:tag_id, :taggable_id, :taggable_type, :context, :tagger_id, :tagger_type], + unique: true, name: 'taggings_idx' + end + + def self.down + remove_index :tags, :name + + remove_index :taggings, name: 'taggings_idx' + + add_index :taggings, :tag_id unless index_exists?(:taggings, :tag_id) + add_index :taggings, [:taggable_id, :taggable_type, :context] + end +end diff --git a/db/migrate/20180723225856_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb b/db/migrate/20180723225856_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb new file mode 100644 index 0000000..1d9b556 --- /dev/null +++ b/db/migrate/20180723225856_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb @@ -0,0 +1,20 @@ +# This migration comes from acts_as_taggable_on_engine (originally 3) +if ActiveRecord.gem_version >= Gem::Version.new('5.0') + class AddTaggingsCounterCacheToTags < ActiveRecord::Migration[4.2]; end +else + class AddTaggingsCounterCacheToTags < ActiveRecord::Migration; end +end +AddTaggingsCounterCacheToTags.class_eval do + def self.up + add_column :tags, :taggings_count, :integer, default: 0 + + ActsAsTaggableOn::Tag.reset_column_information + ActsAsTaggableOn::Tag.find_each do |tag| + ActsAsTaggableOn::Tag.reset_counters(tag.id, :taggings) + end + end + + def self.down + remove_column :tags, :taggings_count + end +end diff --git a/db/migrate/20180723225857_add_missing_taggable_index.acts_as_taggable_on_engine.rb b/db/migrate/20180723225857_add_missing_taggable_index.acts_as_taggable_on_engine.rb new file mode 100644 index 0000000..5f46569 --- /dev/null +++ b/db/migrate/20180723225857_add_missing_taggable_index.acts_as_taggable_on_engine.rb @@ -0,0 +1,15 @@ +# This migration comes from acts_as_taggable_on_engine (originally 4) +if ActiveRecord.gem_version >= Gem::Version.new('5.0') + class AddMissingTaggableIndex < ActiveRecord::Migration[4.2]; end +else + class AddMissingTaggableIndex < ActiveRecord::Migration; end +end +AddMissingTaggableIndex.class_eval do + def self.up + add_index :taggings, [:taggable_id, :taggable_type, :context] + end + + def self.down + remove_index :taggings, [:taggable_id, :taggable_type, :context] + end +end diff --git a/db/migrate/20180723225858_change_collation_for_tag_names.acts_as_taggable_on_engine.rb b/db/migrate/20180723225858_change_collation_for_tag_names.acts_as_taggable_on_engine.rb new file mode 100644 index 0000000..f119b16 --- /dev/null +++ b/db/migrate/20180723225858_change_collation_for_tag_names.acts_as_taggable_on_engine.rb @@ -0,0 +1,15 @@ +# This migration comes from acts_as_taggable_on_engine (originally 5) +# This migration is added to circumvent issue #623 and have special characters +# work properly +if ActiveRecord.gem_version >= Gem::Version.new('5.0') + class ChangeCollationForTagNames < ActiveRecord::Migration[4.2]; end +else + class ChangeCollationForTagNames < ActiveRecord::Migration; end +end +ChangeCollationForTagNames.class_eval do + def up + if ActsAsTaggableOn::Utils.using_mysql? + execute("ALTER TABLE tags MODIFY name varchar(255) CHARACTER SET utf8 COLLATE utf8_bin;") + end + end +end diff --git a/db/migrate/20180723225859_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb b/db/migrate/20180723225859_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb new file mode 100644 index 0000000..94e1f2e --- /dev/null +++ b/db/migrate/20180723225859_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb @@ -0,0 +1,23 @@ +# This migration comes from acts_as_taggable_on_engine (originally 6) +if ActiveRecord.gem_version >= Gem::Version.new('5.0') + class AddMissingIndexesOnTaggings < ActiveRecord::Migration[4.2]; end +else + class AddMissingIndexesOnTaggings < ActiveRecord::Migration; end +end +AddMissingIndexesOnTaggings.class_eval do + def change + add_index :taggings, :tag_id unless index_exists? :taggings, :tag_id + add_index :taggings, :taggable_id unless index_exists? :taggings, :taggable_id + add_index :taggings, :taggable_type unless index_exists? :taggings, :taggable_type + add_index :taggings, :tagger_id unless index_exists? :taggings, :tagger_id + add_index :taggings, :context unless index_exists? :taggings, :context + + unless index_exists? :taggings, [:tagger_id, :tagger_type] + add_index :taggings, [:tagger_id, :tagger_type] + end + + unless index_exists? :taggings, [:taggable_id, :taggable_type, :tagger_id, :context], name: 'taggings_idy' + add_index :taggings, [:taggable_id, :taggable_type, :tagger_id, :context], name: 'taggings_idy' + end + end +end diff --git a/db/migrate/20180803012621_create_video_appearance_confirmations.rb b/db/migrate/20180803012621_create_video_appearance_confirmations.rb new file mode 100644 index 0000000..0baae28 --- /dev/null +++ b/db/migrate/20180803012621_create_video_appearance_confirmations.rb @@ -0,0 +1,10 @@ +class CreateVideoAppearanceConfirmations < ActiveRecord::Migration[5.2] + def change + create_table :video_appearance_confirmations do |t| + t.belongs_to :video, foreign_key: true + t.belongs_to :appearance_release, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20180803012622_create_bookmarks.rb b/db/migrate/20180803012622_create_bookmarks.rb new file mode 100644 index 0000000..869ee4f --- /dev/null +++ b/db/migrate/20180803012622_create_bookmarks.rb @@ -0,0 +1,10 @@ +class CreateBookmarks < ActiveRecord::Migration[5.2] + def change + create_table :bookmarks do |t| + t.belongs_to :video, foreign_key: true + t.string :time_elapsed + + t.timestamps + end + end +end diff --git a/db/migrate/20180808193510_create_unreleased_appearances.rb b/db/migrate/20180808193510_create_unreleased_appearances.rb new file mode 100644 index 0000000..7962777 --- /dev/null +++ b/db/migrate/20180808193510_create_unreleased_appearances.rb @@ -0,0 +1,10 @@ +class CreateUnreleasedAppearances < ActiveRecord::Migration[5.2] + def change + create_table :unreleased_appearances do |t| + t.belongs_to :video, foreign_key: true + t.string :time_elapsed + + t.timestamps + end + end +end diff --git a/db/migrate/20181022134900_create_area_releases.rb b/db/migrate/20181022134900_create_area_releases.rb new file mode 100644 index 0000000..0e3eb25 --- /dev/null +++ b/db/migrate/20181022134900_create_area_releases.rb @@ -0,0 +1,17 @@ +class CreateAreaReleases < ActiveRecord::Migration[5.2] + def change + create_table :area_releases do |t| + t.belongs_to :project, foreign_key: true + t.string :name + t.string :address_street1 + t.string :address_street2 + t.string :address_city + t.string :address_state + t.string :address_zip + t.string :address_country + t.text :notes + + t.timestamps + end + end +end diff --git a/db/migrate/20181026003825_add_time_elapsed_to_video_appearance_confirmations.rb b/db/migrate/20181026003825_add_time_elapsed_to_video_appearance_confirmations.rb new file mode 100644 index 0000000..88e850b --- /dev/null +++ b/db/migrate/20181026003825_add_time_elapsed_to_video_appearance_confirmations.rb @@ -0,0 +1,5 @@ +class AddTimeElapsedToVideoAppearanceConfirmations < ActiveRecord::Migration[5.2] + def change + add_column :video_appearance_confirmations, :time_elapsed, :string + end +end diff --git a/db/migrate/20181030213548_create_location_releases.rb b/db/migrate/20181030213548_create_location_releases.rb new file mode 100644 index 0000000..40a7163 --- /dev/null +++ b/db/migrate/20181030213548_create_location_releases.rb @@ -0,0 +1,28 @@ +class CreateLocationReleases < ActiveRecord::Migration[5.2] + def change + create_table :location_releases do |t| + t.belongs_to :project, foreign_key: true + t.string :name + t.string :address_street1 + t.string :address_street2 + t.string :address_city + t.string :address_state + t.string :address_zip + t.string :address_country + t.string :person_name + t.string :person_address_street1 + t.string :person_address_street2 + t.string :person_address_city + t.string :person_address_state + t.string :person_address_zip + t.string :person_address_country + t.string :person_phone + t.string :person_email + t.string :person_title + t.string :person_company + t.text :notes + + t.timestamps + end + end +end diff --git a/db/migrate/20181101184834_create_material_releases.rb b/db/migrate/20181101184834_create_material_releases.rb new file mode 100644 index 0000000..bff506b --- /dev/null +++ b/db/migrate/20181101184834_create_material_releases.rb @@ -0,0 +1,22 @@ +class CreateMaterialReleases < ActiveRecord::Migration[5.2] + def change + create_table :material_releases do |t| + t.belongs_to :project, foreign_key: true + t.string :name + t.string :person_name + t.string :person_address_street1 + t.string :person_address_street2 + t.string :person_address_city + t.string :person_address_state + t.string :person_address_zip + t.string :person_address_country + t.string :person_phone + t.string :person_email + t.string :person_title + t.string :person_company + t.text :notes + + t.timestamps + end + end +end diff --git a/db/migrate/20181107191123_rename_video_appearance_confirmations.rb b/db/migrate/20181107191123_rename_video_appearance_confirmations.rb new file mode 100644 index 0000000..47b2394 --- /dev/null +++ b/db/migrate/20181107191123_rename_video_appearance_confirmations.rb @@ -0,0 +1,5 @@ +class RenameVideoAppearanceConfirmations < ActiveRecord::Migration[5.2] + def change + rename_table :video_appearance_confirmations, :video_release_confirmations + end +end diff --git a/db/migrate/20181107191220_make_video_release_confirmations_polymorphic.rb b/db/migrate/20181107191220_make_video_release_confirmations_polymorphic.rb new file mode 100644 index 0000000..d55c8f2 --- /dev/null +++ b/db/migrate/20181107191220_make_video_release_confirmations_polymorphic.rb @@ -0,0 +1,8 @@ +class MakeVideoReleaseConfirmationsPolymorphic < ActiveRecord::Migration[5.2] + def change + remove_foreign_key :video_release_confirmations, :appearance_releases + rename_column :video_release_confirmations, :appearance_release_id, :releasable_id + add_column :video_release_confirmations, :releasable_type, :string, after: :releasable_id + add_index :video_release_confirmations, [:releasable_id, :releasable_type], name: "index_video_release_confirmations_on_releasable_id_and_type" + end +end diff --git a/db/migrate/20181113200008_add_notes_to_bookmarks.rb b/db/migrate/20181113200008_add_notes_to_bookmarks.rb new file mode 100644 index 0000000..c087e96 --- /dev/null +++ b/db/migrate/20181113200008_add_notes_to_bookmarks.rb @@ -0,0 +1,5 @@ +class AddNotesToBookmarks < ActiveRecord::Migration[5.2] + def change + add_column :bookmarks, :notes, :text + end +end diff --git a/db/migrate/20181114182730_add_notes_to_unreleased_appearances.rb b/db/migrate/20181114182730_add_notes_to_unreleased_appearances.rb new file mode 100644 index 0000000..78086df --- /dev/null +++ b/db/migrate/20181114182730_add_notes_to_unreleased_appearances.rb @@ -0,0 +1,5 @@ +class AddNotesToUnreleasedAppearances < ActiveRecord::Migration[5.2] + def change + add_column :unreleased_appearances, :notes, :text + end +end diff --git a/db/migrate/20181121211703_add_tagging_status_to_appearance_releases.rb b/db/migrate/20181121211703_add_tagging_status_to_appearance_releases.rb new file mode 100644 index 0000000..df409dc --- /dev/null +++ b/db/migrate/20181121211703_add_tagging_status_to_appearance_releases.rb @@ -0,0 +1,5 @@ +class AddTaggingStatusToAppearanceReleases < ActiveRecord::Migration[5.2] + def change + add_column :appearance_releases, :tagging_status, :integer, default: 0 + end +end diff --git a/db/migrate/20181123034352_add_tagging_status_to_location_releases.rb b/db/migrate/20181123034352_add_tagging_status_to_location_releases.rb new file mode 100644 index 0000000..a77dce6 --- /dev/null +++ b/db/migrate/20181123034352_add_tagging_status_to_location_releases.rb @@ -0,0 +1,5 @@ +class AddTaggingStatusToLocationReleases < ActiveRecord::Migration[5.2] + def change + add_column :location_releases, :tagging_status, :integer, default: 0 + end +end diff --git a/db/migrate/20181123060610_add_tagging_status_to_material_releases.rb b/db/migrate/20181123060610_add_tagging_status_to_material_releases.rb new file mode 100644 index 0000000..f81dd73 --- /dev/null +++ b/db/migrate/20181123060610_add_tagging_status_to_material_releases.rb @@ -0,0 +1,5 @@ +class AddTaggingStatusToMaterialReleases < ActiveRecord::Migration[5.2] + def change + add_column :material_releases, :tagging_status, :integer, default: 0 + end +end diff --git a/db/migrate/20181123061024_add_tagging_status_to_area_releases.rb b/db/migrate/20181123061024_add_tagging_status_to_area_releases.rb new file mode 100644 index 0000000..3629a40 --- /dev/null +++ b/db/migrate/20181123061024_add_tagging_status_to_area_releases.rb @@ -0,0 +1,5 @@ +class AddTaggingStatusToAreaReleases < ActiveRecord::Migration[5.2] + def change + add_column :area_releases, :tagging_status, :integer, default: 0 + end +end diff --git a/db/migrate/20181123211217_install_pg_contrib_packages.rb b/db/migrate/20181123211217_install_pg_contrib_packages.rb new file mode 100644 index 0000000..2be5be2 --- /dev/null +++ b/db/migrate/20181123211217_install_pg_contrib_packages.rb @@ -0,0 +1,11 @@ +class InstallPgContribPackages < ActiveRecord::Migration[5.2] + def up + execute "CREATE EXTENSION IF NOT EXISTS pg_trgm;" + execute "CREATE EXTENSION IF NOT EXISTS fuzzystrmatch;" + end + + def down + execute "DROP EXTENSION pg_trgm;" + execute "DROP EXTENSION fuzzystrmatch;" + end +end diff --git a/db/migrate/20181123211221_add_pg_search_dmetaphone_support_functions.rb b/db/migrate/20181123211221_add_pg_search_dmetaphone_support_functions.rb new file mode 100644 index 0000000..343cbd7 --- /dev/null +++ b/db/migrate/20181123211221_add_pg_search_dmetaphone_support_functions.rb @@ -0,0 +1,19 @@ +class AddPgSearchDmetaphoneSupportFunctions < ActiveRecord::Migration[5.2] + def self.up + say_with_time("Adding support functions for pg_search :dmetaphone") do + execute <<-'SQL' +CREATE OR REPLACE FUNCTION pg_search_dmetaphone(text) RETURNS text LANGUAGE SQL IMMUTABLE STRICT AS $function$ + SELECT array_to_string(ARRAY(SELECT dmetaphone(unnest(regexp_split_to_array($1, E'\\s+')))), ' ') +$function$; + SQL + end + end + + def self.down + say_with_time("Dropping support functions for pg_search :dmetaphone") do + execute <<-'SQL' +DROP FUNCTION pg_search_dmetaphone(text); + SQL + end + end +end diff --git a/db/migrate/20181129215157_create_imports.rb b/db/migrate/20181129215157_create_imports.rb new file mode 100644 index 0000000..6adda8b --- /dev/null +++ b/db/migrate/20181129215157_create_imports.rb @@ -0,0 +1,10 @@ +class CreateImports < ActiveRecord::Migration[5.2] + def change + create_table :imports do |t| + t.belongs_to :project, foreign_key: true + t.integer :status, default: 0 + + t.timestamps + end + end +end diff --git a/db/migrate/20181205193134_add_releasable_to_imports.rb b/db/migrate/20181205193134_add_releasable_to_imports.rb new file mode 100644 index 0000000..542f52b --- /dev/null +++ b/db/migrate/20181205193134_add_releasable_to_imports.rb @@ -0,0 +1,5 @@ +class AddReleasableToImports < ActiveRecord::Migration[5.2] + def change + add_reference :imports, :releasable, polymorphic: true + end +end diff --git a/db/migrate/20181208001821_create_contract_templates.rb b/db/migrate/20181208001821_create_contract_templates.rb new file mode 100644 index 0000000..6e86087 --- /dev/null +++ b/db/migrate/20181208001821_create_contract_templates.rb @@ -0,0 +1,13 @@ +class CreateContractTemplates < ActiveRecord::Migration[5.2] + def change + create_table :contract_templates do |t| + t.belongs_to :project, foreign_key: true + t.string :name + t.text :body + t.text :guardian_clause + t.monetize :fee + + t.timestamps + end + end +end diff --git a/db/migrate/20181210183047_add_contract_template_to_appearance_releases.rb b/db/migrate/20181210183047_add_contract_template_to_appearance_releases.rb new file mode 100644 index 0000000..a8b6d46 --- /dev/null +++ b/db/migrate/20181210183047_add_contract_template_to_appearance_releases.rb @@ -0,0 +1,5 @@ +class AddContractTemplateToAppearanceReleases < ActiveRecord::Migration[5.2] + def change + add_reference :appearance_releases, :contract_template, foreign_key: true + end +end diff --git a/db/migrate/20181214193036_create_action_text_tables.action_text.rb b/db/migrate/20181214193036_create_action_text_tables.action_text.rb new file mode 100644 index 0000000..daad1c1 --- /dev/null +++ b/db/migrate/20181214193036_create_action_text_tables.action_text.rb @@ -0,0 +1,15 @@ +# This migration comes from action_text (originally 201805281641) +class CreateActionTextTables < ActiveRecord::Migration[5.2] + def change + create_table :action_text_rich_texts do |t| + t.string :name, null: false + t.text :body, limit: 16777215 + t.references :record, null: false, polymorphic: true, index: false + + t.datetime :created_at, null: false + t.datetime :updated_at, null: false + + t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true + end + end +end diff --git a/db/migrate/20181219160538_add_locale_to_location_releases.rb b/db/migrate/20181219160538_add_locale_to_location_releases.rb new file mode 100644 index 0000000..728a1af --- /dev/null +++ b/db/migrate/20181219160538_add_locale_to_location_releases.rb @@ -0,0 +1,5 @@ +class AddLocaleToLocationReleases < ActiveRecord::Migration[5.2] + def change + add_column :location_releases, :locale, :string + end +end diff --git a/db/migrate/20181219160621_add_contract_template_to_location_releases.rb b/db/migrate/20181219160621_add_contract_template_to_location_releases.rb new file mode 100644 index 0000000..823693b --- /dev/null +++ b/db/migrate/20181219160621_add_contract_template_to_location_releases.rb @@ -0,0 +1,5 @@ +class AddContractTemplateToLocationReleases < ActiveRecord::Migration[5.2] + def change + add_reference :location_releases, :contract_template, foreign_key: true + end +end diff --git a/db/migrate/20181220151559_add_release_type_to_contract_templates.rb b/db/migrate/20181220151559_add_release_type_to_contract_templates.rb new file mode 100644 index 0000000..d22af3a --- /dev/null +++ b/db/migrate/20181220151559_add_release_type_to_contract_templates.rb @@ -0,0 +1,5 @@ +class AddReleaseTypeToContractTemplates < ActiveRecord::Migration[5.2] + def change + add_column :contract_templates, :release_type, :string + end +end diff --git a/db/migrate/20190111053427_add_contract_template_to_material_releases.rb b/db/migrate/20190111053427_add_contract_template_to_material_releases.rb new file mode 100644 index 0000000..82a258b --- /dev/null +++ b/db/migrate/20190111053427_add_contract_template_to_material_releases.rb @@ -0,0 +1,5 @@ +class AddContractTemplateToMaterialReleases < ActiveRecord::Migration[5.2] + def change + add_reference :material_releases, :contract_template, foreign_key: true + end +end diff --git a/db/migrate/20190111053759_add_locale_to_material_releases.rb b/db/migrate/20190111053759_add_locale_to_material_releases.rb new file mode 100644 index 0000000..867989b --- /dev/null +++ b/db/migrate/20190111053759_add_locale_to_material_releases.rb @@ -0,0 +1,5 @@ +class AddLocaleToMaterialReleases < ActiveRecord::Migration[5.2] + def change + add_column :material_releases, :locale, :string + end +end diff --git a/db/migrate/20190111211317_add_locale_to_area_releases.rb b/db/migrate/20190111211317_add_locale_to_area_releases.rb new file mode 100644 index 0000000..2138589 --- /dev/null +++ b/db/migrate/20190111211317_add_locale_to_area_releases.rb @@ -0,0 +1,5 @@ +class AddLocaleToAreaReleases < ActiveRecord::Migration[5.2] + def change + add_column :area_releases, :locale, :string + end +end diff --git a/db/migrate/20190111211406_add_contract_template_to_area_releases.rb b/db/migrate/20190111211406_add_contract_template_to_area_releases.rb new file mode 100644 index 0000000..c774c49 --- /dev/null +++ b/db/migrate/20190111211406_add_contract_template_to_area_releases.rb @@ -0,0 +1,5 @@ +class AddContractTemplateToAreaReleases < ActiveRecord::Migration[5.2] + def change + add_reference :area_releases, :contract_template, foreign_key: true + end +end diff --git a/db/migrate/20190125012705_add_admin_to_users.rb b/db/migrate/20190125012705_add_admin_to_users.rb new file mode 100644 index 0000000..aa6a7e9 --- /dev/null +++ b/db/migrate/20190125012705_add_admin_to_users.rb @@ -0,0 +1,5 @@ +class AddAdminToUsers < ActiveRecord::Migration[5.2] + def change + add_column :users, :admin, :boolean, default: false + end +end diff --git a/db/migrate/20190129192226_add_plan_uid_to_users.rb b/db/migrate/20190129192226_add_plan_uid_to_users.rb new file mode 100644 index 0000000..a41f63a --- /dev/null +++ b/db/migrate/20190129192226_add_plan_uid_to_users.rb @@ -0,0 +1,5 @@ +class AddPlanUidToUsers < ActiveRecord::Migration[5.2] + def change + add_column :users, :plan_uid, :string + end +end diff --git a/db/migrate/20190204054833_create_accounts.rb b/db/migrate/20190204054833_create_accounts.rb new file mode 100644 index 0000000..ce73fbf --- /dev/null +++ b/db/migrate/20190204054833_create_accounts.rb @@ -0,0 +1,12 @@ +class CreateAccounts < ActiveRecord::Migration[5.2] + def change + create_table :accounts do |t| + t.string :name + t.string :slug + t.string :plan_uid + + t.timestamps + end + add_index :accounts, :slug, unique: true + end +end diff --git a/db/migrate/20190204060502_add_account_to_users.rb b/db/migrate/20190204060502_add_account_to_users.rb new file mode 100644 index 0000000..271c5e5 --- /dev/null +++ b/db/migrate/20190204060502_add_account_to_users.rb @@ -0,0 +1,47 @@ +class AddAccountToUsers < ActiveRecord::Migration[5.2] + def up + add_reference :users, :account, foreign_key: true + migrate_data_up + remove_column :users, :team_name, :string + end + + def down + add_column :users, :team_name, :string + migrate_user_data_down + + remove_reference :users, :account + migrate_account_data_down + end + + private + + def migrate_data_up + say "Creating Accounts.." + + User.find_each do |user| + account = Account.new(name: user.team_name.titleize, slug: user.team_name) + user.update!(account: account) + end + + say "Done." + end + + def migrate_user_data_down + say "Updating Users.." + + User.find_each do |user| + user.update!(team_name: user.account.slug) + end + + say "Done." + end + + def migrate_account_data_down + say "Destroying Accounts.." + + Account.delete_all + + say "Done." + end + +end diff --git a/db/migrate/20190204063948_add_account_to_projects.rb b/db/migrate/20190204063948_add_account_to_projects.rb new file mode 100644 index 0000000..0dc4012 --- /dev/null +++ b/db/migrate/20190204063948_add_account_to_projects.rb @@ -0,0 +1,42 @@ +class AddAccountToProjects < ActiveRecord::Migration[5.2] + def up + add_reference :projects, :account, foreign_key: true + migrate_data_up + remove_reference :projects, :user + end + + def down + add_reference :projects, :user, foreign_key: true + migrate_data_down + remove_reference :projects, :account + end + + private + + class Project < ActiveRecord::Base + belongs_to :account + belongs_to :user + end + + def migrate_data_up + say "Setting Project#account.." + + Project.reset_column_information + Project.find_each do |project| + project.update!(account: project.user.account) + end + + say "Done." + end + + def migrate_data_down + say "Setting Project#user.." + + Project.reset_column_information + Project.find_each do |project| + project.update!(user: project.account.users.first) + end + + say "Done." + end +end diff --git a/db/migrate/20190213234119_add_password_reset_token_to_users.rb b/db/migrate/20190213234119_add_password_reset_token_to_users.rb new file mode 100644 index 0000000..ec9f553 --- /dev/null +++ b/db/migrate/20190213234119_add_password_reset_token_to_users.rb @@ -0,0 +1,6 @@ +class AddPasswordResetTokenToUsers < ActiveRecord::Migration[5.2] + def change + add_column :users, :password_reset_token, :string + add_index :users, :password_reset_token, unique: true + end +end diff --git a/db/migrate/20190509201711_create_talent_releases.rb b/db/migrate/20190509201711_create_talent_releases.rb new file mode 100644 index 0000000..c584f21 --- /dev/null +++ b/db/migrate/20190509201711_create_talent_releases.rb @@ -0,0 +1,35 @@ +class CreateTalentReleases < ActiveRecord::Migration[5.2] + def change + create_table :talent_releases do |t| + t.belongs_to :project, foreign_key: true + t.belongs_to :contract_template, foreign_key: true + t.string :person_name + t.string :person_address_street1 + t.string :person_address_street2 + t.string :person_address_city + t.string :person_address_state + t.string :person_address_zip + t.string :person_address_country + t.string :person_phone + t.string :person_email + t.string :encrypted_person_ssn + t.string :encrypted_person_ssn_iv + t.string :guardian_name + t.string :guardian_address_street1 + t.string :guardian_address_street2 + t.string :guardian_address_city + t.string :guardian_address_state + t.string :guardian_address_zip + t.string :guardian_address_country + t.string :guardian_phone + t.string :guardian_email + t.string :encrypted_guardian_ssn + t.string :encrypted_guardian_ssn_iv + t.boolean :minor, default: false + t.string :locale + t.text :notes + + t.timestamps + end + end +end diff --git a/db/migrate/20190513175340_add_tagging_status_to_talent_releases.rb b/db/migrate/20190513175340_add_tagging_status_to_talent_releases.rb new file mode 100644 index 0000000..1b62cf4 --- /dev/null +++ b/db/migrate/20190513175340_add_tagging_status_to_talent_releases.rb @@ -0,0 +1,5 @@ +class AddTaggingStatusToTalentReleases < ActiveRecord::Migration[5.2] + def change + add_column :talent_releases, :tagging_status, :integer, default: 0 + end +end diff --git a/db/migrate/20190513212952_add_name_and_number_to_videos.rb b/db/migrate/20190513212952_add_name_and_number_to_videos.rb new file mode 100644 index 0000000..16f40a9 --- /dev/null +++ b/db/migrate/20190513212952_add_name_and_number_to_videos.rb @@ -0,0 +1,6 @@ +class AddNameAndNumberToVideos < ActiveRecord::Migration[5.2] + def change + add_column :videos, :name, :string + add_column :videos, :number, :string + end +end diff --git a/db/migrate/20190515233909_create_acquired_media_releases.rb b/db/migrate/20190515233909_create_acquired_media_releases.rb new file mode 100644 index 0000000..ae3e6ac --- /dev/null +++ b/db/migrate/20190515233909_create_acquired_media_releases.rb @@ -0,0 +1,25 @@ +class CreateAcquiredMediaReleases < ActiveRecord::Migration[5.2] + def change + create_table :acquired_media_releases do |t| + t.references :project, foreign_key: true + t.references :contract_template, foreign_key: true + t.string :locale + t.text :notes + t.integer :tagging_status, default: 0 + t.string :name + t.string :person_name + t.string :person_address_street1 + t.string :person_address_street2 + t.string :person_address_city + t.string :person_address_state + t.string :person_address_zip + t.string :person_address_country + t.string :person_phone + t.string :person_email + t.string :person_title + t.string :person_company + + t.timestamps + end + end +end diff --git a/db/migrate/20190517224239_create_music_releases.rb b/db/migrate/20190517224239_create_music_releases.rb new file mode 100644 index 0000000..7f201d4 --- /dev/null +++ b/db/migrate/20190517224239_create_music_releases.rb @@ -0,0 +1,25 @@ +class CreateMusicReleases < ActiveRecord::Migration[5.2] + def change + create_table :music_releases do |t| + t.belongs_to :project, foreign_key: true + t.belongs_to :contract_template, foreign_key: true + t.string :locale + t.text :notes + t.integer :tagging_status, default: 0 + t.string :name + t.string :person_name + t.string :person_address_street1 + t.string :person_address_street2 + t.string :person_address_city + t.string :person_address_state + t.string :person_address_zip + t.string :person_address_country + t.string :person_phone + t.string :person_email + t.string :person_title + t.string :person_company + + t.timestamps + end + end +end diff --git a/db/migrate/20190524220130_rails_settings_migration.rb b/db/migrate/20190524220130_rails_settings_migration.rb new file mode 100644 index 0000000..3bfd8af --- /dev/null +++ b/db/migrate/20190524220130_rails_settings_migration.rb @@ -0,0 +1,21 @@ +MIGRATION_BASE_CLASS = if ActiveRecord::VERSION::MAJOR >= 5 + ActiveRecord::Migration[5.0] +else + ActiveRecord::Migration +end + +class RailsSettingsMigration < MIGRATION_BASE_CLASS + def self.up + create_table :settings do |t| + t.string :var, :null => false + t.text :value + t.references :target, :null => false, :polymorphic => true + t.timestamps :null => true + end + add_index :settings, [ :target_type, :target_id, :var ], :unique => true + end + + def self.down + drop_table :settings + end +end diff --git a/db/migrate/20190529223953_add_parent_id_to_contract_templates.rb b/db/migrate/20190529223953_add_parent_id_to_contract_templates.rb new file mode 100644 index 0000000..46fb5b3 --- /dev/null +++ b/db/migrate/20190529223953_add_parent_id_to_contract_templates.rb @@ -0,0 +1,5 @@ +class AddParentIdToContractTemplates < ActiveRecord::Migration[5.2] + def change + add_column :contract_templates, :parent_id, :integer + end +end diff --git a/db/migrate/20190606220156_add_report_published_at_to_videos.rb b/db/migrate/20190606220156_add_report_published_at_to_videos.rb new file mode 100644 index 0000000..dceaba4 --- /dev/null +++ b/db/migrate/20190606220156_add_report_published_at_to_videos.rb @@ -0,0 +1,5 @@ +class AddReportPublishedAtToVideos < ActiveRecord::Migration[5.2] + def change + add_column :videos, :report_published_at, :datetime + end +end diff --git a/db/migrate/20190610192026_add_collection_uid_to_acquired_media_releases.rb b/db/migrate/20190610192026_add_collection_uid_to_acquired_media_releases.rb new file mode 100644 index 0000000..57cbfe9 --- /dev/null +++ b/db/migrate/20190610192026_add_collection_uid_to_acquired_media_releases.rb @@ -0,0 +1,5 @@ +class AddCollectionUidToAcquiredMediaReleases < ActiveRecord::Migration[5.2] + def change + add_column :acquired_media_releases, :collection_uid, :string + end +end diff --git a/db/migrate/20190610231302_add_additional_info_to_video_release_confirmations.rb b/db/migrate/20190610231302_add_additional_info_to_video_release_confirmations.rb new file mode 100644 index 0000000..db054f9 --- /dev/null +++ b/db/migrate/20190610231302_add_additional_info_to_video_release_confirmations.rb @@ -0,0 +1,5 @@ +class AddAdditionalInfoToVideoReleaseConfirmations < ActiveRecord::Migration[5.2] + def change + add_column :video_release_confirmations, :additional_info, :text + end +end diff --git a/db/migrate/20190613155257_create_graphics_elements_table.rb b/db/migrate/20190613155257_create_graphics_elements_table.rb new file mode 100644 index 0000000..d1615a3 --- /dev/null +++ b/db/migrate/20190613155257_create_graphics_elements_table.rb @@ -0,0 +1,11 @@ +class CreateGraphicsElementsTable < ActiveRecord::Migration[5.2] + def change + create_table :graphics_elements do |t| + t.references :video, foreign_key: true + t.text :text + t.string :time_elapsed + + t.timestamps + end + end +end diff --git a/db/migrate/20190614145141_add_additional_info_to_graphics_elements.rb b/db/migrate/20190614145141_add_additional_info_to_graphics_elements.rb new file mode 100644 index 0000000..d10ccf8 --- /dev/null +++ b/db/migrate/20190614145141_add_additional_info_to_graphics_elements.rb @@ -0,0 +1,5 @@ +class AddAdditionalInfoToGraphicsElements < ActiveRecord::Migration[5.2] + def change + add_column :graphics_elements, :additional_info, :text + end +end diff --git a/db/migrate/20190702221718_create_file_infos.rb b/db/migrate/20190702221718_create_file_infos.rb new file mode 100644 index 0000000..90631c4 --- /dev/null +++ b/db/migrate/20190702221718_create_file_infos.rb @@ -0,0 +1,12 @@ +class CreateFileInfos < ActiveRecord::Migration[5.2] + def change + create_table :file_infos do |t| + t.belongs_to :acquired_media_release, foreign_key: true + t.string :filename, null: false + t.string :content_type + t.integer :byte_size + + t.timestamps + end + end +end diff --git a/db/migrate/20190708165139_add_file_info_to_video_release_confirmations.rb b/db/migrate/20190708165139_add_file_info_to_video_release_confirmations.rb new file mode 100644 index 0000000..3942a34 --- /dev/null +++ b/db/migrate/20190708165139_add_file_info_to_video_release_confirmations.rb @@ -0,0 +1,5 @@ +class AddFileInfoToVideoReleaseConfirmations < ActiveRecord::Migration[5.2] + def change + add_reference :video_release_confirmations, :file_info, foreign_key: true + end +end diff --git a/db/migrate/20190710162403_add_territory_and_term_to_acquired_media_release.rb b/db/migrate/20190710162403_add_territory_and_term_to_acquired_media_release.rb new file mode 100644 index 0000000..18d547a --- /dev/null +++ b/db/migrate/20190710162403_add_territory_and_term_to_acquired_media_release.rb @@ -0,0 +1,6 @@ +class AddTerritoryAndTermToAcquiredMediaRelease < ActiveRecord::Migration[5.2] + def change + add_column :acquired_media_releases, :territory, :string + add_column :acquired_media_releases, :term, :string + end +end diff --git a/db/migrate/20190712132406_convert_file_infos_byte_size_to_bigint.rb b/db/migrate/20190712132406_convert_file_infos_byte_size_to_bigint.rb new file mode 100644 index 0000000..3ffcfbf --- /dev/null +++ b/db/migrate/20190712132406_convert_file_infos_byte_size_to_bigint.rb @@ -0,0 +1,5 @@ +class ConvertFileInfosByteSizeToBigint < ActiveRecord::Migration[5.2] + def change + change_column :file_infos, :byte_size, :bigint + end +end diff --git a/db/migrate/20190716170929_convert_additional_info_hash_to_columns_for_video_release_confirmations.rb b/db/migrate/20190716170929_convert_additional_info_hash_to_columns_for_video_release_confirmations.rb new file mode 100644 index 0000000..a06234f --- /dev/null +++ b/db/migrate/20190716170929_convert_additional_info_hash_to_columns_for_video_release_confirmations.rb @@ -0,0 +1,10 @@ +class ConvertAdditionalInfoHashToColumnsForVideoReleaseConfirmations < ActiveRecord::Migration[5.2] + def change + add_column :video_release_confirmations, :timecode_in, :string + add_column :video_release_confirmations, :timecode_out, :string + add_column :video_release_confirmations, :duration, :string + add_column :video_release_confirmations, :source_file_name, :string + add_column :video_release_confirmations, :clip_name, :string + add_column :video_release_confirmations, :description, :string + end +end diff --git a/db/migrate/20190716183332_convert_additional_info_hash_to_columns_for_graphics_elements.rb b/db/migrate/20190716183332_convert_additional_info_hash_to_columns_for_graphics_elements.rb new file mode 100644 index 0000000..249b78c --- /dev/null +++ b/db/migrate/20190716183332_convert_additional_info_hash_to_columns_for_graphics_elements.rb @@ -0,0 +1,10 @@ +class ConvertAdditionalInfoHashToColumnsForGraphicsElements < ActiveRecord::Migration[5.2] + def change + add_column :graphics_elements, :timecode_in, :string + add_column :graphics_elements, :timecode_out, :string + add_column :graphics_elements, :duration, :string + add_column :graphics_elements, :source_file_name, :string + add_column :graphics_elements, :clip_name, :string + add_column :graphics_elements, :description, :string + end +end diff --git a/db/migrate/20190717210004_add_edl_attributes_to_unreleased_appearances.rb b/db/migrate/20190717210004_add_edl_attributes_to_unreleased_appearances.rb new file mode 100644 index 0000000..da5bc8b --- /dev/null +++ b/db/migrate/20190717210004_add_edl_attributes_to_unreleased_appearances.rb @@ -0,0 +1,10 @@ +class AddEdlAttributesToUnreleasedAppearances < ActiveRecord::Migration[5.2] + def change + add_column :unreleased_appearances, :timecode_in, :string + add_column :unreleased_appearances, :timecode_out, :string + add_column :unreleased_appearances, :duration, :string + add_column :unreleased_appearances, :source_file_name, :string + add_column :unreleased_appearances, :clip_name, :string + add_column :unreleased_appearances, :description, :string + end +end diff --git a/db/migrate/20190719210405_drop_area_releases.rb b/db/migrate/20190719210405_drop_area_releases.rb new file mode 100644 index 0000000..926a819 --- /dev/null +++ b/db/migrate/20190719210405_drop_area_releases.rb @@ -0,0 +1,5 @@ +class DropAreaReleases < ActiveRecord::Migration[5.2] + def change + drop_table :area_releases + end +end diff --git a/db/migrate/20190729215334_remove_ssn_fields_from_talent_and_appearance_releases.rb b/db/migrate/20190729215334_remove_ssn_fields_from_talent_and_appearance_releases.rb new file mode 100644 index 0000000..42d31f6 --- /dev/null +++ b/db/migrate/20190729215334_remove_ssn_fields_from_talent_and_appearance_releases.rb @@ -0,0 +1,12 @@ +class RemoveSsnFieldsFromTalentAndAppearanceReleases < ActiveRecord::Migration[5.2] + def change + remove_column :talent_releases, :encrypted_person_ssn, :string + remove_column :talent_releases, :encrypted_person_ssn_iv, :string + remove_column :talent_releases, :encrypted_guardian_ssn, :string + remove_column :talent_releases, :encrypted_guardian_ssn_iv, :string + + remove_column :appearance_releases, :encrypted_person_ssn, :string + remove_column :appearance_releases, :encrypted_person_ssn_iv, :string + remove_column :appearance_releases, :guardian_ssn, :string + end +end diff --git a/db/migrate/20190731171011_add_edl_start_timecode_to_videos.rb b/db/migrate/20190731171011_add_edl_start_timecode_to_videos.rb new file mode 100644 index 0000000..a2924f6 --- /dev/null +++ b/db/migrate/20190731171011_add_edl_start_timecode_to_videos.rb @@ -0,0 +1,5 @@ +class AddEdlStartTimecodeToVideos < ActiveRecord::Migration[5.2] + def change + add_column :videos, :edl_start_timecode, :string + end +end diff --git a/db/migrate/20190731192015_rename_edl_start_timecode_to_edl_timecode_start.rb b/db/migrate/20190731192015_rename_edl_start_timecode_to_edl_timecode_start.rb new file mode 100644 index 0000000..344bcc1 --- /dev/null +++ b/db/migrate/20190731192015_rename_edl_start_timecode_to_edl_timecode_start.rb @@ -0,0 +1,5 @@ +class RenameEdlStartTimecodeToEdlTimecodeStart < ActiveRecord::Migration[5.2] + def change + rename_column :videos, :edl_start_timecode, :edl_timecode_start + end +end diff --git a/db/migrate/20190807234639_add_edl_type_to_graphics_elements.rb b/db/migrate/20190807234639_add_edl_type_to_graphics_elements.rb new file mode 100644 index 0000000..4e94a40 --- /dev/null +++ b/db/migrate/20190807234639_add_edl_type_to_graphics_elements.rb @@ -0,0 +1,5 @@ +class AddEdlTypeToGraphicsElements < ActiveRecord::Migration[5.2] + def change + add_column :graphics_elements, :edl_type, :string + end +end diff --git a/db/migrate/20190808191051_convert_file_infos_to_be_polymorphic.rb b/db/migrate/20190808191051_convert_file_infos_to_be_polymorphic.rb new file mode 100644 index 0000000..c0961d8 --- /dev/null +++ b/db/migrate/20190808191051_convert_file_infos_to_be_polymorphic.rb @@ -0,0 +1,8 @@ +class ConvertFileInfosToBePolymorphic < ActiveRecord::Migration[5.2] + def change + remove_foreign_key :file_infos, :acquired_media_releases + rename_column :file_infos, :acquired_media_release_id, :releasable_id + add_column :file_infos, :releasable_type, :string, after: :releasable_id + add_index :file_infos, [:releasable_id, :releasable_type], name: "index_file_infos_on_releasable_id_and_type" + end +end diff --git a/db/migrate/20190809133049_create_composers.rb b/db/migrate/20190809133049_create_composers.rb new file mode 100644 index 0000000..56f0222 --- /dev/null +++ b/db/migrate/20190809133049_create_composers.rb @@ -0,0 +1,12 @@ +class CreateComposers < ActiveRecord::Migration[5.2] + def change + create_table :composers do |t| + t.belongs_to :music_release, foreign_key: true + t.string :name, null: false + t.string :affiliation, null: false + t.float :percentage, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20190809133100_create_publishers.rb b/db/migrate/20190809133100_create_publishers.rb new file mode 100644 index 0000000..43d22c4 --- /dev/null +++ b/db/migrate/20190809133100_create_publishers.rb @@ -0,0 +1,12 @@ +class CreatePublishers < ActiveRecord::Migration[5.2] + def change + create_table :publishers do |t| + t.belongs_to :music_release, foreign_key: true + t.string :name, null: false + t.string :affiliation, null: false + t.float :percentage, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20190813204822_add_music_type_and_music_category_to_video_release_confirmations.rb b/db/migrate/20190813204822_add_music_type_and_music_category_to_video_release_confirmations.rb new file mode 100644 index 0000000..b04bbb2 --- /dev/null +++ b/db/migrate/20190813204822_add_music_type_and_music_category_to_video_release_confirmations.rb @@ -0,0 +1,6 @@ +class AddMusicTypeAndMusicCategoryToVideoReleaseConfirmations < ActiveRecord::Migration[5.2] + def change + add_column :video_release_confirmations, :music_type, :string + add_column :video_release_confirmations, :music_category, :string + end +end diff --git a/db/migrate/20190820211808_add_channel_to_video_release_confirmations_graphics_elements_and_unreleased_appearances.rb b/db/migrate/20190820211808_add_channel_to_video_release_confirmations_graphics_elements_and_unreleased_appearances.rb new file mode 100644 index 0000000..a2fd7fc --- /dev/null +++ b/db/migrate/20190820211808_add_channel_to_video_release_confirmations_graphics_elements_and_unreleased_appearances.rb @@ -0,0 +1,7 @@ +class AddChannelToVideoReleaseConfirmationsGraphicsElementsAndUnreleasedAppearances < ActiveRecord::Migration[5.2] + def change + add_column :video_release_confirmations, :channel, :string + add_column :graphics_elements, :channel, :string + add_column :unreleased_appearances, :channel, :string + end +end diff --git a/db/migrate/20190821231050_create_audio_confirmations.rb b/db/migrate/20190821231050_create_audio_confirmations.rb new file mode 100644 index 0000000..a815f06 --- /dev/null +++ b/db/migrate/20190821231050_create_audio_confirmations.rb @@ -0,0 +1,23 @@ +class CreateAudioConfirmations < ActiveRecord::Migration[5.2] + def change + create_table :audio_confirmations do |t| + t.references :video, foreign_key: true + t.string :channel + t.string :timecode_in + t.string :timecode_out + t.string :duration + t.string :source_file_name + t.string :clip_name + t.string :description + t.string :time_elapsed + t.string :music_type + t.string :music_category + t.string :composer_info + t.string :publisher_info + t.string :catalog + t.string :title + + t.timestamps + end + end +end diff --git a/db/migrate/20190822132426_drop_additional_info_from_graphics_elements_video_release_confirmations.rb b/db/migrate/20190822132426_drop_additional_info_from_graphics_elements_video_release_confirmations.rb new file mode 100644 index 0000000..03bef1b --- /dev/null +++ b/db/migrate/20190822132426_drop_additional_info_from_graphics_elements_video_release_confirmations.rb @@ -0,0 +1,6 @@ +class DropAdditionalInfoFromGraphicsElementsVideoReleaseConfirmations < ActiveRecord::Migration[5.2] + def change + remove_column :graphics_elements, :additional_info, :text + remove_column :video_release_confirmations, :additional_info, :text + end +end diff --git a/db/migrate/20190823155000_remove_music_columns_from_video_release_confirmations.rb b/db/migrate/20190823155000_remove_music_columns_from_video_release_confirmations.rb new file mode 100644 index 0000000..07fc81c --- /dev/null +++ b/db/migrate/20190823155000_remove_music_columns_from_video_release_confirmations.rb @@ -0,0 +1,6 @@ +class RemoveMusicColumnsFromVideoReleaseConfirmations < ActiveRecord::Migration[5.2] + def change + remove_column :video_release_confirmations, :music_type, :string + remove_column :video_release_confirmations, :music_category, :string + end +end diff --git a/db/migrate/20190823164829_add_confirmation_type_to_audio_confirmations.rb b/db/migrate/20190823164829_add_confirmation_type_to_audio_confirmations.rb new file mode 100644 index 0000000..5084c31 --- /dev/null +++ b/db/migrate/20190823164829_add_confirmation_type_to_audio_confirmations.rb @@ -0,0 +1,5 @@ +class AddConfirmationTypeToAudioConfirmations < ActiveRecord::Migration[5.2] + def change + add_column :audio_confirmations, :confirmation_type, :string, null: false, default: "original_music" + end +end diff --git a/db/migrate/20190823214942_remove_plan_uid_from_users.rb b/db/migrate/20190823214942_remove_plan_uid_from_users.rb new file mode 100644 index 0000000..0694302 --- /dev/null +++ b/db/migrate/20190823214942_remove_plan_uid_from_users.rb @@ -0,0 +1,5 @@ +class RemovePlanUidFromUsers < ActiveRecord::Migration[5.2] + def change + remove_column :users, :plan_uid, :string + end +end diff --git a/db/migrate/20190905212706_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb b/db/migrate/20190905212706_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb new file mode 100644 index 0000000..ff5d72c --- /dev/null +++ b/db/migrate/20190905212706_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb @@ -0,0 +1,10 @@ +# This migration comes from active_storage (originally 20180723000244) +class AddForeignKeyConstraintToActiveStorageAttachmentsForBlobId < ActiveRecord::Migration[6.0] + def up + return if foreign_key_exists?(:active_storage_attachments, column: :blob_id) + + if table_exists?(:active_storage_blobs) + add_foreign_key :active_storage_attachments, :active_storage_blobs, column: :blob_id + end + end +end diff --git a/db/migrate/20190911174324_add_role_to_users.rb b/db/migrate/20190911174324_add_role_to_users.rb new file mode 100644 index 0000000..152a404 --- /dev/null +++ b/db/migrate/20190911174324_add_role_to_users.rb @@ -0,0 +1,5 @@ +class AddRoleToUsers < ActiveRecord::Migration[6.0] + def change + add_column :users, :role, :integer, default: 0, null: false + end +end diff --git a/db/migrate/20190912195438_add_edl_type_to_audio_confirmations.rb b/db/migrate/20190912195438_add_edl_type_to_audio_confirmations.rb new file mode 100644 index 0000000..7b6c659 --- /dev/null +++ b/db/migrate/20190912195438_add_edl_type_to_audio_confirmations.rb @@ -0,0 +1,5 @@ +class AddEdlTypeToAudioConfirmations < ActiveRecord::Migration[6.0] + def change + add_column :audio_confirmations, :edl_type, :string, default: "all_tracks", null: false + end +end diff --git a/db/migrate/20190914142320_add_video_editing_system_to_videos.rb b/db/migrate/20190914142320_add_video_editing_system_to_videos.rb new file mode 100644 index 0000000..358c465 --- /dev/null +++ b/db/migrate/20190914142320_add_video_editing_system_to_videos.rb @@ -0,0 +1,5 @@ +class AddVideoEditingSystemToVideos < ActiveRecord::Migration[6.0] + def change + add_column :videos, :video_editing_system, :integer, default: 0, null: false + end +end diff --git a/db/migrate/20190916215209_add_remember_created_at_to_users.rb b/db/migrate/20190916215209_add_remember_created_at_to_users.rb new file mode 100644 index 0000000..dba7e07 --- /dev/null +++ b/db/migrate/20190916215209_add_remember_created_at_to_users.rb @@ -0,0 +1,5 @@ +class AddRememberCreatedAtToUsers < ActiveRecord::Migration[6.0] + def change + add_column :users, :remember_created_at, :datetime + end +end diff --git a/db/migrate/20190927164725_add_cae_number_to_composers.rb b/db/migrate/20190927164725_add_cae_number_to_composers.rb new file mode 100644 index 0000000..b67d63d --- /dev/null +++ b/db/migrate/20190927164725_add_cae_number_to_composers.rb @@ -0,0 +1,5 @@ +class AddCaeNumberToComposers < ActiveRecord::Migration[6.0] + def change + add_column :composers, :cae_number, :string + end +end diff --git a/db/migrate/20191007145140_add_note_category_to_unreleased_appearance.rb b/db/migrate/20191007145140_add_note_category_to_unreleased_appearance.rb new file mode 100644 index 0000000..b4ffe3d --- /dev/null +++ b/db/migrate/20191007145140_add_note_category_to_unreleased_appearance.rb @@ -0,0 +1,5 @@ +class AddNoteCategoryToUnreleasedAppearance < ActiveRecord::Migration[6.0] + def change + add_column :unreleased_appearances, :note_category, :integer, null: false, default: 0 + end +end diff --git a/db/migrate/20191007155625_add_graphic_type_to_graphics_elements.rb b/db/migrate/20191007155625_add_graphic_type_to_graphics_elements.rb new file mode 100644 index 0000000..16ca6af --- /dev/null +++ b/db/migrate/20191007155625_add_graphic_type_to_graphics_elements.rb @@ -0,0 +1,5 @@ +class AddGraphicTypeToGraphicsElements < ActiveRecord::Migration[6.0] + def change + add_column :graphics_elements, :graphic_type, :string + end +end diff --git a/db/migrate/20191011094300_add_exploitable_rights_tables.rb b/db/migrate/20191011094300_add_exploitable_rights_tables.rb new file mode 100644 index 0000000..490e15d --- /dev/null +++ b/db/migrate/20191011094300_add_exploitable_rights_tables.rb @@ -0,0 +1,9 @@ +class AddExploitableRightsTables < ActiveRecord::Migration[6.0] + def change + [:applicable_media, :territories, :terms, :restrictions].each do |table_name| + create_table table_name do |t| + t.string :label, null: false, unique:true + end + end + end +end diff --git a/db/migrate/20191011094400_add_exploitable_rights_to_appearance_release.rb b/db/migrate/20191011094400_add_exploitable_rights_to_appearance_release.rb new file mode 100644 index 0000000..67e020c --- /dev/null +++ b/db/migrate/20191011094400_add_exploitable_rights_to_appearance_release.rb @@ -0,0 +1,12 @@ +class AddExploitableRightsToAppearanceRelease < ActiveRecord::Migration[6.0] + def change + add_reference :appearance_releases, :applicable_medium, foreign_key: true + add_column :appearance_releases, :applicable_medium_text, :text, null: false, default: '' + add_reference :appearance_releases, :territory, foreign_key: true + add_column :appearance_releases, :territory_text, :text, null: false, default: '' + add_reference :appearance_releases, :term, foreign_key: true + add_column :appearance_releases, :term_text, :text, null: false, default: '' + add_reference :appearance_releases, :restriction, foreign_key: true + add_column :appearance_releases, :restriction_text, :text, null: false, default: '' + end +end diff --git a/db/migrate/20191011222149_add_exploitable_rights_to_location_releases.rb b/db/migrate/20191011222149_add_exploitable_rights_to_location_releases.rb new file mode 100644 index 0000000..fd87628 --- /dev/null +++ b/db/migrate/20191011222149_add_exploitable_rights_to_location_releases.rb @@ -0,0 +1,12 @@ +class AddExploitableRightsToLocationReleases < ActiveRecord::Migration[6.0] + def change + add_reference :location_releases, :applicable_medium, foreign_key: true + add_column :location_releases, :applicable_medium_text, :string + add_reference :location_releases, :territory, foreign_key: true + add_column :location_releases, :territory_text, :string + add_reference :location_releases, :term, foreign_key: true + add_column :location_releases, :term_text, :string + add_reference :location_releases, :restriction, foreign_key: true + add_column :location_releases, :restriction_text, :string + end +end diff --git a/db/migrate/20191011230849_add_exploitable_rights_to_acquired_media_releases.rb b/db/migrate/20191011230849_add_exploitable_rights_to_acquired_media_releases.rb new file mode 100644 index 0000000..bb4d881 --- /dev/null +++ b/db/migrate/20191011230849_add_exploitable_rights_to_acquired_media_releases.rb @@ -0,0 +1,15 @@ +class AddExploitableRightsToAcquiredMediaReleases < ActiveRecord::Migration[6.0] + def change + rename_column :acquired_media_releases, :territory, :territory_old + rename_column :acquired_media_releases, :term, :term_old + + add_reference :acquired_media_releases, :applicable_medium, foreign_key: true + add_column :acquired_media_releases, :applicable_medium_text, :string + add_reference :acquired_media_releases, :territory, foreign_key: true + add_column :acquired_media_releases, :territory_text, :string + add_reference :acquired_media_releases, :term, foreign_key: true + add_column :acquired_media_releases, :term_text, :string + add_reference :acquired_media_releases, :restriction, foreign_key: true + add_column :acquired_media_releases, :restriction_text, :string + end +end diff --git a/db/migrate/20191014162947_add_exploitable_rights_to_talent_release.rb b/db/migrate/20191014162947_add_exploitable_rights_to_talent_release.rb new file mode 100644 index 0000000..7d34f19 --- /dev/null +++ b/db/migrate/20191014162947_add_exploitable_rights_to_talent_release.rb @@ -0,0 +1,12 @@ +class AddExploitableRightsToTalentRelease < ActiveRecord::Migration[6.0] + def change + add_reference :talent_releases, :applicable_medium, foreign_key: true + add_column :talent_releases, :applicable_medium_text, :string + add_reference :talent_releases, :territory, foreign_key: true + add_column :talent_releases, :territory_text, :string + add_reference :talent_releases, :term, foreign_key: true + add_column :talent_releases, :term_text, :string + add_reference :talent_releases, :restriction, foreign_key: true + add_column :talent_releases, :restriction_text, :string + end +end diff --git a/db/migrate/20191015140222_add_exploitable_rights_to_music_release.rb b/db/migrate/20191015140222_add_exploitable_rights_to_music_release.rb new file mode 100644 index 0000000..1d851cd --- /dev/null +++ b/db/migrate/20191015140222_add_exploitable_rights_to_music_release.rb @@ -0,0 +1,12 @@ +class AddExploitableRightsToMusicRelease < ActiveRecord::Migration[6.0] + def change + add_reference :music_releases, :applicable_medium, foreign_key: true + add_column :music_releases, :applicable_medium_text, :string + add_reference :music_releases, :territory, foreign_key: true + add_column :music_releases, :territory_text, :string + add_reference :music_releases, :term, foreign_key: true + add_column :music_releases, :term_text, :string + add_reference :music_releases, :restriction, foreign_key: true + add_column :music_releases, :restriction_text, :string + end +end diff --git a/db/migrate/20191016141651_add_exploitable_rights_to_material_release.rb b/db/migrate/20191016141651_add_exploitable_rights_to_material_release.rb new file mode 100644 index 0000000..78c651e --- /dev/null +++ b/db/migrate/20191016141651_add_exploitable_rights_to_material_release.rb @@ -0,0 +1,12 @@ +class AddExploitableRightsToMaterialRelease < ActiveRecord::Migration[6.0] + def change + add_reference :material_releases, :applicable_medium, foreign_key: true + add_column :material_releases, :applicable_medium_text, :string + add_reference :material_releases, :territory, foreign_key: true + add_column :material_releases, :territory_text, :string + add_reference :material_releases, :term, foreign_key: true + add_column :material_releases, :term_text, :string + add_reference :material_releases, :restriction, foreign_key: true + add_column :material_releases, :restriction_text, :string + end +end diff --git a/db/migrate/20191017163920_add_exploitable_rights_to_contract_templates.rb b/db/migrate/20191017163920_add_exploitable_rights_to_contract_templates.rb new file mode 100644 index 0000000..4990331 --- /dev/null +++ b/db/migrate/20191017163920_add_exploitable_rights_to_contract_templates.rb @@ -0,0 +1,12 @@ +class AddExploitableRightsToContractTemplates < ActiveRecord::Migration[6.0] + def change + add_reference :contract_templates, :applicable_medium, foreign_key: true + add_column :contract_templates, :applicable_medium_text, :string + add_reference :contract_templates, :territory, foreign_key: true + add_column :contract_templates, :territory_text, :string + add_reference :contract_templates, :term, foreign_key: true + add_column :contract_templates, :term_text, :string + add_reference :contract_templates, :restriction, foreign_key: true + add_column :contract_templates, :restriction_text, :string + end +end diff --git a/db/migrate/20191022195447_rename_releasable_notes_columns.rb b/db/migrate/20191022195447_rename_releasable_notes_columns.rb new file mode 100644 index 0000000..e967c10 --- /dev/null +++ b/db/migrate/20191022195447_rename_releasable_notes_columns.rb @@ -0,0 +1,10 @@ +class RenameReleasableNotesColumns < ActiveRecord::Migration[6.0] + def change + rename_column :talent_releases, :notes, :notes_old + rename_column :appearance_releases, :notes, :notes_old + rename_column :location_releases, :notes, :notes_old + rename_column :acquired_media_releases, :notes, :notes_old + rename_column :material_releases, :notes, :notes_old + rename_column :music_releases, :notes, :notes_old + end +end diff --git a/db/migrate/20191022195448_create_notes.rb b/db/migrate/20191022195448_create_notes.rb new file mode 100644 index 0000000..91da970 --- /dev/null +++ b/db/migrate/20191022195448_create_notes.rb @@ -0,0 +1,11 @@ +class CreateNotes < ActiveRecord::Migration[6.0] + def change + create_table :notes do |t| + t.belongs_to :user, null: false, foreign_key: true + t.belongs_to :notable, polymorphic: true, null: false + t.text :content + + t.timestamps + end + end +end diff --git a/db/migrate/20191126155350_create_account_auths.rb b/db/migrate/20191126155350_create_account_auths.rb new file mode 100644 index 0000000..355832f --- /dev/null +++ b/db/migrate/20191126155350_create_account_auths.rb @@ -0,0 +1,17 @@ +class CreateAccountAuths < ActiveRecord::Migration[6.0] + def change + create_table :account_auths do |t| + t.integer :user_id + t.integer :account_id + t.integer :role + + t.timestamps + end + + rename_column :users, :role, :role_old + rename_column :users, :account_id, :account_id_old + + change_column_null(:users, :role_old, true) + change_column_null(:users, :account_id_old, true) + end +end diff --git a/db/migrate/20191206181600_create_project_memberships.rb b/db/migrate/20191206181600_create_project_memberships.rb new file mode 100644 index 0000000..2058a45 --- /dev/null +++ b/db/migrate/20191206181600_create_project_memberships.rb @@ -0,0 +1,10 @@ +class CreateProjectMemberships < ActiveRecord::Migration[6.0] + def change + create_table :project_memberships do |t| + t.belongs_to :project, null: false, foreign_key: true + t.belongs_to :user, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20191223123415_create_directories.rb b/db/migrate/20191223123415_create_directories.rb new file mode 100644 index 0000000..9c89e4a --- /dev/null +++ b/db/migrate/20191223123415_create_directories.rb @@ -0,0 +1,12 @@ +class CreateDirectories < ActiveRecord::Migration[6.0] + def change + create_table :directories do |t| + t.references :project + t.references :user + t.string :name, null: false + t.integer :category, default: 0 + + t.timestamps + end + end +end diff --git a/db/migrate/20191230115419_add_permissions_to_directories.rb b/db/migrate/20191230115419_add_permissions_to_directories.rb new file mode 100644 index 0000000..e95ff40 --- /dev/null +++ b/db/migrate/20191230115419_add_permissions_to_directories.rb @@ -0,0 +1,5 @@ +class AddPermissionsToDirectories < ActiveRecord::Migration[6.0] + def change + add_column :directories, :permissions, :integer, default: 0 + end +end diff --git a/db/migrate/20200106111835_add_first_and_last_names_to_user.rb b/db/migrate/20200106111835_add_first_and_last_names_to_user.rb new file mode 100644 index 0000000..5bdc6d4 --- /dev/null +++ b/db/migrate/20200106111835_add_first_and_last_names_to_user.rb @@ -0,0 +1,6 @@ +class AddFirstAndLastNamesToUser < ActiveRecord::Migration[6.0] + def change + add_column :users, :first_name, :string + add_column :users, :last_name, :string + end +end diff --git a/db/migrate/20200107192032_set_default_value_for_role_on_account_auths.rb b/db/migrate/20200107192032_set_default_value_for_role_on_account_auths.rb new file mode 100644 index 0000000..708345a --- /dev/null +++ b/db/migrate/20200107192032_set_default_value_for_role_on_account_auths.rb @@ -0,0 +1,5 @@ +class SetDefaultValueForRoleOnAccountAuths < ActiveRecord::Migration[6.0] + def change + change_column :account_auths, :role, :integer, default: 0 + end +end diff --git a/db/migrate/20200127180819_add_category_to_bookmarks.rb b/db/migrate/20200127180819_add_category_to_bookmarks.rb new file mode 100644 index 0000000..97b1ac5 --- /dev/null +++ b/db/migrate/20200127180819_add_category_to_bookmarks.rb @@ -0,0 +1,5 @@ +class AddCategoryToBookmarks < ActiveRecord::Migration[6.0] + def change + add_column :bookmarks, :category, :integer, default: 0 + end +end diff --git a/db/migrate/20200210164619_add_signed_at_to_all_releases.rb b/db/migrate/20200210164619_add_signed_at_to_all_releases.rb new file mode 100644 index 0000000..a2c1f55 --- /dev/null +++ b/db/migrate/20200210164619_add_signed_at_to_all_releases.rb @@ -0,0 +1,7 @@ +class AddSignedAtToAllReleases < ActiveRecord::Migration[6.0] + def change + [:appearance_releases, :talent_releases, :location_releases, :material_releases].each do |table| + add_column table, :signed_at, :datetime + end + end +end diff --git a/db/migrate/20200210210223_add_start_end_datest_to_location_release.rb b/db/migrate/20200210210223_add_start_end_datest_to_location_release.rb new file mode 100644 index 0000000..ff482a0 --- /dev/null +++ b/db/migrate/20200210210223_add_start_end_datest_to_location_release.rb @@ -0,0 +1,6 @@ +class AddStartEndDatestToLocationRelease < ActiveRecord::Migration[6.0] + def change + add_column :location_releases, :filming_started_on, :date + add_column :location_releases, :filming_ended_on, :date + end +end diff --git a/db/migrate/20200211172631_add_birth_date_to_appearance_release.rb b/db/migrate/20200211172631_add_birth_date_to_appearance_release.rb new file mode 100644 index 0000000..b6b1e8c --- /dev/null +++ b/db/migrate/20200211172631_add_birth_date_to_appearance_release.rb @@ -0,0 +1,5 @@ +class AddBirthDateToAppearanceRelease < ActiveRecord::Migration[6.0] + def change + add_column :appearance_releases, :person_date_of_birth, :date + end +end diff --git a/db/migrate/20200213065453_add_description_to_material_release.rb b/db/migrate/20200213065453_add_description_to_material_release.rb new file mode 100644 index 0000000..cbf359a --- /dev/null +++ b/db/migrate/20200213065453_add_description_to_material_release.rb @@ -0,0 +1,5 @@ +class AddDescriptionToMaterialRelease < ActiveRecord::Migration[6.0] + def change + add_column :material_releases, :description, :text + end +end diff --git a/db/migrate/20200217132429_add_categories_to_acquired_media_releases.rb b/db/migrate/20200217132429_add_categories_to_acquired_media_releases.rb new file mode 100644 index 0000000..57b4b05 --- /dev/null +++ b/db/migrate/20200217132429_add_categories_to_acquired_media_releases.rb @@ -0,0 +1,5 @@ +class AddCategoriesToAcquiredMediaReleases < ActiveRecord::Migration[6.0] + def change + add_column :acquired_media_releases, :categories, :string, array: true, default: [] + end +end diff --git a/db/migrate/20200219163255_add_person_fax_to_acquired_media_releases.rb b/db/migrate/20200219163255_add_person_fax_to_acquired_media_releases.rb new file mode 100644 index 0000000..c6134da --- /dev/null +++ b/db/migrate/20200219163255_add_person_fax_to_acquired_media_releases.rb @@ -0,0 +1,6 @@ +class AddPersonFaxToAcquiredMediaReleases < ActiveRecord::Migration[6.0] + def change + add_column :acquired_media_releases, :person_fax, :string + add_column :acquired_media_releases, :description, :text + end +end diff --git a/db/migrate/20200228144628_create_downloads.rb b/db/migrate/20200228144628_create_downloads.rb new file mode 100644 index 0000000..3681b79 --- /dev/null +++ b/db/migrate/20200228144628_create_downloads.rb @@ -0,0 +1,11 @@ +class CreateDownloads < ActiveRecord::Migration[6.0] + def change + create_table :downloads do |t| + t.references :project + t.string :name + t.string :release_type + + t.timestamps + end + end +end diff --git a/db/migrate/20200228154850_change_fee_cents_to_bigint.rb b/db/migrate/20200228154850_change_fee_cents_to_bigint.rb new file mode 100644 index 0000000..c1da680 --- /dev/null +++ b/db/migrate/20200228154850_change_fee_cents_to_bigint.rb @@ -0,0 +1,5 @@ +class ChangeFeeCentsToBigint < ActiveRecord::Migration[6.0] + def change + change_column :contract_templates, :fee_cents, :integer, limit: 8 + end +end diff --git a/db/migrate/20200303232746_add_audio_analysis_columns_to_videos.rb b/db/migrate/20200303232746_add_audio_analysis_columns_to_videos.rb new file mode 100644 index 0000000..afc0b98 --- /dev/null +++ b/db/migrate/20200303232746_add_audio_analysis_columns_to_videos.rb @@ -0,0 +1,8 @@ +class AddAudioAnalysisColumnsToVideos < ActiveRecord::Migration[6.0] + def change + add_column :videos, :audio_analysis_started_at, :datetime + add_column :videos, :audio_analysis_status, :integer, default: 0 + add_column :videos, :audio_analysis_uid, :string + add_index :videos, :audio_analysis_uid + end +end diff --git a/db/migrate/20200304191424_add_archived_at_to_contract_templates.rb b/db/migrate/20200304191424_add_archived_at_to_contract_templates.rb new file mode 100755 index 0000000..c254620 --- /dev/null +++ b/db/migrate/20200304191424_add_archived_at_to_contract_templates.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddArchivedAtToContractTemplates < ActiveRecord::Migration[6.0] + def change + add_column :contract_templates, :archived_at, :datetime, default: nil, null: true + end +end diff --git a/db/migrate/20200308181050_add_person_first_and_last_name_to_releases.rb b/db/migrate/20200308181050_add_person_first_and_last_name_to_releases.rb new file mode 100644 index 0000000..80de15f --- /dev/null +++ b/db/migrate/20200308181050_add_person_first_and_last_name_to_releases.rb @@ -0,0 +1,22 @@ +class AddPersonFirstAndLastNameToReleases < ActiveRecord::Migration[6.0] + RELEASES = [:acquired_media_releases, :appearance_releases, :location_releases, :material_releases, :music_releases, :talent_releases] + + def up + RELEASES.each do |table_name| + add_column(table_name, :person_first_name, :string) + add_column(table_name, :person_last_name, :string) + + change_column(table_name, :person_name, :string, null: true) + rename_column(table_name, :person_name, :person_name_old) + end + end + + def down + RELEASES.each do |table_name| + remove_column(table_name, :person_first_name, :string) + remove_column(table_name, :person_last_name, :string) + + rename_column(table_name, :person_name_old, :person_name) + end + end +end diff --git a/db/migrate/20200309164624_add_signed_at_to_acquired_media_releases.rb b/db/migrate/20200309164624_add_signed_at_to_acquired_media_releases.rb new file mode 100644 index 0000000..6a6bbf7 --- /dev/null +++ b/db/migrate/20200309164624_add_signed_at_to_acquired_media_releases.rb @@ -0,0 +1,5 @@ +class AddSignedAtToAcquiredMediaReleases < ActiveRecord::Migration[6.0] + def change + add_column :acquired_media_releases, :signed_at, :datetime + end +end diff --git a/db/migrate/20200317142545_add_time_zone_to_users.rb b/db/migrate/20200317142545_add_time_zone_to_users.rb new file mode 100644 index 0000000..7caab74 --- /dev/null +++ b/db/migrate/20200317142545_add_time_zone_to_users.rb @@ -0,0 +1,5 @@ +class AddTimeZoneToUsers < ActiveRecord::Migration[6.0] + def change + add_column :users, :time_zone, :string, null: false, default: 'UTC' + end +end diff --git a/db/migrate/20200318143154_add_status_to_downloads.rb b/db/migrate/20200318143154_add_status_to_downloads.rb new file mode 100644 index 0000000..0e1c68c --- /dev/null +++ b/db/migrate/20200318143154_add_status_to_downloads.rb @@ -0,0 +1,5 @@ +class AddStatusToDownloads < ActiveRecord::Migration[6.0] + def change + add_column :downloads, :status, :integer, default: 0 + end +end diff --git a/db/migrate/20200323195446_add_guardian_first_and_last_name_to_releases.rb b/db/migrate/20200323195446_add_guardian_first_and_last_name_to_releases.rb new file mode 100644 index 0000000..ecb2827 --- /dev/null +++ b/db/migrate/20200323195446_add_guardian_first_and_last_name_to_releases.rb @@ -0,0 +1,21 @@ +class AddGuardianFirstAndLastNameToReleases < ActiveRecord::Migration[6.0] + RELEASES = [:appearance_releases, :talent_releases] + + def up + RELEASES.each do |table_name| + add_column(table_name, :guardian_first_name, :string) + add_column(table_name, :guardian_last_name, :string) + + rename_column(table_name, :guardian_name, :guardian_name_old) + end + end + + def down + RELEASES.each do |table_name| + remove_column(table_name, :guardian_first_name, :string) + remove_column(table_name, :guardian_last_name, :string) + + rename_column(table_name, :guardian_name_old, :guardian_name) + end + end +end diff --git a/db/migrate/20200326111351_remove_user_foreign_key_constraint_from_notes.rb b/db/migrate/20200326111351_remove_user_foreign_key_constraint_from_notes.rb new file mode 100644 index 0000000..5d455c6 --- /dev/null +++ b/db/migrate/20200326111351_remove_user_foreign_key_constraint_from_notes.rb @@ -0,0 +1,5 @@ +class RemoveUserForeignKeyConstraintFromNotes < ActiveRecord::Migration[5.2] + def change + remove_foreign_key :notes, :users + end +end diff --git a/db/migrate/20200326115546_create_broadcasts.rb b/db/migrate/20200326115546_create_broadcasts.rb new file mode 100644 index 0000000..f383275 --- /dev/null +++ b/db/migrate/20200326115546_create_broadcasts.rb @@ -0,0 +1,11 @@ +class CreateBroadcasts < ActiveRecord::Migration[6.0] + def change + create_table :broadcasts do |t| + t.references :project + t.string :name + t.integer :status, default: 0 + + t.timestamps + end + end +end diff --git a/db/migrate/20200326172542_create_zoom_meetings.rb b/db/migrate/20200326172542_create_zoom_meetings.rb new file mode 100644 index 0000000..fe0fd1f --- /dev/null +++ b/db/migrate/20200326172542_create_zoom_meetings.rb @@ -0,0 +1,9 @@ +class CreateZoomMeetings < ActiveRecord::Migration[6.0] + def change + create_table :zoom_meetings do |t| + t.string :api_meeting_id + + t.timestamps + end + end +end diff --git a/db/migrate/20200327122450_add_broadcast_to_zoom_meetings.rb b/db/migrate/20200327122450_add_broadcast_to_zoom_meetings.rb new file mode 100644 index 0000000..df2bcfd --- /dev/null +++ b/db/migrate/20200327122450_add_broadcast_to_zoom_meetings.rb @@ -0,0 +1,5 @@ +class AddBroadcastToZoomMeetings < ActiveRecord::Migration[6.0] + def change + add_reference :zoom_meetings, :broadcast, null: true, foreign_key: true + end +end diff --git a/db/migrate/20200330102704_add_email_to_notes.rb b/db/migrate/20200330102704_add_email_to_notes.rb new file mode 100644 index 0000000..5d39a4c --- /dev/null +++ b/db/migrate/20200330102704_add_email_to_notes.rb @@ -0,0 +1,5 @@ +class AddEmailToNotes < ActiveRecord::Migration[5.2] + def change + add_column :notes, :email, :string + end +end diff --git a/db/migrate/20200330145401_remove_notes_old_column_from_releases.rb b/db/migrate/20200330145401_remove_notes_old_column_from_releases.rb new file mode 100644 index 0000000..b66ecff --- /dev/null +++ b/db/migrate/20200330145401_remove_notes_old_column_from_releases.rb @@ -0,0 +1,16 @@ +class RemoveNotesOldColumnFromReleases < ActiveRecord::Migration[6.0] + RELEASES = %i[ + acquired_media_releases + appearance_releases + location_releases + material_releases + music_releases + talent_releases + ].freeze + + def change + RELEASES.each do |table_name| + remove_column table_name, :notes_old, :text + end + end +end diff --git a/db/migrate/20200402190734_add_stream_data_to_broadcasts.rb b/db/migrate/20200402190734_add_stream_data_to_broadcasts.rb new file mode 100644 index 0000000..9d505d2 --- /dev/null +++ b/db/migrate/20200402190734_add_stream_data_to_broadcasts.rb @@ -0,0 +1,7 @@ +class AddStreamDataToBroadcasts < ActiveRecord::Migration[6.0] + def change + add_column :broadcasts, :stream_uid, :string + add_column :broadcasts, :stream_key, :string + add_column :broadcasts, :stream_playback_uid, :string + end +end diff --git a/db/migrate/20200413124619_add_token_to_broadcasts.rb b/db/migrate/20200413124619_add_token_to_broadcasts.rb new file mode 100644 index 0000000..1171809 --- /dev/null +++ b/db/migrate/20200413124619_add_token_to_broadcasts.rb @@ -0,0 +1,6 @@ +class AddTokenToBroadcasts < ActiveRecord::Migration[6.0] + def change + add_column :broadcasts, :token, :string + add_index :broadcasts, :token, unique: true + end +end diff --git a/db/migrate/20200424142736_create_zoom_users.rb b/db/migrate/20200424142736_create_zoom_users.rb new file mode 100644 index 0000000..dcaff6c --- /dev/null +++ b/db/migrate/20200424142736_create_zoom_users.rb @@ -0,0 +1,10 @@ +class CreateZoomUsers < ActiveRecord::Migration[6.0] + def change + create_table :zoom_users do |t| + t.references :project, null: true, foreign_key: true + t.string :api_id + + t.timestamps + end + end +end diff --git a/db/migrate/20200424161117_add_zoom_user_and_project_to_zoom_meetings.rb b/db/migrate/20200424161117_add_zoom_user_and_project_to_zoom_meetings.rb new file mode 100644 index 0000000..341df73 --- /dev/null +++ b/db/migrate/20200424161117_add_zoom_user_and_project_to_zoom_meetings.rb @@ -0,0 +1,6 @@ +class AddZoomUserAndProjectToZoomMeetings < ActiveRecord::Migration[6.0] + def change + add_reference :zoom_meetings, :zoom_user, null: true, foreign_key: true + add_reference :zoom_meetings, :project, null: true, foreign_key: true + end +end diff --git a/db/migrate/20200427073429_add_streamer_status_to_broadcasts.rb b/db/migrate/20200427073429_add_streamer_status_to_broadcasts.rb new file mode 100644 index 0000000..61f6a75 --- /dev/null +++ b/db/migrate/20200427073429_add_streamer_status_to_broadcasts.rb @@ -0,0 +1,5 @@ +class AddStreamerStatusToBroadcasts < ActiveRecord::Migration[6.0] + def change + add_column :broadcasts, :streamer_status, :integer, default: 0 + end +end diff --git a/db/migrate/20200428091105_create_broadcast_recordings.rb b/db/migrate/20200428091105_create_broadcast_recordings.rb new file mode 100644 index 0000000..8b49d32 --- /dev/null +++ b/db/migrate/20200428091105_create_broadcast_recordings.rb @@ -0,0 +1,12 @@ +class CreateBroadcastRecordings < ActiveRecord::Migration[6.0] + def change + create_table :broadcast_recordings do |t| + t.references :broadcast + t.string :asset_uid, null: false, index: {unique: true} + t.string :asset_playback_uid, null: false + t.string :file_name, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20200507110804_add_status_to_zoom_meetings.rb b/db/migrate/20200507110804_add_status_to_zoom_meetings.rb new file mode 100644 index 0000000..73c1597 --- /dev/null +++ b/db/migrate/20200507110804_add_status_to_zoom_meetings.rb @@ -0,0 +1,5 @@ +class AddStatusToZoomMeetings < ActiveRecord::Migration[6.0] + def change + add_column :zoom_meetings, :status, :integer, default: 0 + end +end diff --git a/db/migrate/20200512161738_remove_project_from_zoom_users.rb b/db/migrate/20200512161738_remove_project_from_zoom_users.rb new file mode 100644 index 0000000..2b90e9e --- /dev/null +++ b/db/migrate/20200512161738_remove_project_from_zoom_users.rb @@ -0,0 +1,5 @@ +class RemoveProjectFromZoomUsers < ActiveRecord::Migration[6.0] + def change + remove_column :zoom_users, :project_id + end +end diff --git a/db/seeds.rb b/db/seeds.rb new file mode 100644 index 0000000..1827d90 --- /dev/null +++ b/db/seeds.rb @@ -0,0 +1,27 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). +# +# Examples: +# +# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) +# Character.create(name: 'Luke', movie: movies.first)i + +ApplicableMedium.destroy_all +[:other, :all].each do |label| + ApplicableMedium.create(label: label.to_s.humanize) +end + +Territory.destroy_all +[:other, :worldwide].each do |label| + Territory.create(label: label.to_s.humanize) +end + +Term.destroy_all +[:other, :in_perpetuity].each do |label| + Term.create(label: label.to_s.humanize) +end + +Restriction.destroy_all +[:other, :none].each do |label| + Restriction.create(label: label.to_s.humanize) +end diff --git a/db/structure.sql b/db/structure.sql new file mode 100644 index 0000000..b748069 --- /dev/null +++ b/db/structure.sql @@ -0,0 +1,3504 @@ +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: fuzzystrmatch; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS fuzzystrmatch WITH SCHEMA public; + + +-- +-- Name: EXTENSION fuzzystrmatch; Type: COMMENT; Schema: -; Owner: - +-- + +COMMENT ON EXTENSION fuzzystrmatch IS 'determine similarities and distance between strings'; + + +-- +-- Name: pg_trgm; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public; + + +-- +-- Name: EXTENSION pg_trgm; Type: COMMENT; Schema: -; Owner: - +-- + +COMMENT ON EXTENSION pg_trgm IS 'text similarity measurement and index searching based on trigrams'; + + +-- +-- Name: pg_search_dmetaphone(text); Type: FUNCTION; Schema: public; Owner: - +-- + +CREATE FUNCTION public.pg_search_dmetaphone(text) RETURNS text + LANGUAGE sql IMMUTABLE STRICT + AS $_$ + SELECT array_to_string(ARRAY(SELECT dmetaphone(unnest(regexp_split_to_array($1, E'\\s+')))), ' ') +$_$; + + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: account_auths; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.account_auths ( + id bigint NOT NULL, + user_id integer, + account_id integer, + role integer DEFAULT 0, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: account_auths_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.account_auths_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: account_auths_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.account_auths_id_seq OWNED BY public.account_auths.id; + + +-- +-- Name: accounts; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.accounts ( + id bigint NOT NULL, + name character varying, + slug character varying, + plan_uid character varying, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL +); + + +-- +-- Name: accounts_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.accounts_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: accounts_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.accounts_id_seq OWNED BY public.accounts.id; + + +-- +-- Name: acquired_media_releases; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.acquired_media_releases ( + id bigint NOT NULL, + project_id bigint, + contract_template_id bigint, + locale character varying, + tagging_status integer DEFAULT 0, + name character varying, + person_name_old character varying, + person_address_street1 character varying, + person_address_street2 character varying, + person_address_city character varying, + person_address_state character varying, + person_address_zip character varying, + person_address_country character varying, + person_phone character varying, + person_email character varying, + person_title character varying, + person_company character varying, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + collection_uid character varying, + territory_old character varying, + term_old character varying, + applicable_medium_id bigint, + applicable_medium_text character varying, + territory_id bigint, + territory_text character varying, + term_id bigint, + term_text character varying, + restriction_id bigint, + restriction_text character varying, + categories character varying[] DEFAULT '{}'::character varying[], + person_fax character varying, + description text, + person_first_name character varying, + person_last_name character varying, + signed_at timestamp without time zone +); + + +-- +-- Name: acquired_media_releases_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.acquired_media_releases_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: acquired_media_releases_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.acquired_media_releases_id_seq OWNED BY public.acquired_media_releases.id; + + +-- +-- Name: action_text_rich_texts; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.action_text_rich_texts ( + id bigint NOT NULL, + name character varying NOT NULL, + body text, + record_type character varying NOT NULL, + record_id bigint NOT NULL, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL +); + + +-- +-- Name: action_text_rich_texts_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.action_text_rich_texts_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: action_text_rich_texts_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.action_text_rich_texts_id_seq OWNED BY public.action_text_rich_texts.id; + + +-- +-- Name: active_storage_attachments; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.active_storage_attachments ( + id bigint NOT NULL, + name character varying NOT NULL, + record_type character varying NOT NULL, + record_id bigint NOT NULL, + blob_id bigint NOT NULL, + created_at timestamp without time zone NOT NULL +); + + +-- +-- Name: active_storage_attachments_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.active_storage_attachments_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: active_storage_attachments_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.active_storage_attachments_id_seq OWNED BY public.active_storage_attachments.id; + + +-- +-- Name: active_storage_blobs; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.active_storage_blobs ( + id bigint NOT NULL, + key character varying NOT NULL, + filename character varying NOT NULL, + content_type character varying, + metadata text, + byte_size bigint NOT NULL, + checksum character varying NOT NULL, + created_at timestamp without time zone NOT NULL +); + + +-- +-- Name: active_storage_blobs_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.active_storage_blobs_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: active_storage_blobs_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.active_storage_blobs_id_seq OWNED BY public.active_storage_blobs.id; + + +-- +-- Name: appearance_releases; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.appearance_releases ( + id bigint NOT NULL, + person_name_old character varying, + person_address character varying, + person_phone character varying, + project_id bigint, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + minor boolean DEFAULT false, + guardian_address character varying, + guardian_name_old character varying, + guardian_phone character varying, + person_email character varying, + locale character varying, + tagging_status integer DEFAULT 0, + contract_template_id bigint, + applicable_medium_id bigint, + applicable_medium_text text DEFAULT ''::text NOT NULL, + territory_id bigint, + territory_text text DEFAULT ''::text NOT NULL, + term_id bigint, + term_text text DEFAULT ''::text NOT NULL, + restriction_id bigint, + restriction_text text DEFAULT ''::text NOT NULL, + signed_at timestamp without time zone, + person_date_of_birth date, + person_first_name character varying, + person_last_name character varying, + guardian_first_name character varying, + guardian_last_name character varying +); + + +-- +-- Name: appearance_releases_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.appearance_releases_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: appearance_releases_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.appearance_releases_id_seq OWNED BY public.appearance_releases.id; + + +-- +-- Name: applicable_media; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.applicable_media ( + id bigint NOT NULL, + label character varying NOT NULL +); + + +-- +-- Name: applicable_media_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.applicable_media_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: applicable_media_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.applicable_media_id_seq OWNED BY public.applicable_media.id; + + +-- +-- Name: ar_internal_metadata; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.ar_internal_metadata ( + key character varying NOT NULL, + value character varying, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: audio_confirmations; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.audio_confirmations ( + id bigint NOT NULL, + video_id bigint, + channel character varying, + timecode_in character varying, + timecode_out character varying, + duration character varying, + source_file_name character varying, + clip_name character varying, + description character varying, + time_elapsed character varying, + music_type character varying, + music_category character varying, + composer_info character varying, + publisher_info character varying, + catalog character varying, + title character varying, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + confirmation_type character varying DEFAULT 'original_music'::character varying NOT NULL, + edl_type character varying DEFAULT 'all_tracks'::character varying NOT NULL +); + + +-- +-- Name: audio_confirmations_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.audio_confirmations_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: audio_confirmations_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.audio_confirmations_id_seq OWNED BY public.audio_confirmations.id; + + +-- +-- Name: bookmarks; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.bookmarks ( + id bigint NOT NULL, + video_id bigint, + time_elapsed character varying, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + notes text, + category integer DEFAULT 0 +); + + +-- +-- Name: bookmarks_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.bookmarks_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: bookmarks_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.bookmarks_id_seq OWNED BY public.bookmarks.id; + + +-- +-- Name: broadcast_recordings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.broadcast_recordings ( + id bigint NOT NULL, + broadcast_id bigint, + asset_uid character varying NOT NULL, + asset_playback_uid character varying NOT NULL, + file_name character varying NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: broadcast_recordings_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.broadcast_recordings_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: broadcast_recordings_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.broadcast_recordings_id_seq OWNED BY public.broadcast_recordings.id; + + +-- +-- Name: broadcasts; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.broadcasts ( + id bigint NOT NULL, + project_id bigint, + name character varying, + status integer DEFAULT 0, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + stream_uid character varying, + stream_key character varying, + stream_playback_uid character varying, + token character varying, + streamer_status integer DEFAULT 0 +); + + +-- +-- Name: broadcasts_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.broadcasts_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: broadcasts_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.broadcasts_id_seq OWNED BY public.broadcasts.id; + + +-- +-- Name: composers; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.composers ( + id bigint NOT NULL, + music_release_id bigint, + name character varying NOT NULL, + affiliation character varying NOT NULL, + percentage double precision NOT NULL, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + cae_number character varying +); + + +-- +-- Name: composers_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.composers_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: composers_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.composers_id_seq OWNED BY public.composers.id; + + +-- +-- Name: contract_templates; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.contract_templates ( + id bigint NOT NULL, + project_id bigint, + name character varying, + body text, + guardian_clause text, + fee_cents bigint DEFAULT 0 NOT NULL, + fee_currency character varying DEFAULT 'USD'::character varying NOT NULL, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + release_type character varying, + parent_id integer, + applicable_medium_id bigint, + applicable_medium_text character varying, + territory_id bigint, + territory_text character varying, + term_id bigint, + term_text character varying, + restriction_id bigint, + restriction_text character varying, + archived_at timestamp without time zone +); + + +-- +-- Name: contract_templates_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.contract_templates_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: contract_templates_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.contract_templates_id_seq OWNED BY public.contract_templates.id; + + +-- +-- Name: data_migrations; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.data_migrations ( + version character varying NOT NULL +); + + +-- +-- Name: directories; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.directories ( + id bigint NOT NULL, + project_id bigint, + user_id bigint, + name character varying NOT NULL, + category integer DEFAULT 0, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + permissions integer DEFAULT 0 +); + + +-- +-- Name: directories_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.directories_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: directories_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.directories_id_seq OWNED BY public.directories.id; + + +-- +-- Name: downloads; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.downloads ( + id bigint NOT NULL, + project_id bigint, + name character varying, + release_type character varying, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + status integer DEFAULT 0 +); + + +-- +-- Name: downloads_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.downloads_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: downloads_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.downloads_id_seq OWNED BY public.downloads.id; + + +-- +-- Name: file_infos; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.file_infos ( + id bigint NOT NULL, + releasable_id bigint, + filename character varying NOT NULL, + content_type character varying, + byte_size bigint, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + releasable_type character varying +); + + +-- +-- Name: file_infos_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.file_infos_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: file_infos_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.file_infos_id_seq OWNED BY public.file_infos.id; + + +-- +-- Name: graphics_elements; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.graphics_elements ( + id bigint NOT NULL, + video_id bigint, + text text, + time_elapsed character varying, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + timecode_in character varying, + timecode_out character varying, + duration character varying, + source_file_name character varying, + clip_name character varying, + description character varying, + edl_type character varying, + channel character varying, + graphic_type character varying +); + + +-- +-- Name: graphics_elements_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.graphics_elements_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: graphics_elements_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.graphics_elements_id_seq OWNED BY public.graphics_elements.id; + + +-- +-- Name: imports; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.imports ( + id bigint NOT NULL, + project_id bigint, + status integer DEFAULT 0, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + releasable_type character varying, + releasable_id bigint +); + + +-- +-- Name: imports_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.imports_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: imports_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.imports_id_seq OWNED BY public.imports.id; + + +-- +-- Name: location_releases; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.location_releases ( + id bigint NOT NULL, + project_id bigint, + name character varying, + address_street1 character varying, + address_street2 character varying, + address_city character varying, + address_state character varying, + address_zip character varying, + address_country character varying, + person_name_old character varying, + person_address_street1 character varying, + person_address_street2 character varying, + person_address_city character varying, + person_address_state character varying, + person_address_zip character varying, + person_address_country character varying, + person_phone character varying, + person_email character varying, + person_title character varying, + person_company character varying, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + tagging_status integer DEFAULT 0, + locale character varying, + contract_template_id bigint, + applicable_medium_id bigint, + applicable_medium_text character varying, + territory_id bigint, + territory_text character varying, + term_id bigint, + term_text character varying, + restriction_id bigint, + restriction_text character varying, + signed_at timestamp without time zone, + filming_started_on date, + filming_ended_on date, + person_first_name character varying, + person_last_name character varying +); + + +-- +-- Name: location_releases_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.location_releases_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: location_releases_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.location_releases_id_seq OWNED BY public.location_releases.id; + + +-- +-- Name: material_releases; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.material_releases ( + id bigint NOT NULL, + project_id bigint, + name character varying, + person_name_old character varying, + person_address_street1 character varying, + person_address_street2 character varying, + person_address_city character varying, + person_address_state character varying, + person_address_zip character varying, + person_address_country character varying, + person_phone character varying, + person_email character varying, + person_title character varying, + person_company character varying, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + tagging_status integer DEFAULT 0, + contract_template_id bigint, + locale character varying, + applicable_medium_id bigint, + applicable_medium_text character varying, + territory_id bigint, + territory_text character varying, + term_id bigint, + term_text character varying, + restriction_id bigint, + restriction_text character varying, + signed_at timestamp without time zone, + description text, + person_first_name character varying, + person_last_name character varying +); + + +-- +-- Name: material_releases_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.material_releases_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: material_releases_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.material_releases_id_seq OWNED BY public.material_releases.id; + + +-- +-- Name: music_releases; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.music_releases ( + id bigint NOT NULL, + project_id bigint, + contract_template_id bigint, + locale character varying, + tagging_status integer DEFAULT 0, + name character varying, + person_name_old character varying, + person_address_street1 character varying, + person_address_street2 character varying, + person_address_city character varying, + person_address_state character varying, + person_address_zip character varying, + person_address_country character varying, + person_phone character varying, + person_email character varying, + person_title character varying, + person_company character varying, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + applicable_medium_id bigint, + applicable_medium_text character varying, + territory_id bigint, + territory_text character varying, + term_id bigint, + term_text character varying, + restriction_id bigint, + restriction_text character varying, + person_first_name character varying, + person_last_name character varying +); + + +-- +-- Name: music_releases_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.music_releases_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: music_releases_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.music_releases_id_seq OWNED BY public.music_releases.id; + + +-- +-- Name: notes; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.notes ( + id bigint NOT NULL, + user_id bigint NOT NULL, + notable_type character varying NOT NULL, + notable_id bigint NOT NULL, + content text, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + email character varying +); + + +-- +-- Name: notes_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.notes_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: notes_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.notes_id_seq OWNED BY public.notes.id; + + +-- +-- Name: project_memberships; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.project_memberships ( + id bigint NOT NULL, + project_id bigint NOT NULL, + user_id bigint NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: project_memberships_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.project_memberships_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: project_memberships_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.project_memberships_id_seq OWNED BY public.project_memberships.id; + + +-- +-- Name: projects; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.projects ( + id bigint NOT NULL, + name character varying NOT NULL, + client_name character varying, + producer_name character varying, + producer_address character varying, + description text, + details text, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + sample boolean DEFAULT false, + headshot_collection_uid character varying, + account_id bigint +); + + +-- +-- Name: projects_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.projects_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: projects_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.projects_id_seq OWNED BY public.projects.id; + + +-- +-- Name: publishers; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.publishers ( + id bigint NOT NULL, + music_release_id bigint, + name character varying NOT NULL, + affiliation character varying NOT NULL, + percentage double precision NOT NULL, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL +); + + +-- +-- Name: publishers_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.publishers_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: publishers_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.publishers_id_seq OWNED BY public.publishers.id; + + +-- +-- Name: restrictions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.restrictions ( + id bigint NOT NULL, + label character varying NOT NULL +); + + +-- +-- Name: restrictions_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.restrictions_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: restrictions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.restrictions_id_seq OWNED BY public.restrictions.id; + + +-- +-- Name: schema_migrations; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.schema_migrations ( + version character varying NOT NULL +); + + +-- +-- Name: settings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.settings ( + id integer NOT NULL, + var character varying NOT NULL, + value text, + target_type character varying NOT NULL, + target_id integer NOT NULL, + created_at timestamp without time zone, + updated_at timestamp without time zone +); + + +-- +-- Name: settings_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.settings_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: settings_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.settings_id_seq OWNED BY public.settings.id; + + +-- +-- Name: taggings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.taggings ( + id integer NOT NULL, + tag_id integer, + taggable_type character varying, + taggable_id integer, + tagger_type character varying, + tagger_id integer, + context character varying(128), + created_at timestamp without time zone +); + + +-- +-- Name: taggings_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.taggings_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: taggings_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.taggings_id_seq OWNED BY public.taggings.id; + + +-- +-- Name: tags; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.tags ( + id integer NOT NULL, + name character varying, + taggings_count integer DEFAULT 0 +); + + +-- +-- Name: tags_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.tags_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: tags_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.tags_id_seq OWNED BY public.tags.id; + + +-- +-- Name: talent_releases; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.talent_releases ( + id bigint NOT NULL, + project_id bigint, + contract_template_id bigint, + person_name_old character varying, + person_address_street1 character varying, + person_address_street2 character varying, + person_address_city character varying, + person_address_state character varying, + person_address_zip character varying, + person_address_country character varying, + person_phone character varying, + person_email character varying, + guardian_name_old character varying, + guardian_address_street1 character varying, + guardian_address_street2 character varying, + guardian_address_city character varying, + guardian_address_state character varying, + guardian_address_zip character varying, + guardian_address_country character varying, + guardian_phone character varying, + guardian_email character varying, + minor boolean DEFAULT false, + locale character varying, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + tagging_status integer DEFAULT 0, + applicable_medium_id bigint, + applicable_medium_text character varying, + territory_id bigint, + territory_text character varying, + term_id bigint, + term_text character varying, + restriction_id bigint, + restriction_text character varying, + signed_at timestamp without time zone, + person_first_name character varying, + person_last_name character varying, + guardian_first_name character varying, + guardian_last_name character varying +); + + +-- +-- Name: talent_releases_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.talent_releases_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: talent_releases_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.talent_releases_id_seq OWNED BY public.talent_releases.id; + + +-- +-- Name: terms; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.terms ( + id bigint NOT NULL, + label character varying NOT NULL +); + + +-- +-- Name: terms_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.terms_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: terms_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.terms_id_seq OWNED BY public.terms.id; + + +-- +-- Name: territories; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.territories ( + id bigint NOT NULL, + label character varying NOT NULL +); + + +-- +-- Name: territories_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.territories_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: territories_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.territories_id_seq OWNED BY public.territories.id; + + +-- +-- Name: unreleased_appearances; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.unreleased_appearances ( + id bigint NOT NULL, + video_id bigint, + time_elapsed character varying, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + notes text, + timecode_in character varying, + timecode_out character varying, + duration character varying, + source_file_name character varying, + clip_name character varying, + description character varying, + channel character varying, + note_category integer DEFAULT 0 NOT NULL +); + + +-- +-- Name: unreleased_appearances_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.unreleased_appearances_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: unreleased_appearances_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.unreleased_appearances_id_seq OWNED BY public.unreleased_appearances.id; + + +-- +-- Name: users; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.users ( + id bigint NOT NULL, + email character varying NOT NULL, + password_digest character varying NOT NULL, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + admin boolean DEFAULT false, + account_id_old bigint, + password_reset_token character varying, + role_old integer DEFAULT 0, + remember_created_at timestamp without time zone, + first_name character varying, + last_name character varying, + time_zone character varying DEFAULT 'UTC'::character varying NOT NULL +); + + +-- +-- Name: users_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.users_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.users_id_seq OWNED BY public.users.id; + + +-- +-- Name: video_release_confirmations; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.video_release_confirmations ( + id bigint NOT NULL, + video_id bigint, + releasable_id bigint, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + time_elapsed character varying, + releasable_type character varying, + file_info_id bigint, + timecode_in character varying, + timecode_out character varying, + duration character varying, + source_file_name character varying, + clip_name character varying, + description character varying, + channel character varying +); + + +-- +-- Name: video_release_confirmations_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.video_release_confirmations_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: video_release_confirmations_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.video_release_confirmations_id_seq OWNED BY public.video_release_confirmations.id; + + +-- +-- Name: videos; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.videos ( + id bigint NOT NULL, + project_id bigint, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + analysis_uid character varying, + analysis_status integer DEFAULT 0, + analysis_started_at timestamp without time zone, + name character varying, + number character varying, + report_published_at timestamp without time zone, + edl_timecode_start character varying, + video_editing_system integer DEFAULT 0 NOT NULL, + audio_analysis_started_at timestamp without time zone, + audio_analysis_status integer DEFAULT 0, + audio_analysis_uid character varying +); + + +-- +-- Name: videos_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.videos_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: videos_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.videos_id_seq OWNED BY public.videos.id; + + +-- +-- Name: zoom_meetings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.zoom_meetings ( + id bigint NOT NULL, + api_meeting_id character varying, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + broadcast_id bigint, + zoom_user_id bigint, + project_id bigint, + status integer DEFAULT 0 +); + + +-- +-- Name: zoom_meetings_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.zoom_meetings_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: zoom_meetings_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.zoom_meetings_id_seq OWNED BY public.zoom_meetings.id; + + +-- +-- Name: zoom_users; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.zoom_users ( + id bigint NOT NULL, + api_id character varying, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: zoom_users_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.zoom_users_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: zoom_users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.zoom_users_id_seq OWNED BY public.zoom_users.id; + + +-- +-- Name: account_auths id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.account_auths ALTER COLUMN id SET DEFAULT nextval('public.account_auths_id_seq'::regclass); + + +-- +-- Name: accounts id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.accounts ALTER COLUMN id SET DEFAULT nextval('public.accounts_id_seq'::regclass); + + +-- +-- Name: acquired_media_releases id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.acquired_media_releases ALTER COLUMN id SET DEFAULT nextval('public.acquired_media_releases_id_seq'::regclass); + + +-- +-- Name: action_text_rich_texts id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_text_rich_texts ALTER COLUMN id SET DEFAULT nextval('public.action_text_rich_texts_id_seq'::regclass); + + +-- +-- Name: active_storage_attachments id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.active_storage_attachments ALTER COLUMN id SET DEFAULT nextval('public.active_storage_attachments_id_seq'::regclass); + + +-- +-- Name: active_storage_blobs id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.active_storage_blobs ALTER COLUMN id SET DEFAULT nextval('public.active_storage_blobs_id_seq'::regclass); + + +-- +-- Name: appearance_releases id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.appearance_releases ALTER COLUMN id SET DEFAULT nextval('public.appearance_releases_id_seq'::regclass); + + +-- +-- Name: applicable_media id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.applicable_media ALTER COLUMN id SET DEFAULT nextval('public.applicable_media_id_seq'::regclass); + + +-- +-- Name: audio_confirmations id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.audio_confirmations ALTER COLUMN id SET DEFAULT nextval('public.audio_confirmations_id_seq'::regclass); + + +-- +-- Name: bookmarks id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.bookmarks ALTER COLUMN id SET DEFAULT nextval('public.bookmarks_id_seq'::regclass); + + +-- +-- Name: broadcast_recordings id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.broadcast_recordings ALTER COLUMN id SET DEFAULT nextval('public.broadcast_recordings_id_seq'::regclass); + + +-- +-- Name: broadcasts id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.broadcasts ALTER COLUMN id SET DEFAULT nextval('public.broadcasts_id_seq'::regclass); + + +-- +-- Name: composers id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.composers ALTER COLUMN id SET DEFAULT nextval('public.composers_id_seq'::regclass); + + +-- +-- Name: contract_templates id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.contract_templates ALTER COLUMN id SET DEFAULT nextval('public.contract_templates_id_seq'::regclass); + + +-- +-- Name: directories id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.directories ALTER COLUMN id SET DEFAULT nextval('public.directories_id_seq'::regclass); + + +-- +-- Name: downloads id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.downloads ALTER COLUMN id SET DEFAULT nextval('public.downloads_id_seq'::regclass); + + +-- +-- Name: file_infos id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.file_infos ALTER COLUMN id SET DEFAULT nextval('public.file_infos_id_seq'::regclass); + + +-- +-- Name: graphics_elements id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.graphics_elements ALTER COLUMN id SET DEFAULT nextval('public.graphics_elements_id_seq'::regclass); + + +-- +-- Name: imports id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.imports ALTER COLUMN id SET DEFAULT nextval('public.imports_id_seq'::regclass); + + +-- +-- Name: location_releases id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.location_releases ALTER COLUMN id SET DEFAULT nextval('public.location_releases_id_seq'::regclass); + + +-- +-- Name: material_releases id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.material_releases ALTER COLUMN id SET DEFAULT nextval('public.material_releases_id_seq'::regclass); + + +-- +-- Name: music_releases id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.music_releases ALTER COLUMN id SET DEFAULT nextval('public.music_releases_id_seq'::regclass); + + +-- +-- Name: notes id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notes ALTER COLUMN id SET DEFAULT nextval('public.notes_id_seq'::regclass); + + +-- +-- Name: project_memberships id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.project_memberships ALTER COLUMN id SET DEFAULT nextval('public.project_memberships_id_seq'::regclass); + + +-- +-- Name: projects id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.projects ALTER COLUMN id SET DEFAULT nextval('public.projects_id_seq'::regclass); + + +-- +-- Name: publishers id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.publishers ALTER COLUMN id SET DEFAULT nextval('public.publishers_id_seq'::regclass); + + +-- +-- Name: restrictions id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.restrictions ALTER COLUMN id SET DEFAULT nextval('public.restrictions_id_seq'::regclass); + + +-- +-- Name: settings id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.settings ALTER COLUMN id SET DEFAULT nextval('public.settings_id_seq'::regclass); + + +-- +-- Name: taggings id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.taggings ALTER COLUMN id SET DEFAULT nextval('public.taggings_id_seq'::regclass); + + +-- +-- Name: tags id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tags ALTER COLUMN id SET DEFAULT nextval('public.tags_id_seq'::regclass); + + +-- +-- Name: talent_releases id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.talent_releases ALTER COLUMN id SET DEFAULT nextval('public.talent_releases_id_seq'::regclass); + + +-- +-- Name: terms id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.terms ALTER COLUMN id SET DEFAULT nextval('public.terms_id_seq'::regclass); + + +-- +-- Name: territories id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.territories ALTER COLUMN id SET DEFAULT nextval('public.territories_id_seq'::regclass); + + +-- +-- Name: unreleased_appearances id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.unreleased_appearances ALTER COLUMN id SET DEFAULT nextval('public.unreleased_appearances_id_seq'::regclass); + + +-- +-- Name: users id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.users ALTER COLUMN id SET DEFAULT nextval('public.users_id_seq'::regclass); + + +-- +-- Name: video_release_confirmations id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.video_release_confirmations ALTER COLUMN id SET DEFAULT nextval('public.video_release_confirmations_id_seq'::regclass); + + +-- +-- Name: videos id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.videos ALTER COLUMN id SET DEFAULT nextval('public.videos_id_seq'::regclass); + + +-- +-- Name: zoom_meetings id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.zoom_meetings ALTER COLUMN id SET DEFAULT nextval('public.zoom_meetings_id_seq'::regclass); + + +-- +-- Name: zoom_users id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.zoom_users ALTER COLUMN id SET DEFAULT nextval('public.zoom_users_id_seq'::regclass); + + +-- +-- Name: account_auths account_auths_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.account_auths + ADD CONSTRAINT account_auths_pkey PRIMARY KEY (id); + + +-- +-- Name: accounts accounts_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.accounts + ADD CONSTRAINT accounts_pkey PRIMARY KEY (id); + + +-- +-- Name: acquired_media_releases acquired_media_releases_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.acquired_media_releases + ADD CONSTRAINT acquired_media_releases_pkey PRIMARY KEY (id); + + +-- +-- Name: action_text_rich_texts action_text_rich_texts_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_text_rich_texts + ADD CONSTRAINT action_text_rich_texts_pkey PRIMARY KEY (id); + + +-- +-- Name: active_storage_attachments active_storage_attachments_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.active_storage_attachments + ADD CONSTRAINT active_storage_attachments_pkey PRIMARY KEY (id); + + +-- +-- Name: active_storage_blobs active_storage_blobs_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.active_storage_blobs + ADD CONSTRAINT active_storage_blobs_pkey PRIMARY KEY (id); + + +-- +-- Name: appearance_releases appearance_releases_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.appearance_releases + ADD CONSTRAINT appearance_releases_pkey PRIMARY KEY (id); + + +-- +-- Name: applicable_media applicable_media_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.applicable_media + ADD CONSTRAINT applicable_media_pkey PRIMARY KEY (id); + + +-- +-- Name: ar_internal_metadata ar_internal_metadata_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.ar_internal_metadata + ADD CONSTRAINT ar_internal_metadata_pkey PRIMARY KEY (key); + + +-- +-- Name: audio_confirmations audio_confirmations_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.audio_confirmations + ADD CONSTRAINT audio_confirmations_pkey PRIMARY KEY (id); + + +-- +-- Name: bookmarks bookmarks_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.bookmarks + ADD CONSTRAINT bookmarks_pkey PRIMARY KEY (id); + + +-- +-- Name: broadcast_recordings broadcast_recordings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.broadcast_recordings + ADD CONSTRAINT broadcast_recordings_pkey PRIMARY KEY (id); + + +-- +-- Name: broadcasts broadcasts_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.broadcasts + ADD CONSTRAINT broadcasts_pkey PRIMARY KEY (id); + + +-- +-- Name: composers composers_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.composers + ADD CONSTRAINT composers_pkey PRIMARY KEY (id); + + +-- +-- Name: contract_templates contract_templates_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.contract_templates + ADD CONSTRAINT contract_templates_pkey PRIMARY KEY (id); + + +-- +-- Name: data_migrations data_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.data_migrations + ADD CONSTRAINT data_migrations_pkey PRIMARY KEY (version); + + +-- +-- Name: directories directories_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.directories + ADD CONSTRAINT directories_pkey PRIMARY KEY (id); + + +-- +-- Name: downloads downloads_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.downloads + ADD CONSTRAINT downloads_pkey PRIMARY KEY (id); + + +-- +-- Name: file_infos file_infos_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.file_infos + ADD CONSTRAINT file_infos_pkey PRIMARY KEY (id); + + +-- +-- Name: graphics_elements graphics_elements_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.graphics_elements + ADD CONSTRAINT graphics_elements_pkey PRIMARY KEY (id); + + +-- +-- Name: imports imports_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.imports + ADD CONSTRAINT imports_pkey PRIMARY KEY (id); + + +-- +-- Name: location_releases location_releases_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.location_releases + ADD CONSTRAINT location_releases_pkey PRIMARY KEY (id); + + +-- +-- Name: material_releases material_releases_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.material_releases + ADD CONSTRAINT material_releases_pkey PRIMARY KEY (id); + + +-- +-- Name: music_releases music_releases_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.music_releases + ADD CONSTRAINT music_releases_pkey PRIMARY KEY (id); + + +-- +-- Name: notes notes_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notes + ADD CONSTRAINT notes_pkey PRIMARY KEY (id); + + +-- +-- Name: project_memberships project_memberships_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.project_memberships + ADD CONSTRAINT project_memberships_pkey PRIMARY KEY (id); + + +-- +-- Name: projects projects_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.projects + ADD CONSTRAINT projects_pkey PRIMARY KEY (id); + + +-- +-- Name: publishers publishers_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.publishers + ADD CONSTRAINT publishers_pkey PRIMARY KEY (id); + + +-- +-- Name: restrictions restrictions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.restrictions + ADD CONSTRAINT restrictions_pkey PRIMARY KEY (id); + + +-- +-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schema_migrations + ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version); + + +-- +-- Name: settings settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.settings + ADD CONSTRAINT settings_pkey PRIMARY KEY (id); + + +-- +-- Name: taggings taggings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.taggings + ADD CONSTRAINT taggings_pkey PRIMARY KEY (id); + + +-- +-- Name: tags tags_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tags + ADD CONSTRAINT tags_pkey PRIMARY KEY (id); + + +-- +-- Name: talent_releases talent_releases_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.talent_releases + ADD CONSTRAINT talent_releases_pkey PRIMARY KEY (id); + + +-- +-- Name: terms terms_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.terms + ADD CONSTRAINT terms_pkey PRIMARY KEY (id); + + +-- +-- Name: territories territories_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.territories + ADD CONSTRAINT territories_pkey PRIMARY KEY (id); + + +-- +-- Name: unreleased_appearances unreleased_appearances_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.unreleased_appearances + ADD CONSTRAINT unreleased_appearances_pkey PRIMARY KEY (id); + + +-- +-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.users + ADD CONSTRAINT users_pkey PRIMARY KEY (id); + + +-- +-- Name: video_release_confirmations video_release_confirmations_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.video_release_confirmations + ADD CONSTRAINT video_release_confirmations_pkey PRIMARY KEY (id); + + +-- +-- Name: videos videos_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.videos + ADD CONSTRAINT videos_pkey PRIMARY KEY (id); + + +-- +-- Name: zoom_meetings zoom_meetings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.zoom_meetings + ADD CONSTRAINT zoom_meetings_pkey PRIMARY KEY (id); + + +-- +-- Name: zoom_users zoom_users_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.zoom_users + ADD CONSTRAINT zoom_users_pkey PRIMARY KEY (id); + + +-- +-- Name: index_accounts_on_slug; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_accounts_on_slug ON public.accounts USING btree (slug); + + +-- +-- Name: index_acquired_media_releases_on_applicable_medium_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_acquired_media_releases_on_applicable_medium_id ON public.acquired_media_releases USING btree (applicable_medium_id); + + +-- +-- Name: index_acquired_media_releases_on_contract_template_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_acquired_media_releases_on_contract_template_id ON public.acquired_media_releases USING btree (contract_template_id); + + +-- +-- Name: index_acquired_media_releases_on_project_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_acquired_media_releases_on_project_id ON public.acquired_media_releases USING btree (project_id); + + +-- +-- Name: index_acquired_media_releases_on_restriction_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_acquired_media_releases_on_restriction_id ON public.acquired_media_releases USING btree (restriction_id); + + +-- +-- Name: index_acquired_media_releases_on_term_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_acquired_media_releases_on_term_id ON public.acquired_media_releases USING btree (term_id); + + +-- +-- Name: index_acquired_media_releases_on_territory_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_acquired_media_releases_on_territory_id ON public.acquired_media_releases USING btree (territory_id); + + +-- +-- Name: index_action_text_rich_texts_uniqueness; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_action_text_rich_texts_uniqueness ON public.action_text_rich_texts USING btree (record_type, record_id, name); + + +-- +-- Name: index_active_storage_attachments_on_blob_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_active_storage_attachments_on_blob_id ON public.active_storage_attachments USING btree (blob_id); + + +-- +-- Name: index_active_storage_attachments_uniqueness; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_active_storage_attachments_uniqueness ON public.active_storage_attachments USING btree (record_type, record_id, name, blob_id); + + +-- +-- Name: index_active_storage_blobs_on_key; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_active_storage_blobs_on_key ON public.active_storage_blobs USING btree (key); + + +-- +-- Name: index_appearance_releases_on_applicable_medium_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_appearance_releases_on_applicable_medium_id ON public.appearance_releases USING btree (applicable_medium_id); + + +-- +-- Name: index_appearance_releases_on_contract_template_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_appearance_releases_on_contract_template_id ON public.appearance_releases USING btree (contract_template_id); + + +-- +-- Name: index_appearance_releases_on_project_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_appearance_releases_on_project_id ON public.appearance_releases USING btree (project_id); + + +-- +-- Name: index_appearance_releases_on_restriction_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_appearance_releases_on_restriction_id ON public.appearance_releases USING btree (restriction_id); + + +-- +-- Name: index_appearance_releases_on_term_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_appearance_releases_on_term_id ON public.appearance_releases USING btree (term_id); + + +-- +-- Name: index_appearance_releases_on_territory_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_appearance_releases_on_territory_id ON public.appearance_releases USING btree (territory_id); + + +-- +-- Name: index_audio_confirmations_on_video_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_audio_confirmations_on_video_id ON public.audio_confirmations USING btree (video_id); + + +-- +-- Name: index_bookmarks_on_video_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_bookmarks_on_video_id ON public.bookmarks USING btree (video_id); + + +-- +-- Name: index_broadcast_recordings_on_asset_uid; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_broadcast_recordings_on_asset_uid ON public.broadcast_recordings USING btree (asset_uid); + + +-- +-- Name: index_broadcast_recordings_on_broadcast_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_broadcast_recordings_on_broadcast_id ON public.broadcast_recordings USING btree (broadcast_id); + + +-- +-- Name: index_broadcasts_on_project_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_broadcasts_on_project_id ON public.broadcasts USING btree (project_id); + + +-- +-- Name: index_broadcasts_on_token; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_broadcasts_on_token ON public.broadcasts USING btree (token); + + +-- +-- Name: index_composers_on_music_release_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_composers_on_music_release_id ON public.composers USING btree (music_release_id); + + +-- +-- Name: index_contract_templates_on_applicable_medium_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_contract_templates_on_applicable_medium_id ON public.contract_templates USING btree (applicable_medium_id); + + +-- +-- Name: index_contract_templates_on_project_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_contract_templates_on_project_id ON public.contract_templates USING btree (project_id); + + +-- +-- Name: index_contract_templates_on_restriction_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_contract_templates_on_restriction_id ON public.contract_templates USING btree (restriction_id); + + +-- +-- Name: index_contract_templates_on_term_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_contract_templates_on_term_id ON public.contract_templates USING btree (term_id); + + +-- +-- Name: index_contract_templates_on_territory_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_contract_templates_on_territory_id ON public.contract_templates USING btree (territory_id); + + +-- +-- Name: index_directories_on_project_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_directories_on_project_id ON public.directories USING btree (project_id); + + +-- +-- Name: index_directories_on_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_directories_on_user_id ON public.directories USING btree (user_id); + + +-- +-- Name: index_downloads_on_project_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_downloads_on_project_id ON public.downloads USING btree (project_id); + + +-- +-- Name: index_file_infos_on_releasable_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_file_infos_on_releasable_id ON public.file_infos USING btree (releasable_id); + + +-- +-- Name: index_file_infos_on_releasable_id_and_type; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_file_infos_on_releasable_id_and_type ON public.file_infos USING btree (releasable_id, releasable_type); + + +-- +-- Name: index_graphics_elements_on_video_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_graphics_elements_on_video_id ON public.graphics_elements USING btree (video_id); + + +-- +-- Name: index_imports_on_project_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_imports_on_project_id ON public.imports USING btree (project_id); + + +-- +-- Name: index_imports_on_releasable_type_and_releasable_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_imports_on_releasable_type_and_releasable_id ON public.imports USING btree (releasable_type, releasable_id); + + +-- +-- Name: index_location_releases_on_applicable_medium_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_location_releases_on_applicable_medium_id ON public.location_releases USING btree (applicable_medium_id); + + +-- +-- Name: index_location_releases_on_contract_template_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_location_releases_on_contract_template_id ON public.location_releases USING btree (contract_template_id); + + +-- +-- Name: index_location_releases_on_project_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_location_releases_on_project_id ON public.location_releases USING btree (project_id); + + +-- +-- Name: index_location_releases_on_restriction_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_location_releases_on_restriction_id ON public.location_releases USING btree (restriction_id); + + +-- +-- Name: index_location_releases_on_term_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_location_releases_on_term_id ON public.location_releases USING btree (term_id); + + +-- +-- Name: index_location_releases_on_territory_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_location_releases_on_territory_id ON public.location_releases USING btree (territory_id); + + +-- +-- Name: index_material_releases_on_applicable_medium_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_material_releases_on_applicable_medium_id ON public.material_releases USING btree (applicable_medium_id); + + +-- +-- Name: index_material_releases_on_contract_template_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_material_releases_on_contract_template_id ON public.material_releases USING btree (contract_template_id); + + +-- +-- Name: index_material_releases_on_project_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_material_releases_on_project_id ON public.material_releases USING btree (project_id); + + +-- +-- Name: index_material_releases_on_restriction_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_material_releases_on_restriction_id ON public.material_releases USING btree (restriction_id); + + +-- +-- Name: index_material_releases_on_term_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_material_releases_on_term_id ON public.material_releases USING btree (term_id); + + +-- +-- Name: index_material_releases_on_territory_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_material_releases_on_territory_id ON public.material_releases USING btree (territory_id); + + +-- +-- Name: index_music_releases_on_applicable_medium_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_music_releases_on_applicable_medium_id ON public.music_releases USING btree (applicable_medium_id); + + +-- +-- Name: index_music_releases_on_contract_template_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_music_releases_on_contract_template_id ON public.music_releases USING btree (contract_template_id); + + +-- +-- Name: index_music_releases_on_project_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_music_releases_on_project_id ON public.music_releases USING btree (project_id); + + +-- +-- Name: index_music_releases_on_restriction_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_music_releases_on_restriction_id ON public.music_releases USING btree (restriction_id); + + +-- +-- Name: index_music_releases_on_term_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_music_releases_on_term_id ON public.music_releases USING btree (term_id); + + +-- +-- Name: index_music_releases_on_territory_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_music_releases_on_territory_id ON public.music_releases USING btree (territory_id); + + +-- +-- Name: index_notes_on_notable_type_and_notable_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_notes_on_notable_type_and_notable_id ON public.notes USING btree (notable_type, notable_id); + + +-- +-- Name: index_notes_on_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_notes_on_user_id ON public.notes USING btree (user_id); + + +-- +-- Name: index_project_memberships_on_project_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_project_memberships_on_project_id ON public.project_memberships USING btree (project_id); + + +-- +-- Name: index_project_memberships_on_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_project_memberships_on_user_id ON public.project_memberships USING btree (user_id); + + +-- +-- Name: index_projects_on_account_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_projects_on_account_id ON public.projects USING btree (account_id); + + +-- +-- Name: index_publishers_on_music_release_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_publishers_on_music_release_id ON public.publishers USING btree (music_release_id); + + +-- +-- Name: index_settings_on_target_type_and_target_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_settings_on_target_type_and_target_id ON public.settings USING btree (target_type, target_id); + + +-- +-- Name: index_settings_on_target_type_and_target_id_and_var; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_settings_on_target_type_and_target_id_and_var ON public.settings USING btree (target_type, target_id, var); + + +-- +-- Name: index_taggings_on_context; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_taggings_on_context ON public.taggings USING btree (context); + + +-- +-- Name: index_taggings_on_tag_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_taggings_on_tag_id ON public.taggings USING btree (tag_id); + + +-- +-- Name: index_taggings_on_taggable_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_taggings_on_taggable_id ON public.taggings USING btree (taggable_id); + + +-- +-- Name: index_taggings_on_taggable_id_and_taggable_type_and_context; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_taggings_on_taggable_id_and_taggable_type_and_context ON public.taggings USING btree (taggable_id, taggable_type, context); + + +-- +-- Name: index_taggings_on_taggable_type; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_taggings_on_taggable_type ON public.taggings USING btree (taggable_type); + + +-- +-- Name: index_taggings_on_tagger_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_taggings_on_tagger_id ON public.taggings USING btree (tagger_id); + + +-- +-- Name: index_taggings_on_tagger_id_and_tagger_type; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_taggings_on_tagger_id_and_tagger_type ON public.taggings USING btree (tagger_id, tagger_type); + + +-- +-- Name: index_tags_on_name; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_tags_on_name ON public.tags USING btree (name); + + +-- +-- Name: index_talent_releases_on_applicable_medium_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_talent_releases_on_applicable_medium_id ON public.talent_releases USING btree (applicable_medium_id); + + +-- +-- Name: index_talent_releases_on_contract_template_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_talent_releases_on_contract_template_id ON public.talent_releases USING btree (contract_template_id); + + +-- +-- Name: index_talent_releases_on_project_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_talent_releases_on_project_id ON public.talent_releases USING btree (project_id); + + +-- +-- Name: index_talent_releases_on_restriction_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_talent_releases_on_restriction_id ON public.talent_releases USING btree (restriction_id); + + +-- +-- Name: index_talent_releases_on_term_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_talent_releases_on_term_id ON public.talent_releases USING btree (term_id); + + +-- +-- Name: index_talent_releases_on_territory_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_talent_releases_on_territory_id ON public.talent_releases USING btree (territory_id); + + +-- +-- Name: index_unreleased_appearances_on_video_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_unreleased_appearances_on_video_id ON public.unreleased_appearances USING btree (video_id); + + +-- +-- Name: index_users_on_account_id_old; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_users_on_account_id_old ON public.users USING btree (account_id_old); + + +-- +-- Name: index_users_on_email; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_users_on_email ON public.users USING btree (email); + + +-- +-- Name: index_users_on_password_reset_token; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_users_on_password_reset_token ON public.users USING btree (password_reset_token); + + +-- +-- Name: index_video_release_confirmations_on_file_info_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_video_release_confirmations_on_file_info_id ON public.video_release_confirmations USING btree (file_info_id); + + +-- +-- Name: index_video_release_confirmations_on_releasable_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_video_release_confirmations_on_releasable_id ON public.video_release_confirmations USING btree (releasable_id); + + +-- +-- Name: index_video_release_confirmations_on_releasable_id_and_type; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_video_release_confirmations_on_releasable_id_and_type ON public.video_release_confirmations USING btree (releasable_id, releasable_type); + + +-- +-- Name: index_video_release_confirmations_on_video_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_video_release_confirmations_on_video_id ON public.video_release_confirmations USING btree (video_id); + + +-- +-- Name: index_videos_on_analysis_uid; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_videos_on_analysis_uid ON public.videos USING btree (analysis_uid); + + +-- +-- Name: index_videos_on_audio_analysis_uid; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_videos_on_audio_analysis_uid ON public.videos USING btree (audio_analysis_uid); + + +-- +-- Name: index_videos_on_project_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_videos_on_project_id ON public.videos USING btree (project_id); + + +-- +-- Name: index_zoom_meetings_on_broadcast_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_zoom_meetings_on_broadcast_id ON public.zoom_meetings USING btree (broadcast_id); + + +-- +-- Name: index_zoom_meetings_on_project_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_zoom_meetings_on_project_id ON public.zoom_meetings USING btree (project_id); + + +-- +-- Name: index_zoom_meetings_on_zoom_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_zoom_meetings_on_zoom_user_id ON public.zoom_meetings USING btree (zoom_user_id); + + +-- +-- Name: taggings_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX taggings_idx ON public.taggings USING btree (tag_id, taggable_id, taggable_type, context, tagger_id, tagger_type); + + +-- +-- Name: taggings_idy; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX taggings_idy ON public.taggings USING btree (taggable_id, taggable_type, tagger_id, context); + + +-- +-- Name: unreleased_appearances fk_rails_0393a349dd; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.unreleased_appearances + ADD CONSTRAINT fk_rails_0393a349dd FOREIGN KEY (video_id) REFERENCES public.videos(id); + + +-- +-- Name: material_releases fk_rails_06d19ea851; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.material_releases + ADD CONSTRAINT fk_rails_06d19ea851 FOREIGN KEY (project_id) REFERENCES public.projects(id); + + +-- +-- Name: location_releases fk_rails_09f439b8f2; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.location_releases + ADD CONSTRAINT fk_rails_09f439b8f2 FOREIGN KEY (territory_id) REFERENCES public.territories(id); + + +-- +-- Name: material_releases fk_rails_0c0f3a10fc; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.material_releases + ADD CONSTRAINT fk_rails_0c0f3a10fc FOREIGN KEY (territory_id) REFERENCES public.territories(id); + + +-- +-- Name: material_releases fk_rails_0ca4698349; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.material_releases + ADD CONSTRAINT fk_rails_0ca4698349 FOREIGN KEY (term_id) REFERENCES public.terms(id); + + +-- +-- Name: acquired_media_releases fk_rails_0eae3b5083; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.acquired_media_releases + ADD CONSTRAINT fk_rails_0eae3b5083 FOREIGN KEY (project_id) REFERENCES public.projects(id); + + +-- +-- Name: zoom_meetings fk_rails_1190f0e0fa; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.zoom_meetings + ADD CONSTRAINT fk_rails_1190f0e0fa FOREIGN KEY (project_id) REFERENCES public.projects(id); + + +-- +-- Name: bookmarks fk_rails_15735b7db8; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.bookmarks + ADD CONSTRAINT fk_rails_15735b7db8 FOREIGN KEY (video_id) REFERENCES public.videos(id); + + +-- +-- Name: acquired_media_releases fk_rails_15b450b040; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.acquired_media_releases + ADD CONSTRAINT fk_rails_15b450b040 FOREIGN KEY (restriction_id) REFERENCES public.restrictions(id); + + +-- +-- Name: acquired_media_releases fk_rails_16ec0c6f1f; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.acquired_media_releases + ADD CONSTRAINT fk_rails_16ec0c6f1f FOREIGN KEY (territory_id) REFERENCES public.territories(id); + + +-- +-- Name: material_releases fk_rails_17ae91ce4d; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.material_releases + ADD CONSTRAINT fk_rails_17ae91ce4d FOREIGN KEY (applicable_medium_id) REFERENCES public.applicable_media(id); + + +-- +-- Name: project_memberships fk_rails_18b611e244; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.project_memberships + ADD CONSTRAINT fk_rails_18b611e244 FOREIGN KEY (project_id) REFERENCES public.projects(id); + + +-- +-- Name: acquired_media_releases fk_rails_191924bd8d; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.acquired_media_releases + ADD CONSTRAINT fk_rails_191924bd8d FOREIGN KEY (applicable_medium_id) REFERENCES public.applicable_media(id); + + +-- +-- Name: composers fk_rails_1b1c389ee5; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.composers + ADD CONSTRAINT fk_rails_1b1c389ee5 FOREIGN KEY (music_release_id) REFERENCES public.music_releases(id); + + +-- +-- Name: contract_templates fk_rails_21d503cdcd; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.contract_templates + ADD CONSTRAINT fk_rails_21d503cdcd FOREIGN KEY (project_id) REFERENCES public.projects(id); + + +-- +-- Name: video_release_confirmations fk_rails_2787252ceb; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.video_release_confirmations + ADD CONSTRAINT fk_rails_2787252ceb FOREIGN KEY (file_info_id) REFERENCES public.file_infos(id); + + +-- +-- Name: music_releases fk_rails_3a2b4033ad; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.music_releases + ADD CONSTRAINT fk_rails_3a2b4033ad FOREIGN KEY (contract_template_id) REFERENCES public.contract_templates(id); + + +-- +-- Name: acquired_media_releases fk_rails_3caf7e5fea; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.acquired_media_releases + ADD CONSTRAINT fk_rails_3caf7e5fea FOREIGN KEY (term_id) REFERENCES public.terms(id); + + +-- +-- Name: music_releases fk_rails_3d26b13df8; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.music_releases + ADD CONSTRAINT fk_rails_3d26b13df8 FOREIGN KEY (project_id) REFERENCES public.projects(id); + + +-- +-- Name: location_releases fk_rails_3fe03cc59d; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.location_releases + ADD CONSTRAINT fk_rails_3fe03cc59d FOREIGN KEY (applicable_medium_id) REFERENCES public.applicable_media(id); + + +-- +-- Name: contract_templates fk_rails_4500d10f4b; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.contract_templates + ADD CONSTRAINT fk_rails_4500d10f4b FOREIGN KEY (restriction_id) REFERENCES public.restrictions(id); + + +-- +-- Name: material_releases fk_rails_4a2a7c86d1; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.material_releases + ADD CONSTRAINT fk_rails_4a2a7c86d1 FOREIGN KEY (restriction_id) REFERENCES public.restrictions(id); + + +-- +-- Name: zoom_meetings fk_rails_4d9376d137; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.zoom_meetings + ADD CONSTRAINT fk_rails_4d9376d137 FOREIGN KEY (zoom_user_id) REFERENCES public.zoom_users(id); + + +-- +-- Name: talent_releases fk_rails_4eaff8ed26; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.talent_releases + ADD CONSTRAINT fk_rails_4eaff8ed26 FOREIGN KEY (restriction_id) REFERENCES public.restrictions(id); + + +-- +-- Name: appearance_releases fk_rails_507a94fd4b; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.appearance_releases + ADD CONSTRAINT fk_rails_507a94fd4b FOREIGN KEY (restriction_id) REFERENCES public.restrictions(id); + + +-- +-- Name: talent_releases fk_rails_52acc79e0e; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.talent_releases + ADD CONSTRAINT fk_rails_52acc79e0e FOREIGN KEY (territory_id) REFERENCES public.territories(id); + + +-- +-- Name: video_release_confirmations fk_rails_53bf2ab39d; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.video_release_confirmations + ADD CONSTRAINT fk_rails_53bf2ab39d FOREIGN KEY (video_id) REFERENCES public.videos(id); + + +-- +-- Name: location_releases fk_rails_5a82e49de3; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.location_releases + ADD CONSTRAINT fk_rails_5a82e49de3 FOREIGN KEY (restriction_id) REFERENCES public.restrictions(id); + + +-- +-- Name: location_releases fk_rails_5d7b9e3937; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.location_releases + ADD CONSTRAINT fk_rails_5d7b9e3937 FOREIGN KEY (term_id) REFERENCES public.terms(id); + + +-- +-- Name: graphics_elements fk_rails_5e0d384ad7; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.graphics_elements + ADD CONSTRAINT fk_rails_5e0d384ad7 FOREIGN KEY (video_id) REFERENCES public.videos(id); + + +-- +-- Name: users fk_rails_61ac11da2b; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.users + ADD CONSTRAINT fk_rails_61ac11da2b FOREIGN KEY (account_id_old) REFERENCES public.accounts(id); + + +-- +-- Name: music_releases fk_rails_62ea419108; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.music_releases + ADD CONSTRAINT fk_rails_62ea419108 FOREIGN KEY (term_id) REFERENCES public.terms(id); + + +-- +-- Name: imports fk_rails_633cd693b9; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.imports + ADD CONSTRAINT fk_rails_633cd693b9 FOREIGN KEY (project_id) REFERENCES public.projects(id); + + +-- +-- Name: material_releases fk_rails_6b945b36b9; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.material_releases + ADD CONSTRAINT fk_rails_6b945b36b9 FOREIGN KEY (contract_template_id) REFERENCES public.contract_templates(id); + + +-- +-- Name: appearance_releases fk_rails_7a58302526; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.appearance_releases + ADD CONSTRAINT fk_rails_7a58302526 FOREIGN KEY (applicable_medium_id) REFERENCES public.applicable_media(id); + + +-- +-- Name: music_releases fk_rails_83201818cb; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.music_releases + ADD CONSTRAINT fk_rails_83201818cb FOREIGN KEY (applicable_medium_id) REFERENCES public.applicable_media(id); + + +-- +-- Name: project_memberships fk_rails_86b046ec96; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.project_memberships + ADD CONSTRAINT fk_rails_86b046ec96 FOREIGN KEY (user_id) REFERENCES public.users(id); + + +-- +-- Name: music_releases fk_rails_890d967673; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.music_releases + ADD CONSTRAINT fk_rails_890d967673 FOREIGN KEY (territory_id) REFERENCES public.territories(id); + + +-- +-- Name: zoom_meetings fk_rails_8d814ea729; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.zoom_meetings + ADD CONSTRAINT fk_rails_8d814ea729 FOREIGN KEY (broadcast_id) REFERENCES public.broadcasts(id); + + +-- +-- Name: contract_templates fk_rails_9b4d9d0e5a; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.contract_templates + ADD CONSTRAINT fk_rails_9b4d9d0e5a FOREIGN KEY (applicable_medium_id) REFERENCES public.applicable_media(id); + + +-- +-- Name: appearance_releases fk_rails_9bafd6638c; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.appearance_releases + ADD CONSTRAINT fk_rails_9bafd6638c FOREIGN KEY (contract_template_id) REFERENCES public.contract_templates(id); + + +-- +-- Name: talent_releases fk_rails_a15162b7f5; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.talent_releases + ADD CONSTRAINT fk_rails_a15162b7f5 FOREIGN KEY (project_id) REFERENCES public.projects(id); + + +-- +-- Name: projects fk_rails_b4884d7210; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.projects + ADD CONSTRAINT fk_rails_b4884d7210 FOREIGN KEY (account_id) REFERENCES public.accounts(id); + + +-- +-- Name: acquired_media_releases fk_rails_b61f0d00a0; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.acquired_media_releases + ADD CONSTRAINT fk_rails_b61f0d00a0 FOREIGN KEY (contract_template_id) REFERENCES public.contract_templates(id); + + +-- +-- Name: appearance_releases fk_rails_b6b5d35202; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.appearance_releases + ADD CONSTRAINT fk_rails_b6b5d35202 FOREIGN KEY (territory_id) REFERENCES public.territories(id); + + +-- +-- Name: talent_releases fk_rails_bcdc48f80f; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.talent_releases + ADD CONSTRAINT fk_rails_bcdc48f80f FOREIGN KEY (contract_template_id) REFERENCES public.contract_templates(id); + + +-- +-- Name: appearance_releases fk_rails_c009c323b4; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.appearance_releases + ADD CONSTRAINT fk_rails_c009c323b4 FOREIGN KEY (project_id) REFERENCES public.projects(id); + + +-- +-- Name: publishers fk_rails_c2bb50e67b; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.publishers + ADD CONSTRAINT fk_rails_c2bb50e67b FOREIGN KEY (music_release_id) REFERENCES public.music_releases(id); + + +-- +-- Name: active_storage_attachments fk_rails_c3b3935057; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.active_storage_attachments + ADD CONSTRAINT fk_rails_c3b3935057 FOREIGN KEY (blob_id) REFERENCES public.active_storage_blobs(id); + + +-- +-- Name: location_releases fk_rails_c7458d662e; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.location_releases + ADD CONSTRAINT fk_rails_c7458d662e FOREIGN KEY (contract_template_id) REFERENCES public.contract_templates(id); + + +-- +-- Name: talent_releases fk_rails_d913f5d891; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.talent_releases + ADD CONSTRAINT fk_rails_d913f5d891 FOREIGN KEY (applicable_medium_id) REFERENCES public.applicable_media(id); + + +-- +-- Name: contract_templates fk_rails_dbcb42b50c; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.contract_templates + ADD CONSTRAINT fk_rails_dbcb42b50c FOREIGN KEY (term_id) REFERENCES public.terms(id); + + +-- +-- Name: music_releases fk_rails_dff1dce9d5; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.music_releases + ADD CONSTRAINT fk_rails_dff1dce9d5 FOREIGN KEY (restriction_id) REFERENCES public.restrictions(id); + + +-- +-- Name: appearance_releases fk_rails_e0bcdcb3dc; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.appearance_releases + ADD CONSTRAINT fk_rails_e0bcdcb3dc FOREIGN KEY (term_id) REFERENCES public.terms(id); + + +-- +-- Name: location_releases fk_rails_e4fd2f9f1e; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.location_releases + ADD CONSTRAINT fk_rails_e4fd2f9f1e FOREIGN KEY (project_id) REFERENCES public.projects(id); + + +-- +-- Name: audio_confirmations fk_rails_e6b7684f9f; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.audio_confirmations + ADD CONSTRAINT fk_rails_e6b7684f9f FOREIGN KEY (video_id) REFERENCES public.videos(id); + + +-- +-- Name: videos fk_rails_f3cee18430; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.videos + ADD CONSTRAINT fk_rails_f3cee18430 FOREIGN KEY (project_id) REFERENCES public.projects(id); + + +-- +-- Name: contract_templates fk_rails_f932cce5a8; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.contract_templates + ADD CONSTRAINT fk_rails_f932cce5a8 FOREIGN KEY (territory_id) REFERENCES public.territories(id); + + +-- +-- Name: talent_releases fk_rails_fe66547f55; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.talent_releases + ADD CONSTRAINT fk_rails_fe66547f55 FOREIGN KEY (term_id) REFERENCES public.terms(id); + + +-- +-- PostgreSQL database dump complete +-- + +SET search_path TO "$user", public; + +INSERT INTO "schema_migrations" (version) VALUES +('20171206201733'), +('20171226201818'), +('20171231154902'), +('20180103145108'), +('20180103154125'), +('20180110185920'), +('20180118222351'), +('20180126234124'), +('20180207174905'), +('20180207175033'), +('20180213203308'), +('20180326025014'), +('20180418183903'), +('20180517183044'), +('20180531175610'), +('20180531202216'), +('20180609203232'), +('20180723225854'), +('20180723225855'), +('20180723225856'), +('20180723225857'), +('20180723225858'), +('20180723225859'), +('20180803012621'), +('20180803012622'), +('20180808193510'), +('20181022134900'), +('20181026003825'), +('20181030213548'), +('20181101184834'), +('20181107191123'), +('20181107191220'), +('20181113200008'), +('20181114182730'), +('20181121211703'), +('20181123034352'), +('20181123060610'), +('20181123061024'), +('20181123211217'), +('20181123211221'), +('20181129215157'), +('20181205193134'), +('20181208001821'), +('20181210183047'), +('20181214193036'), +('20181219160538'), +('20181219160621'), +('20181220151559'), +('20190111053427'), +('20190111053759'), +('20190111211317'), +('20190111211406'), +('20190125012705'), +('20190129192226'), +('20190204054833'), +('20190204060502'), +('20190204063948'), +('20190213234119'), +('20190509201711'), +('20190513175340'), +('20190513212952'), +('20190515233909'), +('20190517224239'), +('20190524220130'), +('20190529223953'), +('20190606220156'), +('20190610192026'), +('20190610231302'), +('20190613155257'), +('20190614145141'), +('20190702221718'), +('20190708165139'), +('20190710162403'), +('20190712132406'), +('20190716170929'), +('20190716183332'), +('20190717210004'), +('20190719210405'), +('20190729215334'), +('20190731171011'), +('20190731192015'), +('20190807234639'), +('20190808191051'), +('20190809133049'), +('20190809133100'), +('20190813204822'), +('20190820211808'), +('20190821231050'), +('20190822132426'), +('20190823155000'), +('20190823164829'), +('20190823214942'), +('20190905212706'), +('20190911174324'), +('20190912195438'), +('20190914142320'), +('20190916215209'), +('20190927164725'), +('20191007145140'), +('20191007155625'), +('20191011094300'), +('20191011094400'), +('20191011222149'), +('20191011230849'), +('20191014162947'), +('20191015140222'), +('20191016141651'), +('20191017163920'), +('20191022195447'), +('20191022195448'), +('20191126155350'), +('20191206181600'), +('20191223123415'), +('20191230115419'), +('20200106111835'), +('20200107192032'), +('20200127180819'), +('20200210164619'), +('20200210210223'), +('20200211172631'), +('20200213065453'), +('20200217132429'), +('20200219163255'), +('20200228144628'), +('20200228154850'), +('20200303232746'), +('20200304191424'), +('20200308181050'), +('20200309164624'), +('20200317142545'), +('20200318143154'), +('20200323195446'), +('20200326111351'), +('20200326115546'), +('20200326172542'), +('20200327122450'), +('20200330102704'), +('20200330145401'), +('20200402190734'), +('20200413124619'), +('20200424142736'), +('20200424161117'), +('20200427073429'), +('20200428091105'), +('20200507110804'), +('20200512161738'); + + diff --git a/lib/assets/.keep b/lib/assets/.keep new file mode 100644 index 0000000..e69de29 diff --git a/lib/assets/javascripts/signature_pad-extensions.js b/lib/assets/javascripts/signature_pad-extensions.js new file mode 100644 index 0000000..6a7f99d --- /dev/null +++ b/lib/assets/javascripts/signature_pad-extensions.js @@ -0,0 +1,48 @@ +/** + * Crop signature canvas to only contain the signature and no whitespace. + * + * @returns The cropped canvas + * @credit - https://github.com/szimek/signature_pad/issues/49#issuecomment-260976909 + */ +SignaturePad.prototype.crop = function() { + var canvas = this._ctx.canvas; + + // First duplicate the canvas to not alter the original + var croppedCanvas = document.createElement('canvas'), + croppedCtx = croppedCanvas.getContext('2d'); + + croppedCanvas.width = canvas.width; + croppedCanvas.height = canvas.height; + croppedCtx.drawImage(canvas, 0, 0); + + // Next do the actual cropping + var w = croppedCanvas.width, + h = croppedCanvas.height, + pix = {x:[], y:[]}, + imageData = croppedCtx.getImageData(0,0,croppedCanvas.width,croppedCanvas.height), + x, y, index; + + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + index = (y * w + x) * 4; + if (imageData.data[index+3] > 0) { + pix.x.push(x); + pix.y.push(y); + + } + } + } + pix.x.sort(function(a,b){return a-b}); + pix.y.sort(function(a,b){return a-b}); + var n = pix.x.length-1; + + w = pix.x[n] - pix.x[0]; + h = pix.y[n] - pix.y[0]; + var cut = croppedCtx.getImageData(pix.x[0], pix.y[0], w, h); + + croppedCanvas.width = w; + croppedCanvas.height = h; + croppedCtx.putImageData(cut, 0, 0); + + return croppedCanvas; +}; diff --git a/lib/bootstrap_form_extensions.rb b/lib/bootstrap_form_extensions.rb new file mode 100644 index 0000000..04feddd --- /dev/null +++ b/lib/bootstrap_form_extensions.rb @@ -0,0 +1,95 @@ +module BootstrapFormExtensions + # Extend help text to allow translations to be specified under 'helpers.help' + def get_help_text_by_i18n_key(name) + return if object.nil? + + HelpText.new(object, name).to_s + end + + def required_attribute?(obj, attribute) + RequiredAttribute.new(obj, attribute, options[:validation_context]).required? + end + + private + + class RequiredAttribute + def initialize(obj, attribute, context) + @obj = obj + @attribute = attribute + @context = context + end + + def required? + return false unless obj and attribute + + presence_validator.present? && validates_in_context? + end + + private + + attr_reader :obj, :attribute, :context + + def model_validation_context + if presence_validator + presence_validator.options[:on] + end + end + + def presence_validator + target_validators.detect { |validator| validator.class.in? [ActiveModel::Validations::PresenceValidator, ActiveRecord::Validations::PresenceValidator] } + end + + def target + (obj.class == Class) ? obj : obj.class + end + + def target_validators + if target.respond_to? :validators_on + target.validators_on(attribute) + else + [] + end + end + + def validates_in_context? + (model_validation_context.blank? || model_validation_context == context) + end + end + + class HelpText + attr_reader :object, :name + + def initialize(object, name) + @object = object + @name = name + end + + def to_s + namespaces.map { |namespace| help_text_for(namespace) }.compact.first + end + + private + + def help_text_for(namespace) + underscored_scope = "#{namespace}.#{partial_scope.underscore}" + downcased_scope = "#{namespace}.#{partial_scope.downcase}" + + help_text = I18n.t(name, scope: underscored_scope, default: '').presence + help_text ||= if text = I18n.t(name, scope: downcased_scope, default: '').presence + warn "I18n key '#{downcased_scope}.#{name}' is deprecated, use '#{underscored_scope}.#{name}' instead" + text + end + + help_text + end + + def partial_scope + # ActiveModel::Naming 3.X.X does not support .name; it is supported as of 4.X.X + @partial_scope ||= object.class.model_name.respond_to?(:name) ? object.class.model_name.name : object.class.model_name + end + + def namespaces + %w(activerecord.help helpers.help) + end + end +end diff --git a/lib/brayniac_ai.rb b/lib/brayniac_ai.rb new file mode 100644 index 0000000..ce9bc72 --- /dev/null +++ b/lib/brayniac_ai.rb @@ -0,0 +1,14 @@ +require_relative "./brayniac_ai/aws_signature_headers" +require_relative "./brayniac_ai/aws_request_signing" +require_relative "./brayniac_ai/aws_signed_connection" + +require_relative "./brayniac_ai/base" +require_relative "./brayniac_ai/audio_recognition" +require_relative "./brayniac_ai/collection" +require_relative "./brayniac_ai/document_analysis" +require_relative "./brayniac_ai/edl_parse" +require_relative "./brayniac_ai/edl_parse_result" +require_relative "./brayniac_ai/facial_recognition" +require_relative "./brayniac_ai/facial_recognition_result" +require_relative "./brayniac_ai/tag" +require_relative "./brayniac_ai/validation" diff --git a/lib/brayniac_ai/audio_recognition.rb b/lib/brayniac_ai/audio_recognition.rb new file mode 100644 index 0000000..28bfac4 --- /dev/null +++ b/lib/brayniac_ai/audio_recognition.rb @@ -0,0 +1,4 @@ +module BrayniacAI + class AudioRecognition < Base + end +end diff --git a/lib/brayniac_ai/aws_request_signing.rb b/lib/brayniac_ai/aws_request_signing.rb new file mode 100644 index 0000000..dffe694 --- /dev/null +++ b/lib/brayniac_ai/aws_request_signing.rb @@ -0,0 +1,19 @@ +module BrayniacAI + module AwsRequestSigning + def request(method, path, *arguments) + case method + when :patch, :put, :post + # These request types include a body and headers + body = arguments.first + headers = arguments.last + new_headers = AwsSignatureHeaders.new(method, self.site.merge(path), body, headers) + super(method, path, body, headers.merge(new_headers)) + else + # All other request types only include headers + headers = arguments.first + new_headers = AwsSignatureHeaders.new(method, self.site.merge(path)) + super(method, path, headers.merge(new_headers)) + end + end + end +end diff --git a/lib/brayniac_ai/aws_signature_headers.rb b/lib/brayniac_ai/aws_signature_headers.rb new file mode 100644 index 0000000..eb481a5 --- /dev/null +++ b/lib/brayniac_ai/aws_signature_headers.rb @@ -0,0 +1,47 @@ +module BrayniacAI + class AwsSignatureHeaders < Hash + def initialize(http_method, uri, body = "", headers = {}) + @http_method = http_method + @uri = uri + @body = body + @headers = headers + + set_headers + end + + private + + attr_reader :http_method, :uri, :body, :headers + + def request_params + { + http_method: http_method, + url: uri, + body: body, + headers: headers, + } + end + + def set_headers + # Set self to the signature headers + signature.headers.each { |key, value| self[key] = value } + end + + def signature + signer.sign_request(request_params) + end + + def signer + Aws::Sigv4::Signer.new(signer_params) + end + + def signer_params + { + service: "execute-api", # TODO: can this be inferred from the URI? + region: ENV["AWS_REGION"], + access_key_id: ENV["AWS_ACCESS_KEY_ID"], + secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"], + } + end + end +end diff --git a/lib/brayniac_ai/aws_signed_connection.rb b/lib/brayniac_ai/aws_signed_connection.rb new file mode 100644 index 0000000..39adc3b --- /dev/null +++ b/lib/brayniac_ai/aws_signed_connection.rb @@ -0,0 +1,5 @@ +module BrayniacAI + class AwsSignedConnection < ActiveResource::Connection + prepend AwsRequestSigning + end +end diff --git a/lib/brayniac_ai/base.rb b/lib/brayniac_ai/base.rb new file mode 100644 index 0000000..f37f092 --- /dev/null +++ b/lib/brayniac_ai/base.rb @@ -0,0 +1,16 @@ +module BrayniacAI + class Base < ActiveResource::Base + API_ENDPOINT = ENV.fetch("BRAYNIAC_AI_API_ENDPOINT") + + self.connection_class = AwsSignedConnection + self.include_format_in_path = false + self.site = API_ENDPOINT + + + def self.enable_logging + ActiveSupport::Notifications.subscribe('request.active_resource') do |name, start, finish, id, payload| + puts payload + end + end + end +end diff --git a/lib/brayniac_ai/collection.rb b/lib/brayniac_ai/collection.rb new file mode 100644 index 0000000..bc1f0ad --- /dev/null +++ b/lib/brayniac_ai/collection.rb @@ -0,0 +1,4 @@ +module BrayniacAI + class Collection < Base + end +end diff --git a/lib/brayniac_ai/document_analysis.rb b/lib/brayniac_ai/document_analysis.rb new file mode 100644 index 0000000..6137207 --- /dev/null +++ b/lib/brayniac_ai/document_analysis.rb @@ -0,0 +1,11 @@ +module BrayniacAI + class DocumentAnalysis < Base + def headshot_filename + object_name + end + + def headshot_url + "https://s3.amazonaws.com/#{bucket_name}/#{headshot_filename}" + end + end +end diff --git a/lib/brayniac_ai/edl_parse.rb b/lib/brayniac_ai/edl_parse.rb new file mode 100644 index 0000000..c69760e --- /dev/null +++ b/lib/brayniac_ai/edl_parse.rb @@ -0,0 +1,4 @@ +module BrayniacAI + class EdlParse < Base + end +end diff --git a/lib/brayniac_ai/edl_parse_result.rb b/lib/brayniac_ai/edl_parse_result.rb new file mode 100644 index 0000000..2ba6bfe --- /dev/null +++ b/lib/brayniac_ai/edl_parse_result.rb @@ -0,0 +1,5 @@ +# TODO: Use this in EdlParse +module BrayniacAI + class EdlParseResult < Base + end +end \ No newline at end of file diff --git a/lib/brayniac_ai/facial_recognition.rb b/lib/brayniac_ai/facial_recognition.rb new file mode 100644 index 0000000..6fab9a9 --- /dev/null +++ b/lib/brayniac_ai/facial_recognition.rb @@ -0,0 +1,4 @@ +module BrayniacAI + class FacialRecognition < Base + end +end diff --git a/lib/brayniac_ai/facial_recognition_result.rb b/lib/brayniac_ai/facial_recognition_result.rb new file mode 100644 index 0000000..88667e8 --- /dev/null +++ b/lib/brayniac_ai/facial_recognition_result.rb @@ -0,0 +1,5 @@ +# TODO: Use this in FacialRecognition +module BrayniacAI + class FacialRecognitionResult < Base + end +end \ No newline at end of file diff --git a/lib/brayniac_ai/tag.rb b/lib/brayniac_ai/tag.rb new file mode 100644 index 0000000..46b99fc --- /dev/null +++ b/lib/brayniac_ai/tag.rb @@ -0,0 +1,44 @@ +module BrayniacAI + class Tag < Base + def to_a + [faces_list, labels_list, texts_list].concat.flatten + end + + private + + CATEGORIES = %w( AgeRange Gender ) + BOOLEAN_CATEGORIES = %w( Beard Mustache Eyeglasses Sunglasses ) + + def faces_list + [].tap do |tags| + # Add the value of the category to the tags + tags.append convert_category_values_to_array CATEGORIES + + # If the value for the category is true, add that category name to the tags + tags.append convert_category_booleans_to_array BOOLEAN_CATEGORIES + end + end + + def labels_list + labels + end + + def texts_list + text.map { |tag| tag.text }.uniq + end + + def convert_category_values_to_array(categories) + categories.map do |category| + Array.wrap(faces.try(category)).map do |value| + value.titleize + end + end.flatten + end + + def convert_category_booleans_to_array(categories) + categories.map do |category| + category if faces.try(category) + end.compact + end + end +end diff --git a/lib/brayniac_ai/validation.rb b/lib/brayniac_ai/validation.rb new file mode 100644 index 0000000..dea495b --- /dev/null +++ b/lib/brayniac_ai/validation.rb @@ -0,0 +1,4 @@ +module BrayniacAI + class Validation < Base + end +end diff --git a/lib/dev_clockwork.rb b/lib/dev_clockwork.rb new file mode 100644 index 0000000..75eeda8 --- /dev/null +++ b/lib/dev_clockwork.rb @@ -0,0 +1,5 @@ +require "clockwork" + +module Clockwork + every(3.minutes, 'dev.poll_for_analysis_updates') { `rake dev:poll_for_analysis_updates` } +end diff --git a/lib/duplicate_remover.rb b/lib/duplicate_remover.rb new file mode 100644 index 0000000..98d5b90 --- /dev/null +++ b/lib/duplicate_remover.rb @@ -0,0 +1,36 @@ +# Updates an attribute for a given model to ensure it is unique +class DuplicateRemover + ERROR_MESSAGE_WHEN_INVALID = "has already been taken" + + def initialize(record, attribute) + @record = record + @attribute = attribute + @original_attribute_value = record.send(attribute) + @current_index = 2 + end + + def perform! + while duplicate? + record.send("#{attribute}=", new_name) + increment_index + end + + record.save! + end + + private + + attr_reader :attribute, :current_index, :original_attribute_value, :record + + def duplicate? + !record.valid? && record.errors[attribute].include?(ERROR_MESSAGE_WHEN_INVALID) + end + + def increment_index + @current_index += 1 + end + + def new_name + [original_attribute_value, "(#{current_index})"].join(" ") + end +end diff --git a/lib/tasks/.keep b/lib/tasks/.keep new file mode 100644 index 0000000..e69de29 diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake new file mode 100644 index 0000000..dbeedb6 --- /dev/null +++ b/lib/tasks/dev.rake @@ -0,0 +1,50 @@ +if Rails.env.development? || Rails.env.test? || Rails.env.review? + require "factory_bot" + + namespace :dev do + desc "Sample data for local development environment" + task :prime, [:skip_reset_db] => [:environment] do |_task, args| + include FactoryBot::Syntax::Methods + + Rake::Task["db:setup"].invoke unless args[:skip_reset_db].present? + + data = { + account_name: "Dev Account", + account_plan: "me_suite", + user_email: "dev@test.com", + user_password: "password", + } + + # Account and Admin User + dev_account = create(:account, name: data.fetch(:account_name), plan_uid: data.fetch(:account_plan)) + user = Oath::Services::SignUp.new(email: data.fetch(:user_email), password: data.fetch(:user_password), admin: true).perform + + dev_account.account_auths.create(user: user, role: :account_manager) + # Add Sample Project + dev_account.projects << SampleProject.new + dev_account.projects.first.save! + + # Enable all sections for the sample project + project = dev_account.projects.first + project.settings(:features).update!({ + acquired_media_release: true, + appearance_release: true, + location_release: true, + material_release: true, + music_release: true, + talent_release: true, + video_analysis: true, + }) + + # Add a ContractTemplate + create(:contract_template, project: project) + end + + desc "Poll videos with pending analysis for updates" + task poll_for_analysis_updates: :environment do + puts "Polling videos with pending analysis for updates..." + PendingAnalysis.poll + puts "Done." + end + end +end diff --git a/lib/tasks/scheduler.rake b/lib/tasks/scheduler.rake new file mode 100644 index 0000000..f21d5bf --- /dev/null +++ b/lib/tasks/scheduler.rake @@ -0,0 +1,8 @@ +namespace :scheduler do + desc "Expire videos which are still pending analysis after a period of time" + task expire_videos_with_pending_analysis: :environment do + puts "Updating videos with expired analysis..." + PendingAnalysis.expire(1.hour.ago) + puts "Done." + end +end diff --git a/lib/tasks/zoom.rake b/lib/tasks/zoom.rake new file mode 100644 index 0000000..3ac0868 --- /dev/null +++ b/lib/tasks/zoom.rake @@ -0,0 +1,20 @@ +require 'zoom_gateway' +namespace :zoom do + desc "Setup necessary zoom roles and users" + task :setup => :environment do + zoom = Zoom.new + + # Find or create DirectME host role + host_role = zoom.roles_list["roles"].select{ |r| r["name"] == ZoomGateway.HOST_ROLE }.first + if host_role.present? + Rails.logger.info "Role #{host_role["name"]} already present." + else + host_role = zoom.roles_create({ + name: ZoomGateway.HOST_ROLE, + description: "Directme meetings host", + privileges: %w(Role:Read) + }) + Rails.logger.info "Created role #{ZoomGateway.HOST_ROLE}." + end + end +end \ No newline at end of file diff --git a/lib/zoom_gateway.rb b/lib/zoom_gateway.rb new file mode 100644 index 0000000..00cd249 --- /dev/null +++ b/lib/zoom_gateway.rb @@ -0,0 +1,104 @@ +class ZoomGateway + class AuthenticationError < StandardError; end + class MeetingExpired < StandardError; end + class UserNotFound < StandardError; end + class TooManyHosts < StandardError; end + + class << self + def USER_TYPE_NAME + default = 'basic' + env_name = ENV['ZOOM_USER_TYPE'] || default + %w[pro basic].include?(env_name) ? env_name : default + end + + def USER_TYPE + self.USER_TYPE_NAME == 'pro' ? 2 : 1 + end + + def PRO_USERS_LIMIT + (ENV['ZOOM_PRO_USERS_LIMIT'] || 3).to_i + end + + def HOST_ROLE + "#{self.USER_TYPE_NAME}-directme-host" + end + + def enable_recordings? + ENV['ZOOM_ENABLE_RECORDINGS'] == '1' + end + + def apply_limits? + self.USER_TYPE_NAME == 'pro' + end + end + + def initialize + @client = Zoom.new + end + + def create_meeting(host_id, **kwargs) + recording_type = self.class.enable_recordings? ? 'cloud' : 'none' + meeting = @client.meeting_create({ user_id: host_id, + topic: kwargs[:topic], + type: 1, # Instant meeting + settings: { + host_video: true, + participant_video: true, + auto_recording: recording_type, + } }) + meeting["id"] + end + + def find_meeting(meeting_id) + meeting = @client.meeting_get(meeting_id: meeting_id) + HashWithIndifferentAccess.new(meeting) + rescue Zoom::APIError => e + parse_zoom_error(e) + end + + def create_host(host_email) + # Find role + host_role = @client.roles_list["roles"].try(:select) { |r| r["name"] == self.class.HOST_ROLE }.try(:first) + raise StandardError.new("Zoom host role #{self.class.HOST_ROLE} does not exist, try running rails zoom:setup.") unless host_role.present? + + if self.class.apply_limits? + hosts_count = @client.roles_members(role_id: host_role["id"]).try(:[], 'total_records').try(:to_i) + raise TooManyHosts, 'The limit of hosts has been reached' if hosts_count >= self.class.PRO_USERS_LIMIT + end + + # Create new user + host_user = @client.user_create({ + action: "custCreate", + email: host_email, + type: self.class.USER_TYPE + }) + + # Assign role to user + @client.roles_assign role_id: host_role["id"], members: [{id: host_user["id"]}] + + # Return user id + host_user["id"] + end + + def delete_host(host_id) + @client.user_delete(id: host_id) + end + + def delete_recording(meeting_id, recording_id) + @client.recording_delete(meeting_id: meeting_id, recording_id: recording_id) + end + + private + + def parse_zoom_error(error) + if error.status_code == 104 + raise AuthenticationError, error.message + elsif error.status_code == 1001 + raise UserNotFound, error.message + elsif error.status_code == 3001 + raise MeetingExpired, error.message + else + raise error + end + end +end diff --git a/log/.keep b/log/.keep new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json new file mode 100644 index 0000000..b955819 --- /dev/null +++ b/package.json @@ -0,0 +1,55 @@ +{ + "name": "easy-release", + "private": true, + "dependencies": { + "@rails/webpacker": "^4.0.7", + "actiontext": "https://github.com/rails/actiontext", + "activestorage": "^5.2.3", + "bs-custom-file-input": "^1.3.1", + "clappr": "^0.2.97", + "clappr-markers-plugin": "^0.6.1", + "clappr-playback-rate-plugin": "0.3.2", + "dropzone": "^5.5.1", + "evaporate": "^2.1.4", + "exif-js": "^2.3.0", + "jquery": "^3.4.1", + "js-sha256": "^0.9.0", + "signature_pad": "^2.3.2", + "smpte-timecode": "^1.2.3", + "spark-md5": "^3.0.0" + }, + "devDependencies": { + "babel-jest": "^24.8.0", + "jest": "^24.8.0", + "regenerator-runtime": "^0.13.2", + "webpack-dev-server": "^3.7.2" + }, + "scripts": { + "test": "jest" + }, + "jest": { + "roots": [ + "spec/javascript" + ], + "moduleDirectories": [ + "node_modules", + "app/javascript/packs" + ], + "transform": { + ".*": "babel-jest" + }, + "transformIgnorePatterns": [ + "node_modules/?!(react-icons)" + ] + }, + "env": { + "test": { + "presets": [ + [ + "env" + ], + "react" + ] + } + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..aa5998a --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,12 @@ +module.exports = { + plugins: [ + require('postcss-import'), + require('postcss-flexbugs-fixes'), + require('postcss-preset-env')({ + autoprefixer: { + flexbox: 'no-2009' + }, + stage: 3 + }) + ] +} diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..2be3af2 --- /dev/null +++ b/public/404.html @@ -0,0 +1,67 @@ + + + + The page you were looking for doesn't exist (404) + + + + + + +
    +
    +

    The page you were looking for doesn't exist.

    +

    You may have mistyped the address or the page may have moved.

    +
    +

    If you are the application owner check the logs for more information.

    +
    + + diff --git a/public/422.html b/public/422.html new file mode 100644 index 0000000..c08eac0 --- /dev/null +++ b/public/422.html @@ -0,0 +1,67 @@ + + + + The change you wanted was rejected (422) + + + + + + +
    +
    +

    The change you wanted was rejected.

    +

    Maybe you tried to change something you didn't have access to.

    +
    +

    If you are the application owner check the logs for more information.

    +
    + + diff --git a/public/500.html b/public/500.html new file mode 100644 index 0000000..78a030a --- /dev/null +++ b/public/500.html @@ -0,0 +1,66 @@ + + + + We're sorry, but something went wrong (500) + + + + + + +
    +
    +

    We're sorry, but something went wrong.

    +
    +

    If you are the application owner check the logs for more information.

    +
    + + diff --git a/public/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png new file mode 100644 index 0000000..e69de29 diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..e69de29 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..37b576a --- /dev/null +++ b/public/robots.txt @@ -0,0 +1 @@ +# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/scripts/release_tasks.sh b/scripts/release_tasks.sh new file mode 100755 index 0000000..26ebdda --- /dev/null +++ b/scripts/release_tasks.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +rails db:migrate +rails data:migrate \ No newline at end of file diff --git a/spec/channels/broadcasts_channel_spec.rb b/spec/channels/broadcasts_channel_spec.rb new file mode 100644 index 0000000..9622ed2 --- /dev/null +++ b/spec/channels/broadcasts_channel_spec.rb @@ -0,0 +1,53 @@ +require "rails_helper" + +RSpec.describe BroadcastsChannel, type: :channel do + let(:broadcast) { create(:broadcast, :with_stream, skip_create_callback: true) } + + before do + stub_connection + end + + it "successfully subscribes to public stream" do + subscribe token: broadcast.token + expect(subscription).to be_confirmed + expect(subscription).to have_stream_for(broadcast) + end + + describe ".broadcast_stream_updates" do + it "broadcasts to the channel" do + status_content = ApplicationController.render partial: "broadcasts/broadcast_status", locals: { broadcast: broadcast } + video_content = ApplicationController.render partial: "broadcasts/video", locals: { broadcast: broadcast } + + expect { + BroadcastsChannel.broadcast_stream_updates(broadcast) + }.to have_broadcasted_to(broadcast).with({ + event: "broadcast_stream_update", + status: broadcast.status, + playback_url: broadcast.stream_playback_url, + status_content: status_content, + video_content: video_content, + streamer_status: broadcast.streamer_status + }) + end + end + + describe '#stream_recording_ready' do + it 'broadcasts to the channel with the right data' do + create_list(:broadcast_recording, 1, broadcast: broadcast) + recordings = broadcast.broadcast_recordings.paginate(page: 1) + flash_message = OpenStruct.new(notice: 'Hello world', 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 + + expect { + BroadcastsChannel.stream_recording_ready(broadcast, recordings, 'Hello world') + }.to have_broadcasted_to(broadcast).with({ + event: 'stream_recording_ready', + flash_content: flash_content, + recordings_content: recordings_content, + recordings_nav_content: recordings_nav_content + }) + end + end +end diff --git a/spec/channels/projects_channel_spec.rb b/spec/channels/projects_channel_spec.rb new file mode 100644 index 0000000..9a8aea5 --- /dev/null +++ b/spec/channels/projects_channel_spec.rb @@ -0,0 +1,43 @@ +require "rails_helper" + +RSpec.describe ProjectsChannel, type: :channel do + let(:user) { create(:user) } + let(:project) { create(:project, account: user.primary_account) } + + before do + stub_connection current_user: user + end + + it "successfully subscribes to project stream" do + subscribe id: project.id + expect(subscription).to be_confirmed + expect(subscription).to have_stream_for(project) + end + + describe ".broadcast_video_analysis_update" do + it "broadcasts to the project channel" do + video = create(:video, project: project) + content = ApplicationController.render partial: "video_analyses/video_status_updated", locals: { video: video } + + expect { + ProjectsChannel.broadcast_video_analysis_update(video) + }.to have_broadcasted_to(project).with(event: "video_status_update", content: content) + end + end + + describe ".conference_recording_ready" do + it "broadcasts to the project channel" do + recording_file = Rack::Test::UploadedFile.new(Rails.root.join("spec", "fixtures", "files", "video_file.mp4"), "video/mp4") + zoom_meeting = create(:zoom_meeting, project: project, recording: recording_file) + video_url = 'http://url.to.video/video.mp4' + allow(zoom_meeting.recording).to receive(:service_url).and_return(video_url) + + expect { + ProjectsChannel.conference_recording_ready(project, zoom_meeting.recording) + }.to have_broadcasted_to(project).with { |data| + expect(data['content']).to include(video_url) + expect(data['event']).to eq('conference_recording_ready') + } + end + end +end diff --git a/spec/controllers/account_auths_controller_spec.rb b/spec/controllers/account_auths_controller_spec.rb new file mode 100644 index 0000000..406026b --- /dev/null +++ b/spec/controllers/account_auths_controller_spec.rb @@ -0,0 +1,101 @@ +require "rails_helper" + +RSpec.describe AccountAuthsController, type: :controller do + render_views + + let(:current_user) { create(:user) } + let(:other_user) { create(:user, :accountless) } + + before do + sign_in current_user + end + + describe "#index" do + it "responds successfully" do + get :index + + expect(response).to be_ok + end + + it "includes all users in the table" do + account = current_user.primary_account + associate = create(:user, :associate, primary_account: account) + project_manager = create(:user, :manager, primary_account: account) + account_manager = create(:user, :account_manager, primary_account: account) + + get :index + + expect(response.body).to have_content(associate.email) + expect(response.body).to have_content(project_manager.email) + expect(response.body).to have_content(account_manager.email) + end + end + + describe "#create" do + let(:new_account) { create(:account) } + + it "responds successfully" do + + post :create, params: { account_auth: account_auth_params(other_user, new_account) } + + expect(response).to redirect_to account_auths_path({ account_id: new_account.id }) + end + + it "creates a new record" do + expect { + post :create, params: { account_auth: account_auth_params(other_user, new_account) } + }.to change(AccountAuth, :count).by(1) + end + end + + describe "#update" do + let(:account_auth) { current_user.account_auths.first } + + it "responds with redirect" do + patch :update, params: { id: account_auth, account_auth: { role: "project_manager" } } + + expect(response).to be_redirect + expect(response).to redirect_to(account_auths_path) + expect(flash.notice).to be_present + end + + it "updates the record" do + patch :update, params: { id: account_auth, account_auth: { role: "project_manager" } } + + expect(account_auth.reload.role).to eq "project_manager" + end + + context "when record could not be updated" do + before do + allow_any_instance_of(AccountAuth).to receive(:update).and_return(false) + end + + it "responds with redirect" do + patch :update, params: { id: account_auth, account_auth: { role: "project_manager" } } + + expect(response).to be_redirect + expect(response).to redirect_to(account_auths_path) + expect(flash.alert).to be_present + end + end + end + + describe "#destroy" do + let(:account_auth) { create(:account_auth, account: current_user.primary_account, user: other_user, role: :account_manager) } + + it "responds with redirect" do + delete :destroy, params: { id: account_auth.id } + + expect(response).to be_redirect + expect(response).to redirect_to(account_auths_path) + expect(flash.alert).to be_present + end + end + + + private + + def account_auth_params(user, account) + { user_email: user.email, account_id: account.id } + end +end diff --git a/spec/controllers/account_sessions_controller_spec.rb b/spec/controllers/account_sessions_controller_spec.rb new file mode 100644 index 0000000..a8afe4f --- /dev/null +++ b/spec/controllers/account_sessions_controller_spec.rb @@ -0,0 +1,24 @@ +require "rails_helper" + +RSpec.describe AccountSessionsController, type: :controller do + let(:second_account) { create(:account) } + let(:current_user) { create(:user) } + before do + AccountAuth.create(user: current_user, account: second_account, role: :account_manager) + sign_in current_user + end + + describe "#update" do + it "responds successfully" do + post :update, params: { account_session: account_sessions_params(second_account) } + + expect(response).to redirect_to signed_in_root_path + end + end + + private + + def account_sessions_params(account) + { account_id: account.id } + end +end diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb new file mode 100644 index 0000000..f85dbc9 --- /dev/null +++ b/spec/controllers/accounts_controller_spec.rb @@ -0,0 +1,118 @@ +require "rails_helper" + +RSpec.describe AccountsController, type: :controller do + render_views + + describe "#new" do + it "returns a successful response" do + get :new + + expect(response).to be_successful + end + end + + describe "#create" do + let(:params) do + { + user: { + first_name: "John", + last_name: "Doe", + email: "test_user+1@test.com", + password: "password", + account_name: "Test Dev account", + interested_product_name: "DirectME" + } + } + end + + context "when valid account and user parameters are passed" do + it "creates account successfully" do + expect { + post :create, params: params + }.to change(Account, :count).by(1) + end + + it "creates user successfully" do + expect { + post :create, params: params + }.to change(User, :count).by(1) + + expect(User.last.first_name).to eq("John") + expect(User.last.last_name).to eq("Doe") + end + + it "signs in user successfully" do + post :create, params: params + + expect(response).to redirect_to(signed_in_root_path) + end + + it "creates guest_sign_up event" do + expect { + post :create, params: params + }.to have_enqueued_job(TrackAnalyticsJob).with(be_kind_of(User), be_kind_of(Account), :track_guest_sign_up, user_agent: "Rails Testing", user_ip: "0.0.0.0") + end + + it "enqueues hubspot form submission job" do + expect { + post :create, params: params + }.to have_enqueued_job(SubmitHubspotFormJob).with( + "test_user+1@test.com", + "Test Dev account", + i_m_interested_in: "DirectME" + ) + end + end + + context "when user is not able to sign in immediately after signup" do + before do + allow(@controller).to receive(:sign_in).and_return(nil) + end + + it "redirects to the sign in page" do + post :create, params: params + + expect(response).to redirect_to(new_session_path) + end + end + + context "when account creation fails" do + it "redirects to the new account page" do + post :create, params: { user: { email: "test_user+1@test.com", password: "password", account_name: "" }} + + expect(response).to redirect_to(new_account_path) + end + end + + context "when user is invalid" do + it "redirects to the new account page" do + post :create, params: { user: { email: "", password: "password", account_name: "Test Dev account" }} + + expect(response).to redirect_to(new_account_path) + end + end + end + + describe "#update" do + let(:current_user) { create(:user, :account_manager) } + + before do + sign_in(current_user) + end + + context "when account successfully updated" do + it "returns updated account" do + patch :update, params: { id: current_user.primary_account, account: account_params }, format: :js + + expect(current_user.primary_account.reload.logo.attached?).to be(true) + expect(current_user.primary_account.logo.filename).to eq "person_photo.png" + end + end + end + + def account_params + logo = Rack::Test::UploadedFile.new(file_fixture("person_photo.png"), "image/png") + + { logo: logo } + end +end diff --git a/spec/controllers/acquired_media_releases_controller_spec.rb b/spec/controllers/acquired_media_releases_controller_spec.rb new file mode 100644 index 0000000..4fe5f31 --- /dev/null +++ b/spec/controllers/acquired_media_releases_controller_spec.rb @@ -0,0 +1,209 @@ +require "rails_helper" + +RSpec.describe AcquiredMediaReleasesController, type: :controller do + render_views + + let(:user) { create(:user) } + let(:account) { user.primary_account } + let(:project) { create(:project, account: user.primary_account) } + + before do + sign_in user + end + + describe "#index" do + it "responds successfully" do + get :index, params: { project_id: project } + + expect(response).to be_successful + end + + it "renders content" do + release = create(:acquired_media_release, project: project, name: "My Release") + create(:note, notable: release, content: "Some notes here") + get :index, params: { project_id: project } + + expect(response.body).to have_content "My Release" + expect(response.body).to have_content "0" + expect(response.body).to have_content "Some notes here" + expect(response.body).to have_link "Import Release" + expect(response.body).to have_content "Manage" + end + + context "when there are no acquired_media releases" do + it "renders an empty message" do + get :index, params: { project_id: project } + + expect(response.body).to have_content("Acquired Media Releases will appear here") + end + end + + context "when there are many records" do + it "paginates the table" do + create_list(:acquired_media_release, 20, project: project) + + get :index, params: { project_id: project } + + expect(response.body).to have_link("2", href: project_acquired_media_releases_path(project, page: 2)) + end + end + + context "for xhr request" do + it "filters the releases by a query param" do + acquired_media_releases = [ + create(:acquired_media_release, name: "OJ Simpson Trial Audio", project: project), + create(:acquired_media_release, name: "Shark Footage", project: project), + ] + + get :index, params: { project_id: project, query: "Shark" }, xhr: true + + expect(response.body).not_to have_content("OJ") + expect(response.body).to have_content("Shark") + end + end + end + + describe "#new" do + it "responds successfully" do + get :new, params: { project_id: project } + + expect(response).to be_successful + end + end + + describe "#create" do + it "responds successfully" do + expect { + post :create, params: { project_id: project, acquired_media_release: acquired_media_release_params } + }.to change(AcquiredMediaRelease, :count).by(1) + + expect(response).to redirect_to [project, :acquired_media_releases] + expect(flash.notice).not_to be_nil + end + + it "enqueues tagging job" do + expect { + post :create, params: { project_id: project, acquired_media_release: acquired_media_release_params } + }.to have_enqueued_job(SetTagsForReleasableJob).with(AcquiredMediaRelease.last) + end + + it "creates nested file info records" do + expect { + post :create, params: { + project_id: project, + acquired_media_release: acquired_media_release_params.merge( + file_infos_attributes: { + 0 => attributes_for(:file_info) + } + ) + } + }.to change(FileInfo, :count).by(1) + expect(AcquiredMediaRelease.last.file_infos.size).to eq(1) + end + + it "logs analytics" do + expect { + post :create, params: { project_id: project, acquired_media_release: acquired_media_release_params } + }.to( + have_enqueued_job(TrackAnalyticsJob).with(user, account, :track_create_non_native_release, release_type: "AcquiredMediaRelease", user_agent: "Rails Testing", user_ip: "0.0.0.0") + ) + end + + context "when the record would be invalid" do + before :each do + allow_any_instance_of(AcquiredMediaRelease).to receive(:save).and_return(false) + end + + it "re-renders the form" do + expect { + post :create, params: { project_id: project, acquired_media_release: { name: "" } } + }.not_to change(AcquiredMediaRelease, :count) + + expect(response).to be_successful + expect(response.body).to include "Import Acquired Media Release" + end + + it "does not enqueue any jobs" do + expect { + post :create, params: { project_id: project, acquired_media_release: acquired_media_release_params } + }.not_to have_enqueued_job + end + end + end + + describe "#edit" do + let(:non_native_acquired_media_release) { create(:acquired_media_release, project: project ) } + let(:native_acquired_media_release) { create(:acquired_media_release, :native, project: project ) } + + it "responds successfully for non-native release" do + get :edit, params: { id: non_native_acquired_media_release } + + expect(response).to be_successful + end + + it "responds with error for native release" do + expect{get :edit, params: { id: native_acquired_media_release }}.to raise_exception Pundit::NotAuthorizedError + end + end + + describe "#update" do + let(:acquired_media_release) { create(:acquired_media_release, project: project) } + + it "responds successfully" do + put :update, params: { id: acquired_media_release, acquired_media_release: { name: "Updated name" } } + + expect(response).to redirect_to [project, :acquired_media_releases] + expect(flash.notice).to eq "The acquired media release has been updated" + expect(acquired_media_release.reload.name).to eq "Updated name" + end + + context "when the record would be invalid" do + before :each do + allow_any_instance_of(AcquiredMediaRelease).to receive(:update).and_return(false) + end + + it "re-renders the form" do + put :update, params: { id: acquired_media_release, acquired_media_release: acquired_media_release_params } + + expect(response).to render_template :edit + expect(flash.notice).to be_nil + end + end + end + + describe "#destroy" do + let!(:acquired_media_release) { create(:acquired_media_release, project: project) } + + it "destroys the record" do + expect { + delete :destroy, params: { id: acquired_media_release } + }.to change(AcquiredMediaRelease, :count).by(-1) + end + + it "redirects to project and sets alert" do + delete :destroy, params: { id: acquired_media_release } + + expect(response).to redirect_to [project, :acquired_media_releases] + expect(flash.alert).to eq "The acquired media release has been deleted" + end + end + + private + + def acquired_media_release_params + attributes_for(:acquired_media_release).merge(exploitable_rights_params) + end + + def exploitable_rights_params + { + applicable_medium_id: ApplicableMedium.last.id, + applicable_medium_text: "applicable_media", + territory_id: Territory.last.id, + territory_text: "territory", + term_id: Term.last.id, + term_text: "term", + restriction_id: Restriction.last.id, + restriction_text: "restrictions", + } + end +end diff --git a/spec/controllers/admin/accounts_controller_spec.rb b/spec/controllers/admin/accounts_controller_spec.rb new file mode 100644 index 0000000..61f6cd6 --- /dev/null +++ b/spec/controllers/admin/accounts_controller_spec.rb @@ -0,0 +1,172 @@ +require "rails_helper" + +RSpec.describe Admin::AccountsController, type: :controller do + render_views + + let(:current_user) { create(:user, :admin) } + + before do + sign_in(current_user) + end + + describe "#index" do + it "returns a successful response" do + get :index + + expect(response).to be_successful + end + + it "has a search field form" do + get :index + + expect(response.body).to have_button("search-button") + end + + it "paginates the results" do + create_list(:account, 20) + + get :index + expect(response.body).to have_link("2", href: admin_accounts_path(page: 2)) + end + + it "filters the account by a query param" do + create(:account, name: "First account") + create(:account, name: "Second account") + get :index, params: { query: "Second" } + + expect(response.body).not_to have_content("First account") + expect(response.body).to have_content("Second account") + end + end + + describe "#new" do + it "returns a successful response" do + get :new + + expect(response).to be_successful + end + + it "assigns 'account'" do + get :new + + expect(assigns(:account)).not_to be_nil + end + end + + describe "#create" do + it "responds with a redirect" do + post :create, params: { account: account_params } + + expect(response).to be_redirect + end + + it "sets a flash notice" do + post :create, params: { account: account_params } + + expect(flash.notice).to eq "The account was created" + end + + it "creates a new Account record" do + expect { + post :create, params: { account: account_params } + }.to change(Account, :count).by(1) + + expect(assigns(:account)).not_to be_nil + end + + context "when record cannot be saved" do + before do + allow_any_instance_of(Account).to receive(:save).and_return(false) + end + + it "re-displays the form" do + post :create, params: { account: account_params } + + expect(response).to be_successful + expect(flash.notice).to be_nil + end + + it "does not create a new Account record" do + expect { + post :create, params: { account: account_params } + }.not_to change(Account, :count) + end + end + end + + describe "#show" do + it "returns a successful response" do + get :show, params: { id: current_user.primary_account } + + expect(response).to be_successful + end + + it "has a video search field form" do + get :index + + expect(response.body).to have_button("search-button") + end + + it "paginates the video list" do + project = create(:project, account: current_user.primary_account) + create_list(:video, 20, project: project ) + + get :show, params: { id: current_user.primary_account } + + expect(response.body).to have_link("2", href: admin_account_path(current_user.primary_account, page: 2)) + end + + it "filters the videos by a query param" do + project = create(:project, account: current_user.primary_account) + create(:video, project: project, name: "First video") + create(:video, project: project, name: "Second video") + + get :show, params: { id: current_user.primary_account, query: "Second"} + + expect(response.body).not_to have_content("First video") + expect(response.body).to have_content("Second video") + end + end + + describe "#edit" do + it "assigns account" do + get :show, params: { id: current_user.primary_account } + + expect(response).to be_successful + expect(assigns(:account)).to eq current_user.primary_account + end + end + + describe "#update" do + context "when account successfully updated" do + it "returns updates account" do + patch :update, params: { id: current_user.primary_account, account: { name: "Fred", plan_uid: "deliverme" } } + + expect(current_user.primary_account.reload.name).to eq "Fred" + expect(current_user.primary_account.plan_uid).to eq "deliverme" + end + + it "redirects to accounts page" do + patch :update, params: { id: current_user.primary_account, account: account_params } + + expect(response).to redirect_to admin_accounts_path + end + end + + context "when account update fails" do + before :each do + allow_any_instance_of(Account).to receive(:update).and_return(false) + end + + it "renders edit" do + patch :update, params: { id: current_user.primary_account, account: account_params } + + expect(response).to render_template :edit + end + end + end + + def account_params + attributes_for(:account) + end +end diff --git a/spec/controllers/admin/application_controller_spec.rb b/spec/controllers/admin/application_controller_spec.rb new file mode 100644 index 0000000..59a854b --- /dev/null +++ b/spec/controllers/admin/application_controller_spec.rb @@ -0,0 +1,28 @@ +require 'rails_helper' + +RSpec.describe Admin::ApplicationController, type: :controller do + controller do + def new + authorize Account + render plain: "Hello world" + end + end + + it "allows admin users" do + admin = create(:user, :admin) + + sign_in(admin) + get :new + + expect(response).to be_successful + end + + it "redirects non-admin users" do + non_admin = create(:user, admin: false) + + sign_in(non_admin) + get :new + + expect(response).to be_redirect + end +end diff --git a/spec/controllers/admin/masquerades_controller_spec.rb b/spec/controllers/admin/masquerades_controller_spec.rb new file mode 100644 index 0000000..8edc74e --- /dev/null +++ b/spec/controllers/admin/masquerades_controller_spec.rb @@ -0,0 +1,30 @@ +require "rails_helper" + +RSpec.describe Admin::MasqueradesController, type: :controller do + render_views + + let!(:current_user) { create(:user, :admin) } + let!(:unprivileged_user) { create(:user) } + + before do + sign_in(current_user) + end + + describe "#create" do + it "redirects to the first page" do + post :create, params: { user_id: unprivileged_user.id } + + expect(response).to redirect_to signed_in_root_path + end + end + + describe "#destroy" do + it "returns to the users page" do + post :create, params: { user_id: unprivileged_user.id } + + delete :destroy + + expect(response).to redirect_to admin_users_path + end + end +end diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb new file mode 100644 index 0000000..7947f91 --- /dev/null +++ b/spec/controllers/admin/users_controller_spec.rb @@ -0,0 +1,233 @@ +require "rails_helper" + +RSpec.describe Admin::UsersController, type: :controller do + render_views + + let!(:current_user) { create(:user, :admin) } + + before do + sign_in(current_user) + end + + describe "#index" do + it "returns a successful response" do + get :index + + expect(response).to be_successful + end + + describe "for xhr request" do + before do + users = [ + create(:user, :with_name), + create(:user, :with_different_name) + ] + end + + it "shows all users if search field is empty" do + get :index, xhr: true + expect(response.body).to have_content("John") + expect(response.body).to have_content("Specimen") + end + + it "shows only users with first name, last name or email matching search query" do + get :index, params: { query: "John" }, xhr: true + expect(response.body).to have_content("John") + expect(response.body).not_to have_content("Specimen") + + get :index, params: { query: "Simpson" }, xhr: true + expect(response.body).not_to have_content("John") + expect(response.body).to have_content("Specimen") + + get :index, params: { query: "different@" }, xhr: true + expect(response.body).not_to have_content("John") + expect(response.body).to have_content("Specimen") + end + end + end + + describe "#new" do + it "returns a successful response" do + get :new + + expect(response).to be_successful + end + + it "assigns user, accounts" do + get :new + + expect(assigns(:user)).not_to be_nil + expect(assigns(:accounts)).to eq Account.all + end + end + + describe "#create" do + it "responds with a redirect" do + post :create, params: { user: user_create_params } + + expect(response).to be_redirect + expect(response).to redirect_to [:admin, :users] + end + + it "sets a flash notice" do + post :create, params: { user: user_create_params } + + expect(flash.notice).to eq "The user was created" + end + + it "creates a new User record" do + expect { + post :create, params: { user: user_create_params } + }.to change(User, :count).by(1) + + expect(assigns(:user)).to have_attributes( + email: "bob@example.com", + admin: false, + ) + end + + it "sends a welcome email" do + assert_enqueued_emails 1 do + post :create, params: { user: user_create_params } + end + end + + context "when record cannot be saved" do + before do + allow_any_instance_of(User).to receive(:valid?).and_return(false) + end + + it "re-displays the form" do + post :create, params: { user: user_create_params } + + expect(response).to be_successful + expect(flash.notice).to be_nil + end + + it "does not create a new User record" do + expect { + post :create, params: { user: user_create_params } + }.not_to change(User, :count) + end + + it "assigns accounts" do + post :create, params: { user: user_create_params } + + expect(assigns(:accounts)).to eq Account.all + end + end + end + + describe "#edit" do + let(:user) { create(:user) } + + it "returns a successful response" do + get :edit, params: { id: user } + + expect(response).to be_successful + end + + it "assigns user, accounts" do + get :edit, params: { id: user } + + expect(assigns(:user)).to eq user + expect(assigns(:accounts)).to eq Account.all + end + end + + describe "#update" do + let(:user) { create(:user) } + + it "redirects to users page" do + patch :update, params: { id: user, user: user_update_params } + + expect(response).to be_redirect + expect(response).to redirect_to admin_users_path + end + + it "sets a flash notice" do + patch :update, params: { id: user, user: user_update_params } + + expect(flash.notice).to eq "The user was updated" + end + + it "updates the user record" do + patch :update, params: { id: user, user: { email: "new-email@example.com" } } + + expect(assigns(:user)).to have_attributes( + email: "new-email@example.com", + admin: false, + ) + end + + it "updates user's password" do + patch :update, params: { id: user, user: { email: "new-email@example.com", password: "New Pass" } } + + expect(user.reload.password_digest).to eq("New Pass") + end + + context "when record cannot be saved" do + before do + allow_any_instance_of(User).to receive(:update).and_return(false) + end + + it "re-displays the form" do + patch :update, params: { id: user, user: user_update_params } + + expect(response).to be_successful + expect(flash.notice).to be_nil + end + + it "assigns accounts" do + patch :update, params: { id: user, user: user_update_params } + + expect(assigns(:accounts)).to eq Account.all + end + end + end + + describe "#destroy" do + let!(:user) { create(:user, accounts: current_user.accounts) } + let(:note_author_user) { create(:user, accounts: current_user.accounts) } + + it "deletes user" do + expect { + delete :destroy, params: { id: user } + }.to change(User, :count).by(-1) + end + + it "redirects to account edit page" do + delete :destroy, params: { id: user } + + expect(response).to redirect_to admin_users_path + end + + it "deletes user who posted notes" do + note = create(:note, user: note_author_user, email: note_author_user.email) + + expect { + delete :destroy, params: { id: note_author_user } + }.to change(User, :count).by(-1) + expect(response).to redirect_to admin_users_path + expect(Note.find(note.id).email).to eq note.email + end + end + + private + + def user_create_params + { + email: "bob@example.com", + password: "password", + account_id: current_user.primary_account.id, + admin: false, + role: "account_manager", + } + end + + def user_update_params + { + email: "bob@example.com", + } + end +end diff --git a/spec/controllers/api/acquired_media_releases_controller_spec.rb b/spec/controllers/api/acquired_media_releases_controller_spec.rb new file mode 100644 index 0000000..6cceb57 --- /dev/null +++ b/spec/controllers/api/acquired_media_releases_controller_spec.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Api::AcquiredMediaReleasesController, type: :controller do + let(:current_user) { create(:user) } + let(:project) { create(:project, name: 'first', account: current_user.primary_account) } + + describe '#index' do + it 'returns a succesful response' do + create(:acquired_media_release, name: 'ct1', project_id: project.id) + + sign_in_to_api(current_user) + get :index, params: { project_id: project.id } + + expect(response).to be_successful + old_body = response.body + + get :index, params: { project_id: project.id, updated_since: (DateTime.current + 10.days).to_date.to_s } + expect(response.body).not_to eq(old_body) + end + end + + describe '#show' do + it 'returns a succesful response' do + tested_release = create(:acquired_media_release, name: 'ct1', project_id: project.id) + + sign_in_to_api(current_user) + get :show, params: { id: tested_release.id } + + expect(response).to be_successful + end + end + + describe '#create' do + before do + template = create(:contract_template, name: 'ct1', project_id: project.id) + signature = signature_base64 + @parameters = { + contract_template_id: template.id, + acquired_media_release: { + signature: signature, + person_phone: '123', + name: 'aaa', + signed_at: '2020-01-01 16:50:26 UTC' + } + } + sign_in_to_api(current_user) + end + + it 'returns 201 - created response' do + post :create, params: @parameters + expect(response.status).to eq 201 + + expect(AcquiredMediaRelease.last.signature).to be_attached + end + + it 'saves signed on date correctly' do + post :create, params: @parameters + expect(response.status).to eq 201 + expect(AcquiredMediaRelease.last.signed_on).to eq '01/01/20' + end + + it 'runs attach contract to background job' do + expect do + post :create, params: @parameters + end.to have_enqueued_job(AttachContractToReleasableJob).with(AcquiredMediaRelease.last) + end + + it 'runs set tags for background releasable job' do + expect do + post :create, params: @parameters + end.to have_enqueued_job(SetTagsForReleasableJob).with(AcquiredMediaRelease.last) + end + + it 'fails if there is no signature' do + template = create(:contract_template, name: 'ct1', project_id: project.id) + parameters = { + contract_template_id: template.id, + acquired_media_release: { + name: 'aaa' + } + } + + sign_in_to_api(current_user) + expect do + post :create, params: parameters + end.to raise_error(ActiveRecord::RecordInvalid) + end + end + + private + + def signature_base64 + @signature_base64 ||= Base64Image.from_image(file_fixture('signature.png')).data_uri + end + + def response_body_data_attributes + JSON.parse(response.body).dig('data', 'attributes') + end +end diff --git a/spec/controllers/api/appearance_releases_controller_spec.rb b/spec/controllers/api/appearance_releases_controller_spec.rb new file mode 100644 index 0000000..af7a088 --- /dev/null +++ b/spec/controllers/api/appearance_releases_controller_spec.rb @@ -0,0 +1,219 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Api::AppearanceReleasesController, type: :controller do + let(:current_user) { create(:user) } + let(:project) { create(:project, name: 'first', account: current_user.primary_account) } + + before do + allow(BrayniacAI::Validation).to receive(:create).and_return(double(:validation, valid: true)) + end + + describe '#index' do + it 'returns a succesful response' do + create(:appearance_release, person_first_name: 'ct1', person_last_name: 'ct12', project_id: project.id) + + sign_in_to_api(current_user) + get :index, params: { project_id: project.id } + + expect(response).to be_successful + old_body = response.body + + get :index, params: { project_id: project.id, updated_since: (DateTime.current + 10.days).to_date.to_s } + expect(response.body).not_to eq(old_body) + end + end + + describe '#show' do + it 'returns a succesful response' do + tested_release = create(:appearance_release, person_first_name: 'ct1', person_last_name: 'ct12', project_id: project.id) + + sign_in_to_api(current_user) + get :show, params: { id: tested_release.id } + + expect(response).to be_successful + end + + it 'includes photos' do + project = create(:project, account_id: current_user.primary_account.id) + appearance_release = create(:appearance_release, person_first_name: 'Release', person_last_name: 'Name', project: project) + + sign_in_to_api(current_user) + get :show, params: { id: appearance_release.id } + + photo = appearance_release.person_photo + data = { + 'id' => photo.id.to_s, + 'type' => 'active_storage_attachment', + 'attributes' => { + 'content_type' => photo.content_type, + 'filename' => photo.filename.to_s, + 'url' => photo_path_for(photo), + 'thumbnail_url' => photo_variant_path_for(photo, '150x150') + } + } + expect(response_body_included_attributes).to include(data) + end + end + + describe '#create' do + before do + template = create(:contract_template, name: 'ct1', project_id: project.id) + small_photo = photo_base64 + guardian_photo = guardian_photo_base64 + signature = signature_base64 + @parameters = { + contract_template_id: template.id, + appearance_release: { + signature: signature, + person_phone: '123', + person_first_name: 'aaa', + person_last_name: 'bbb', + minor: true, + guardian_first_name: 'John', + guardian_last_name: 'Doe', + guardian_phone: '9191919191', + signed_at: '2020-01-01 16:50:26 UTC', + person_photo: { + io: small_photo, + filename: 'filenameee.jpg' + } + } + } + + @guardian_info = { + minor: true, + guardian_name: 'Guardian', + guardian_phone: '101010' + } + + @guardian_photo = { + guardian_photo: { + io: guardian_photo, + filename: 'guardianPhoto.jpeg' + } + } + sign_in_to_api(current_user) + end + + it 'returns 201 - created response' do + post :create, params: @parameters + expect(response.status).to eq 201 + + expect(AppearanceRelease.last.signature).to be_attached + end + + it 'saves signed on date correctly' do + post :create, params: @parameters + expect(response.status).to eq 201 + expect(AppearanceRelease.last.signed_on).to eq '01/01/20' + end + + + it 'runs attach contract to background job' do + expect do + post :create, params: @parameters + end.to have_enqueued_job(AttachContractToReleasableJob).with(AppearanceRelease.last) + end + + it 'runs add headshot collection uid background job' do + expect do + post :create, params: @parameters + end.to have_enqueued_job(AddHeadshotCollectionUidToProjectJob).with(project) + end + + it 'runs set tags for background releasable job' do + expect do + post :create, params: @parameters + end.to have_enqueued_job(SetTagsForReleasableJob).with(AppearanceRelease.last) + end + + it 'fails if there is no signature' do + template = create(:contract_template, name: 'ct1', project_id: project.id) + small_photo = photo_base64 + parameters = { + contract_template_id: template.id, + appearance_release: { + person_name: 'aaa' + } + } + + parameters[:appearance_release][:person_photo] = { + io: small_photo, + filename: 'filenameee.jpg' + } + + sign_in_to_api(current_user) + expect do + post :create, params: parameters + end.to raise_error(ActiveRecord::RecordInvalid) + end + + it 'returns 201 for minor without guardian photo' do + params_without_guardian_photo = @parameters + params_without_guardian_photo[:appearance_release].merge!(@guardian_info) + + post :create, params: params_without_guardian_photo + expect(response.status).to eq 201 + expect(AppearanceRelease.last.signature).to be_attached + expect(AppearanceRelease.last.guardian_photo).not_to be_attached + end + + it 'returns 201 for minor with guardian photo' do + params_with_guardian_photo = @parameters + params_with_guardian_photo[:appearance_release].merge!(@guardian_info, @guardian_photo) + + post :create, params: params_with_guardian_photo + expect(response.status).to eq 201 + + appearance_release = AppearanceRelease.last + photo = appearance_release.guardian_photo + data = { + 'id' => photo.id.to_s, + 'type' => 'active_storage_attachment', + 'attributes' => { + 'content_type' => photo.content_type, + 'filename' => photo.filename.to_s, + 'url' => photo_path_for(photo), + 'thumbnail_url' => photo_variant_path_for(photo, '150x150') + } + } + expect(response_body_included_attributes).to include(data) + expect(appearance_release.signature).to be_attached + expect(appearance_release.guardian_photo).to be_attached + end + + + end + + private + + def photo_base64 + @photo_base64 ||= Base64Image.from_image(file_fixture('person_photo.png')).data_uri + end + + def guardian_photo_base64 + @guardian_photo_base64 ||= Base64Image.from_image(file_fixture('pratt.jpg')).data_uri + end + + def signature_base64 + @signature_base64 ||= Base64Image.from_image(file_fixture('signature.png')).data_uri + end + + def photo_path_for(attachment) + Rails.application.routes.url_helpers.rails_blob_url(attachment, host: AppHost.new.domain_with_port) + end + + def photo_variant_path_for(attachment, _size) + Rails.application.routes.url_helpers.rails_representation_url(attachment.variant(resize: '150x150'), host: AppHost.new.domain_with_port) + end + + def response_body_data_attributes + JSON.parse(response.body).dig('data', 'attributes') + end + + def response_body_included_attributes + JSON.parse(response.body).dig('included') + end +end diff --git a/spec/controllers/api/broadcasts_controller_spec.rb b/spec/controllers/api/broadcasts_controller_spec.rb new file mode 100644 index 0000000..56bae48 --- /dev/null +++ b/spec/controllers/api/broadcasts_controller_spec.rb @@ -0,0 +1,156 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Api::BroadcastsController, type: :controller do + let(:current_user) { create(:user, :admin) } + + describe '#index' do + before do + allow(MuxLiveStream).to receive(:new).and_return(double(id: 'id', key: 'key', playback_id: 'playback_id')) + end + + it 'returns a list of all broadcasts ready for streaming in the project' do + project = create(:project, name: 'first', account_id: current_user.primary_account.id) + br1 = create(:broadcast, project: project, name: 'Created Bc', status: 'created') + br2 = create(:broadcast, project: project, name: 'Idle Bc', status: 'idle') + + sign_in_to_api(current_user) + get :index, params: { project_id: project.id } + + expect(response).to be_successful + + expect(response.body).to have_content('Created Bc') + expect(response.body).to have_content('Idle Bc') + expect(response.body).to have_content('created') + expect(response.body).to have_content('idle') + expect(response.body).to have_content('created_at') + end + + it 'returns empty list if there are no broadcasts' do + project = create(:project, name: 'empty', account_id: current_user.primary_account.id) + sign_in_to_api(current_user) + get :index, params: { project_id: project.id } + expect(response).to be_successful + + expect(response.body).to have_content('[]') + end + + it 'does not return broadcasts that are not ready to stream' do + project = create(:project, name: 'first', account_id: current_user.primary_account.id) + br1 = create(:broadcast, project: project, name: 'Created Bc', status: 'created') + br2 = create(:broadcast, project: project, name: 'Active Bc', status: 'active') + + sign_in_to_api(current_user) + get :index, params: { project_id: project.id } + + expect(response).to be_successful + + expect(response.body).to have_content('Created Bc') + expect(response.body).to have_content('created') + expect(response.body).not_to have_content('Active Bc') + expect(response.body).not_to have_content('active') + end + + it 'does not return broadcasts from another project' do + project = create(:project, name: 'empty', account_id: current_user.primary_account.id) + project2 = create(:project, name: 'with broadcasts', account_id: current_user.primary_account.id) + create(:broadcast, project: project2, name: 'Created Bc', status: 'created') + + sign_in_to_api(current_user) + get :index, params: { project_id: project.id } + expect(response).to be_successful + + expect(response.body).to have_content('[]') + end + + it 'does not return broadcasts from another account' do + second_account = create(:account, name: 'second') + project = create(:project, name: 'empty', account_id: current_user.primary_account.id) + project2 = create(:project, name: 'with broadcasts', account_id: second_account.id) + create(:broadcast, project: project2, name: 'Created Bc', status: 'created') + + sign_in_to_api(current_user) + get :index, params: { project_id: project.id } + expect(response).to be_successful + + expect(response.body).to have_content('[]') + end + end + + describe '#show' do + it 'returns a single broadcast' do + project = create(:project, name: 'first', account_id: current_user.primary_account.id) + br1 = create(:broadcast, :with_stream, skip_create_callback: true, project: project, name: 'Created Bc', status: 'created') + + sign_in_to_api(current_user) + get :show, params: { project_id: project.id, id: br1.id } + + expect(response).to be_successful + + expect(response.body).to have_content('Created Bc') + expect(response.body).to have_content('created') + expect(response.body).to have_content('created_at') + end + + it 'includes files relationship' do + project = create(:project, name: 'first', account_id: current_user.primary_account.id) + broadcast = create(:broadcast, :with_stream, :with_files, skip_create_callback: true, project: project) + + sign_in_to_api(current_user) + get :show, params: { project_id: project.id, id: broadcast.id } + + expect(response).to be_successful + + relationships = JSON.parse(response.body).dig('data', 'relationships') + included = JSON.parse(response.body).dig('included') + + expect(relationships.keys).to include('files') + expect(included.size).to eq 1 + expect(included.first.dig("id")).to eq broadcast.files.first.id.to_s + expect(included.first.dig("type")).to eq 'active_storage_attachment' + end + end + + describe "#update" do + it "uploads files to broadcast" do + project = create(:project, name: 'first', account_id: current_user.primary_account.id) + broadcast = create(:broadcast, :with_stream, skip_create_callback: true, project: project, name: 'Created Bc', status: 'created') + + sign_in_to_api(current_user) + patch :update, params: file_params(project, broadcast) + + relationships = JSON.parse(response.body).dig('data', 'relationships') + included = JSON.parse(response.body).dig('included') + + expect(relationships.keys).to include('files') + expect(included.size).to eq 1 + expect(included.first.dig("id")).to eq broadcast.files.first.id.to_s + expect(included.first.dig("type")).to eq 'active_storage_attachment' + end + end + + after do + # Set the callback again or it will affect other test cases where the callback is required + Broadcast.set_callback(:create, :after, :create_mux_live_stream) + end + + private + + def file_params(project, broadcast) + { + :project_id => project.id, + :id => broadcast.id, + :broadcast => { + files: [{ + io: file_base64, + filename: "person_photo.png" + }] + } + } + end + + def file_base64 + Base64Image.from_image(file_fixture('person_photo.png')).data_uri + end +end diff --git a/spec/controllers/api/contract_templates_controller_spec.rb b/spec/controllers/api/contract_templates_controller_spec.rb new file mode 100644 index 0000000..6124c3b --- /dev/null +++ b/spec/controllers/api/contract_templates_controller_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Api::ContractTemplatesController, type: :controller do + let(:current_user) { create(:user) } + let(:project) { create(:project, name: 'first', account: current_user.primary_account) } + + describe '#index' do + it 'returns a succesful response' do + create(:contract_template, name: 'ct1', project_id: project.id) + + sign_in_to_api(current_user) + get :index, params: { project_id: project.id } + + expect(response).to be_successful + old_body = response.body + + get :index, params: { project_id: project.id, updated_since: (DateTime.current + 10.days).to_date.to_s } + expect(response.body).not_to eq(old_body) + end + end + + describe '#show' do + it 'returns a succesful response' do + tested_release = create(:contract_template, name: 'ct1', project_id: project.id) + + sign_in_to_api(current_user) + get :show, params: { id: tested_release.id } + + expect(response).to be_successful + end + end + + private + + def response_body_data_attributes + JSON.parse(response.body).dig('data', 'attributes') + end + + def response_body_included_attributes + JSON.parse(response.body).dig('included') + end +end diff --git a/spec/controllers/api/notes_controller_spec.rb b/spec/controllers/api/notes_controller_spec.rb new file mode 100644 index 0000000..72a999e --- /dev/null +++ b/spec/controllers/api/notes_controller_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'rails_helper' + +releases = [ + { + type: :appearance_release, + obligatory_attribute: :person_name + }, + { + type: :talent_release, + obligatory_attribute: :person_name + }, + { + type: :location_release, + obligatory_attribute: :name + }, + { + type: :material_release, + obligatory_attribute: :name + } +] + +releases.each do |release| + RSpec.describe Api::NotesController, type: :controller do + let(:current_user) { create(:user, :admin) } + + describe '#create' do + it 'creates a note and returns a created response' do + project = create(:project, name: 'first', account_id: current_user.primary_account.id) + tested_release = create(release[:type], release[:obligatory_attribute] => 'contract template 1', :project_id => project.id) + sign_in_to_api(current_user) + post :create, params: { "#{release[:type]}_id": tested_release.id, note: { content: 'blah blah' } } + expect(response.status).to eq 201 + end + end + + describe '#index' do + it 'returns all notes for the release' do + project = create(:project, name: 'first', account_id: current_user.primary_account.id) + tested_release = create(release[:type], release[:obligatory_attribute] => 'contract template 1', :project_id => project.id) + sign_in_to_api(current_user) + get :index, params: { "#{release[:type]}_id": tested_release.id } + expect(response).to be_successful + end + end + end +end diff --git a/spec/controllers/api/profiles_controller_spec.rb b/spec/controllers/api/profiles_controller_spec.rb new file mode 100644 index 0000000..ad50aaf --- /dev/null +++ b/spec/controllers/api/profiles_controller_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Api::ProfilesController, type: :controller do + let(:current_user) { create(:user) } + + describe '#show' do + it 'responds with profile info for the current user' do + sign_in_to_api(current_user) + + get :show + + expect(response).to be_successful + expect(response_body_data).to include('id' => current_user.to_param, 'type' => 'user') + expect(response_body_data_attributes).to include('email' => current_user.email) + end + end + + private + + def response_body_data + JSON.parse(response.body).dig('data') + end + + def response_body_data_attributes + response_body_data.dig('attributes') + end +end diff --git a/spec/controllers/api/projects_controller_spec.rb b/spec/controllers/api/projects_controller_spec.rb new file mode 100644 index 0000000..210be20 --- /dev/null +++ b/spec/controllers/api/projects_controller_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Api::ProjectsController, type: :controller do + let(:current_user) { create(:user, :admin) } + + describe '#index' do + it 'returns a succesful response with and without updated_since param' do + create(:project, name: 'first', account_id: current_user.primary_account.id, updated_at: 10.days.ago) + create(:project, name: 'second', account_id: current_user.primary_account.id, updated_at: 10.days.ago) + + sign_in_to_api(current_user) + get :index + + expect(response).to be_successful + + expect(response.body).to have_content('first') + expect(response.body).to have_content('second') + end + end +end diff --git a/spec/controllers/api/releases_controller_spec.rb b/spec/controllers/api/releases_controller_spec.rb new file mode 100644 index 0000000..f72496b --- /dev/null +++ b/spec/controllers/api/releases_controller_spec.rb @@ -0,0 +1,204 @@ +# frozen_string_literal: true + +require 'rails_helper' + +releases = [ + { + type: :location_release, + obligatory_attribute: :name + }, + { + type: :material_release, + obligatory_attribute: :name + } +] + +releases.each do |release| + controller = "Api::#{release[:type].to_s.pluralize.camelize}Controller".constantize + + RSpec.describe controller, type: :controller do + let(:current_user) { create(:user) } + let(:project) { create(:project, name: 'first', account: current_user.primary_account) } + + describe '#index' do + it 'returns a succesful response' do + r = create(release[:type], release[:obligatory_attribute] => 'contract template 1', :project_id => project.id) + + sign_in_to_api(current_user) + get :index, params: { project_id: project.id } + + expect(response).to be_successful + old_body = response.body + + get :index, params: { project_id: project.id, updated_since: (DateTime.current + 10.days).to_date.to_s } + expect(response.body).not_to eq(old_body) + end + end + + describe '#show' do + it 'returns a succesful response' do + tested_release = create(release[:type], release[:obligatory_attribute] => 'contract template 1', :project_id => project.id) + + sign_in_to_api(current_user) + get :show, params: { id: tested_release.id } + + expect(response).to be_successful + end + + it 'includes photos' do + tested_release = create("#{release[:type]}_with_photo", release[:obligatory_attribute] => 'Release Name', project: project) + + sign_in_to_api(current_user) + get :show, params: { id: tested_release.id } + + tested_release.photos.each do |photo| + data = { + 'id' => photo.id.to_s, + 'type' => 'active_storage_attachment', + 'attributes' => { + 'content_type' => photo.content_type, + 'filename' => photo.filename.to_s, + 'url' => photo_path_for(photo), + 'thumbnail_url' => photo_variant_path_for(photo, '150x150') + } + } + + expect(response_body_included_attributes).to include(data) + end + end + end + + describe '#create' do + before do + template = create(:contract_template, name: 'contract template 1', project_id: project.id) + small_photo = photo_base64 + signature = signature_base64 + @parameters = { + contract_template_id: template.id, + release[:type] => { + release[:obligatory_attribute] => 'aaa', + :signature => signature, + :person_phone => '123', + :person_first_name => 'aaa', + :person_last_name => 'aaa', + :signed_at => '2020-01-01 16:50:26 UTC', + :photos => [{ + io: small_photo, + filename: 'photo1.jpg' + }] + + } + } + + sign_in_to_api(current_user) + end + + it 'returns 201 - created response' do + post :create, params: @parameters + releasable = release[:type].to_s.classify.constantize.last + + expect(response.status).to eq 201 + + expect(releasable.signature).to be_attached + end + + it 'saves signed on date correctly' do + post :create, params: @parameters + expect(response.status).to eq 201 + + expect(release[:type].to_s.classify.constantize.last.signed_on).to eq '01/01/20' + end + + it 'runs attach contract to background job' do + expect do + post :create, params: @parameters + end.to have_enqueued_job(AttachContractToReleasableJob).with(release[:type].to_s.classify.constantize.last) + end + + it 'runs set tags for releasable background job' do + expect do + post :create, params: @parameters + end.to have_enqueued_job(SetTagsForReleasableJob).with(release[:type].to_s.classify.constantize.last) + end + + it 'fails if there is no signature' do + template = create(:contract_template, name: 'ct1', project_id: project.id) + small_photo = photo_base64 + parameters = { + contract_template_id: template.id, + release[:type] => { + release[:obligatory_attribute] => 'aaa bbb' + } + } + + if release[:type] == :appearance_release + parameters[release[:type]][:person_photo] = { + io: small_photo, + filename: 'filenameee.jpg' + } + else + parameters[release[:type]][:photos] = [{ + io: small_photo, + filename: 'photo1.jpg' + }] + end + + sign_in_to_api(current_user) + expect do + post :create, params: parameters + end.to raise_error(ActiveRecord::RecordInvalid) + end + end + + describe '#update' do + it 'returns 200 - successful response' do + tested_release = create(release[:type], release[:obligatory_attribute] => 'contract template 1', + :project_id => project.id, + :person_phone => '123', + :person_name => 'aaa bbb') + + small_photo = photo_base64 + + parameters = { + :id => tested_release.id, + release[:type] => { + photos: [{ + io: small_photo, + filename: 'photo1.jpg' + }] + } + } + + sign_in_to_api(current_user) + put :update, params: parameters + expect(response.status).to eq 200 + end + end + end + + private + + def photo_base64 + @photo_base64 ||= Base64Image.from_image(file_fixture('person_photo.png')).data_uri + end + + def signature_base64 + @signature_base64 ||= Base64Image.from_image(file_fixture('signature.png')).data_uri + end + + def photo_path_for(attachment) + Rails.application.routes.url_helpers.rails_blob_url(attachment, host: AppHost.new.domain_with_port) + end + + def photo_variant_path_for(attachment, _size) + Rails.application.routes.url_helpers.rails_representation_url(attachment.variant(resize: '150x150'), host: AppHost.new.domain_with_port) + end + + def response_body_data_attributes + JSON.parse(response.body).dig('data', 'attributes') + end + + def response_body_included_attributes + JSON.parse(response.body).dig('included') + end +end diff --git a/spec/controllers/api/sync_controller_spec.rb b/spec/controllers/api/sync_controller_spec.rb new file mode 100644 index 0000000..2955a64 --- /dev/null +++ b/spec/controllers/api/sync_controller_spec.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Api::SyncController, type: :controller do + let(:current_user) { create(:user, :admin) } + + before do + sign_in_to_api(current_user) + end + + describe '#index' do + it 'dumps all the data when parameter since not suplied' do + create_default_data + + get :index + + expect(response).to be_successful + end + + it 'does not leak data from other accounts' do + another_user = create(:user) + my_project = create(:project, account: current_user.primary_account) + their_project = create(:project, account: another_user.primary_account) + my_contract_template = create(:contract_template, project: my_project) + their_contract_template = create(:contract_template, project: their_project) + my_appearance_release = create(:appearance_release, project: my_project) + their_appearance_release = create(:appearance_release, project: their_project) + my_talent_release = create(:talent_release, project: my_project) + their_talent_release = create(:talent_release, project: their_project) + my_location_release = create(:location_release, project: my_project) + their_location_release = create(:location_release, project: their_project) + my_material_release = create(:material_release, project: my_project) + their_material_release = create(:material_release, project: their_project) + my_note = create(:note, notable: my_appearance_release, user: current_user) + their_note = create(:note, notable: their_appearance_release, user: another_user) + + get :index + + account_ids = ids_for_type('accounts') + project_ids = ids_for_type('projects') + contract_template_ids = ids_for_type('contract_templates') + appearance_release_ids = ids_for_type('appearance_releases') + talent_release_ids = ids_for_type('talent_releases') + location_release_ids = ids_for_type('location_releases') + material_release_ids = ids_for_type('material_releases') + note_ids = ids_for_type('notes') + + expect(account_ids).to include(current_user.primary_account.id.to_s) + expect(account_ids).not_to include(another_user.primary_account.id.to_s) + expect(project_ids).to include(my_project.id.to_s) + expect(project_ids).not_to include(their_project.id.to_s) + expect(contract_template_ids).to include(my_contract_template.id.to_s) + expect(contract_template_ids).not_to include(their_contract_template.id.to_s) + expect(appearance_release_ids).to include(my_appearance_release.id.to_s) + expect(appearance_release_ids).not_to include(their_appearance_release.id.to_s) + expect(location_release_ids).to include(my_location_release.id.to_s) + expect(location_release_ids).not_to include(their_location_release.id.to_s) + expect(talent_release_ids).to include(my_talent_release.id.to_s) + expect(talent_release_ids).not_to include(their_talent_release.id.to_s) + expect(material_release_ids).to include(my_material_release.id.to_s) + expect(material_release_ids).not_to include(their_material_release.id.to_s) + expect(note_ids).to include(my_note.id.to_s) + expect(note_ids).not_to include(their_note.id.to_s) + end + + it 'returns only the notes that belong to the accessible projects (the note bug)' do + create_default_data + + my_appearance_release = AppearanceRelease.last + my_note = create(:note, notable: my_appearance_release, user: current_user) + another_user = create(:user) + their_project = create(:project, account: another_user.primary_account) + their_appearance_release = create(:appearance_release, project: their_project) + their_note = create(:note, notable: their_appearance_release, user: another_user) + another_user.account_auths.create(account: current_user.primary_account, role: :account_manager) + + get :index + + note_ids = ids_for_type('notes') + expect(note_ids).to eq [my_note.id.to_s] + end + + it 'shows guardian_photo property for appearance and talent releases and not for other release types' do + create_default_data + + get :index + + appearance_releases = attributes_for_type('appearance_releases') + talent_releases = attributes_for_type('talent_releases') + location_releases = attributes_for_type('location_releases') + material_releases = attributes_for_type('material_releases') + + expect(appearance_releases.first).to include('guardian_photo') + expect(talent_releases.first).to include('guardian_photo') + expect(location_releases.first).not_to include('guardian_photo') + expect(material_releases.first).not_to include('guardian_photo') + end + + it 'guardian photo has same format as other photos' do + create_default_data_with_guardian_info + + get :index + + appearance_releases = attributes_for_type('appearance_releases') + guardian_photo = appearance_releases.first['guardian_photo'] + photo_attributes = guardian_photo['attributes'] + + expect(appearance_releases.first).to include('guardian_photo') + expect(guardian_photo).to include('id', 'type', 'attributes') + expect(photo_attributes).to include('filename', 'content_type', 'url', 'thumbnail_url') + end + end + + private + + def create_default_data + project = create(:project, name: 'first', account: current_user.primary_account) + create(:appearance_release, project: project) + create(:talent_release, project: project) + create(:location_release, project: project) + create(:material_release, project: project) + end + + def create_default_data_with_guardian_info + project = create(:project, name: 'first', account: current_user.primary_account) + create(:appearance_release, :minor_with_guardian_photo, project: project) + end + + def response_body_json + JSON.parse(response.body) + end + + def ids_for_type(name) + response_body_json.dig('data', name).flat_map { |json| json.dig('attributes', 'id') } + end + + def attributes_for_type(name) + response_body_json.dig('data', name).flat_map { |json| json['attributes'] } + end +end diff --git a/spec/controllers/api/talent_releases_controller_spec.rb b/spec/controllers/api/talent_releases_controller_spec.rb new file mode 100644 index 0000000..6612f82 --- /dev/null +++ b/spec/controllers/api/talent_releases_controller_spec.rb @@ -0,0 +1,214 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Api::TalentReleasesController, type: :controller do + let(:current_user) { create(:user) } + let(:project) { create(:project, name: 'first', account: current_user.primary_account) } + + describe '#index' do + it 'returns a succesful response' do + create(:talent_release, person_first_name: 'ct1', person_last_name: 'ct12', project_id: project.id) + + sign_in_to_api(current_user) + get :index, params: { project_id: project.id } + + expect(response).to be_successful + old_body = response.body + + get :index, params: { project_id: project.id, updated_since: (DateTime.current + 10.days).to_date.to_s } + expect(response.body).not_to eq(old_body) + end + end + + describe '#show' do + it 'returns a succesful response' do + tested_release = create(:talent_release, person_first_name: 'ct1', person_last_name: 'ct12', project_id: project.id) + + sign_in_to_api(current_user) + get :show, params: { id: tested_release.id } + + expect(response).to be_successful + end + + it 'includes photos' do + tested_release = create("talent_release_with_photo", person_name: 'Release 1', project: project) + + sign_in_to_api(current_user) + get :show, params: { id: tested_release.id } + + tested_release.photos.each do |photo| + data = { + 'id' => photo.id.to_s, + 'type' => 'active_storage_attachment', + 'attributes' => { + 'content_type' => photo.content_type, + 'filename' => photo.filename.to_s, + 'url' => photo_path_for(photo), + 'thumbnail_url' => photo_variant_path_for(photo, '150x150') + } + } + + expect(response_body_included_attributes).to include(data) + end + end + end + + describe '#create' do + before do + template = create(:contract_template, name: 'ct1', project_id: project.id) + small_photo = photo_base64 + guardian_photo = guardian_photo_base64 + signature = signature_base64 + @parameters = { + contract_template_id: template.id, + talent_release: { + signature: signature, + person_phone: '123', + person_first_name: 'aaa', + person_last_name: 'bbb', + minor: true, + guardian_first_name: 'John', + guardian_last_name: 'Doe', + guardian_phone: '9191919191', + signed_at: '2020-01-01 16:50:26 UTC', + photos: [{ + io: small_photo, + filename: 'filenameee.jpg' + }] + } + } + + @guardian_info = { + minor: true, + guardian_name: 'Guardian', + guardian_phone: '101010' + } + + @guardian_photo = { + guardian_photo: { + io: guardian_photo, + filename: 'guardianPhoto.jpeg' + } + } + sign_in_to_api(current_user) + end + + it 'returns 201 - created response' do + post :create, params: @parameters + expect(response.status).to eq 201 + + expect(TalentRelease.last.signature).to be_attached + expect(TalentRelease.last.guardian_first_name).to eq('John') + expect(TalentRelease.last.guardian_last_name).to eq('Doe') + end + + it 'saves signed on date correctly' do + post :create, params: @parameters + expect(response.status).to eq 201 + + expect(TalentRelease.last.signed_on).to eq '01/01/20' + end + + it 'runs attach contract to background job' do + expect do + post :create, params: @parameters + end.to have_enqueued_job(AttachContractToReleasableJob).with(TalentRelease.last) + end + + it 'runs add headshot collection uid background job' do + expect do + post :create, params: @parameters + end.to have_enqueued_job(AddHeadshotCollectionUidToProjectJob).with(project) + end + + it 'runs set tags for background releasable job' do + expect do + post :create, params: @parameters + end.to have_enqueued_job(SetTagsForReleasableJob).with(TalentRelease.last) + end + + it 'fails if there is no signature' do + template = create(:contract_template, name: 'ct1', project_id: project.id) + small_photo = photo_base64 + parameters = { + contract_template_id: template.id, + talent_release: { + person_name: 'aaa bbb' + } + } + + parameters[:talent_release][:photos] = [{ + io: small_photo, + filename: 'filenameee.jpg' + }] + + sign_in_to_api(current_user) + expect do + post :create, params: parameters + end.to raise_error(ActiveRecord::RecordInvalid) + end + + it 'returns 201 for minor without guardian photo' do + params_without_guardian_photo = @parameters + params_without_guardian_photo[:talent_release].merge!(@guardian_info) + + post :create, params: params_without_guardian_photo + expect(response.status).to eq 201 + expect(TalentRelease.last.signature).to be_attached + expect(TalentRelease.last.guardian_photo).not_to be_attached + end + + it 'returns 201 for minor with guardian photo' do + params_with_guardian_photo = @parameters + params_with_guardian_photo[:talent_release].merge!(@guardian_info, @guardian_photo) + + post :create, params: params_with_guardian_photo + expect(response.status).to eq 201 + talent_release = TalentRelease.last + photo = talent_release.guardian_photo + data = { + 'id' => photo.id.to_s, + 'type' => 'active_storage_attachment', + 'attributes' => { + 'content_type' => photo.content_type, + 'filename' => photo.filename.to_s, + 'url' => photo_path_for(photo), + 'thumbnail_url' => photo_variant_path_for(photo, '150x150') + } + } + expect(talent_release.signature).to be_attached + expect(talent_release.guardian_photo).to be_attached + end + end + + private + + def photo_base64 + @photo_base64 ||= Base64Image.from_image(file_fixture('person_photo.png')).data_uri + end + + def guardian_photo_base64 + @guardian_photo_base64 ||= Base64Image.from_image(file_fixture('pratt.jpg')).data_uri + end + + def signature_base64 + @signature_base64 ||= Base64Image.from_image(file_fixture('signature.png')).data_uri + end + + def photo_path_for(attachment) + Rails.application.routes.url_helpers.rails_blob_url(attachment, host: AppHost.new.domain_with_port) + end + + def photo_variant_path_for(attachment, _size) + Rails.application.routes.url_helpers.rails_representation_url(attachment.variant(resize: '150x150'), host: AppHost.new.domain_with_port) + end + + def response_body_data_attributes + JSON.parse(response.body).dig('data', 'attributes') + end + + def response_body_included_attributes + JSON.parse(response.body).dig('included') + end +end diff --git a/spec/controllers/appearance_releases_controller_spec.rb b/spec/controllers/appearance_releases_controller_spec.rb new file mode 100644 index 0000000..e7bc676 --- /dev/null +++ b/spec/controllers/appearance_releases_controller_spec.rb @@ -0,0 +1,288 @@ +require "rails_helper" + +RSpec.describe AppearanceReleasesController, tye: :controller do + render_views + + let(:user) { create(:user) } + let(:account) { user.primary_account } + let(:project) { create(:project, account: user.primary_account) } + + before do + sign_in(user) + end + + describe "#index" do + it "responds successfully" do + get :index, params: { project_id: project } + + expect(response).to be_successful + end + + it "renders content" do + release = create(:appearance_release, project: project, + person_first_name: "John", person_last_name: "Doe", person_phone: "5551234567", person_email: "jane.doe@test.com") + create(:note, notable: release, content: "Some notes here") + + get :index, params: { project_id: project } + + expect(response.body).to have_content "John Doe" + expect(response.body).to have_content "P: 5551234567E: jane.doe@test.com" + expect(response.body).to have_content "Some notes here" + expect(response.body).to have_button "Import Release" + expect(response.body).to have_content "Manage" + end + + context "when there are no appearance releases" do + it "renders an empty message" do + get :index, params: { project_id: project } + + expect(response.body).to have_content("Appearance Releases will appear here") + end + end + + context "when there are many records" do + it "paginates the table" do + create_list(:appearance_release, 20, project: project) + + get :index, params: { project_id: project } + + expect(response.body).to have_link("2", href: project_appearance_releases_path(project, page: 2)) + end + end + + context "for xhr request" do + it "filters the releases by a query param" do + appearance_releases = [ + create(:appearance_release, person_first_name: "Adam", person_last_name: "Sandler", project: project), + create(:appearance_release, person_first_name: "Zoe", person_last_name: "Saldana", project: project), + ] + + get :index, params: { project_id: project, query: "Zoe" }, xhr: true + + expect(response.body).not_to have_content("Adam") + expect(response.body).to have_content("Zoe") + end + + it "filters the releases by a type_filter param" do + appearance_releases = [ + create(:appearance_release, :without_person_photo, :non_native, person_first_name: "Adam", person_last_name: "Sandler", project: project), + create(:appearance_release, :non_native, person_first_name: "Zoe", person_last_name: "Saldana", project: project), + ] + + get :index, params: { project_id: project, type_filter: 'all' }, xhr: true + + expect(response.body).to have_content("Adam") + expect(response.body).to have_content("Zoe") + + + get :index, params: { project_id: project, type_filter: 'complete' }, xhr: true + + expect(response.body).not_to have_content("Adam") + expect(response.body).to have_content("Zoe") + + get :index, params: { project_id: project, type_filter: 'incomplete' }, xhr: true + + expect(response.body).to have_content("Adam") + expect(response.body).not_to have_content("Zoe") + end + + it "filters the releases by a type_filter and query params simultaneously" do + appearance_releases = [ + create(:appearance_release, :without_person_photo, :non_native, person_first_name: "Adam", person_last_name: "Sandler Incomplete", project: project), + create(:appearance_release, :non_native, person_first_name: "Adam", person_last_name: "Sandler Complete", project: project), + create(:appearance_release, :without_person_photo, :non_native, person_first_name: "Zoe", person_last_name: "Saldana Incomplete", project: project), + create(:appearance_release, :non_native, person_first_name: "Zoe", person_last_name: "Saldana Complete", project: project), + ] + + get :index, params: { project_id: project, type_filter: 'complete', query: 'Adam' }, xhr: true + + expect(response.body).to have_content("Adam Sandler Complete") + expect(response.body).not_to have_content("Adam Sandler Incomplete") + expect(response.body).not_to have_content("Zoe Saldana Incomplete") + expect(response.body).not_to have_content("Zoe Saldana Complete") + + get :index, params: { project_id: project, type_filter: 'incomplete', query: 'Zoe' }, xhr: true + + expect(response.body).not_to have_content("Adam Sandler Complete") + expect(response.body).not_to have_content("Adam Sandler Incomplete") + expect(response.body).to have_content("Zoe Saldana Incomplete") + expect(response.body).not_to have_content("Zoe Saldana Complete") + end + end + end + + describe "#new" do + it "responds successfully" do + get :new, params: { project_id: project } + + expect(response).to be_successful + end + end + + describe "#create" do + it "responds successfully when guardian_photo is not sent" do + expect { + post :create, params: { project_id: project, appearance_release: appearance_release_params } + }.to change(AppearanceRelease, :count).by(1) + + expect(response).to be_redirect + expect(response).to redirect_to [project, :appearance_releases] + expect(flash.notice).to be_present + end + + it "responds successfully when guardian_photo is sent" do + expect { + post :create, params: { project_id: project, appearance_release: minor_appearance_release_params } + }.to change(AppearanceRelease, :count).by(1) + + expect(response).to be_redirect + expect(response).to redirect_to [project, :appearance_releases] + expect(flash.notice).to be_present + end + + it "enqueues headshot collection job" do + expect { + post :create, params: { project_id: project, appearance_release: appearance_release_params } + }.to have_enqueued_job(AddHeadshotCollectionUidToProjectJob).with(project) + end + + it "enqueues tagging job" do + expect { + post :create, params: { project_id: project, appearance_release: appearance_release_params } + }.to have_enqueued_job(SetTagsForReleasableJob).with(AppearanceRelease.last) + end + + it "logs analytics" do + expect { + post :create, params: { project_id: project, appearance_release: appearance_release_params } + }.to( + have_enqueued_job(TrackAnalyticsJob).with(user, account, :track_create_non_native_release, release_type: "AppearanceRelease", user_agent: "Rails Testing", user_ip: "0.0.0.0") + ) + end + + context "when the record would be invalid" do + before do + allow_any_instance_of(AppearanceRelease).to receive(:save).and_return(false) + end + + it "re-renders the form" do + expect { + post :create, params: { project_id: project, appearance_release: appearance_release_params } + }.not_to change(AppearanceRelease, :count) + + expect(response).to be_successful + expect(flash.notice).to be_nil + end + + it "does not enqueue any jobs" do + expect { + post :create, params: { project_id: project, appearance_release: appearance_release_params } + }.not_to have_enqueued_job + end + end + end + + describe "#edit" do + let(:non_native_appearance_release) { create(:appearance_release, project: project) } + let(:native_appearance_release) { create(:appearance_release, :native, project: project) } + + it "responds successfully for non-native release" do + get :edit, params: { project_id: project, id: non_native_appearance_release } + + expect(response).to be_successful + end + + it "responds with error for native release" do + expect { get :edit, params: { id: native_appearance_release } }.to raise_exception Pundit::NotAuthorizedError + end + end + + describe "#update" do + let(:appearance_release) { create(:appearance_release, project: project) } + + it "responds successfully" do + patch :update, params: { project_id: project, id: appearance_release, appearance_release: appearance_release_params } + + expect(response).to redirect_to [project, :appearance_releases] + expect(flash.notice).to be_present + end + + it "enqueues headshot collection job" do + expect { + patch :update, params: { project_id: project, id: appearance_release, appearance_release: appearance_release_params } + }.to have_enqueued_job(AddHeadshotCollectionUidToProjectJob).with(project) + end + + context "when the record would be invalid" do + before do + allow_any_instance_of(AppearanceRelease).to receive(:save).and_return(false) + end + + it "re-renders the form" do + patch :update, params: { project_id: project, id: appearance_release, appearance_release: appearance_release_params } + + expect(response).to be_successful + expect(flash.notice).to be_nil + end + + it "does not enqueue any jobs" do + expect { + patch :update, params: { project_id: project, id: appearance_release, appearance_release: appearance_release_params } + }.not_to have_enqueued_job(AddHeadshotCollectionUidToProjectJob) + end + end + end + + describe "#destroy" do + let!(:appearance_release) { create(:appearance_release, project: project) } + + it "responds with redirect" do + delete :destroy, params: { project_id: project, id: appearance_release } + + expect(response).to be_redirect + expect(response).to redirect_to [project, :appearance_releases] + end + + it "sets the flash" do + delete :destroy, params: { project_id: project, id: appearance_release } + + expect(flash.alert).not_to be_nil + end + + it "destroys the record" do + expect { + delete :destroy, params: { project_id: project, id: appearance_release } + }.to change(AppearanceRelease, :count).by(-1) + end + end + + private + + def appearance_release_params + attributes_for(:appearance_release, :non_native).except(:contract).merge(contract_param, exploitable_rights_params) + end + + def minor_appearance_release_params + attributes_for(:appearance_release, :non_native, :minor_with_guardian_photo).except(:contract).merge(contract_param, exploitable_rights_params) + end + + def exploitable_rights_params + { + applicable_medium_id: ApplicableMedium.last.id, + applicable_medium_text: "applicable_media", + territory_id: Territory.last.id, + territory_text: "territory", + term_id: Term.last.id, + term_text: "term", + restriction_id: Restriction.last.id, + restriction_text: "restrictions", + } + end + + def contract_param + path = Rails.root.join("spec", "fixtures", "files", "contract.pdf") + contract_file = Rack::Test::UploadedFile.new(path, "application/pdf") + + { contract: contract_file } + end +end diff --git a/spec/controllers/audio_confirmations_controller_spec.rb b/spec/controllers/audio_confirmations_controller_spec.rb new file mode 100644 index 0000000..76e5ccf --- /dev/null +++ b/spec/controllers/audio_confirmations_controller_spec.rb @@ -0,0 +1,216 @@ +require "rails_helper" + +RSpec.describe VideoAnalyses::AudioConfirmationsController, type: :controller do + let(:user) { create(:user) } + let(:project) { create(:project, account: user.primary_account) } + let(:video) { create(:video, project: project) } + + before :each do + sign_in user + end + + render_views + + describe "#new" do + let(:edl_gateway) { double(:edl_gateway) } + + before :each do + allow(EdlEventGateway).to receive(:new).and_return(edl_gateway) + allow(edl_gateway).to receive(:edl_events).and_return(build_list(:edl_event, 1)) + end + + it "responds successfully and sets audio_confirmation, edl_events_data, matched_filename" do + get :new, params: { + video_id: video.id, + audio_confirmation: { + time_elapsed: "00:01:00", + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + music_type: "music_type", + music_category: "music_category", + composer_info: "composer_info", + publisher_info: "publisher_info", + catalog: "catalog", + title: "title", + confirmation_type: "library_music", + edl_type: "audio", + }, + matched_file_name: "matched_file_name", + }, xhr: true + + expect(EdlEventGateway).to have_received(:new).with( + instance_of(AudioFilesForRequest), + "00:00:00:00", + "00:00:00:00", + channel_filter: "A", + ) + expect(response).to be_successful + expect(assigns(:audio_confirmation)).to have_attributes( + id: nil, + time_elapsed: "00:01:00", + clip_name: "clip_name", + description: "description", + duration: "duration", + source_file_name: "source_file_name", + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + music_type: "music_type", + music_category: "music_category", + composer_info: "composer_info", + publisher_info: "publisher_info", + catalog: "catalog", + title: "title", + confirmation_type: "library_music", + ) + expect(assigns(:edl_events_data)).to eq({ + edl_events: build_list(:edl_event, 1), + edl_attributes: { + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + }, + info_message: "An EDL event was found. Data is shown below", + }) + expect(assigns(:matched_file_name)).to eq "matched_file_name" + end + + context "when edl_type param is all_tracks" do + it "uses the all tracks edl" do + get :new, params: { + video_id: video.id, + audio_confirmation: { + time_elapsed: "00:01:00", + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + music_type: "music_type", + music_category: "music_category", + composer_info: "composer_info", + publisher_info: "publisher_info", + catalog: "catalog", + title: "title", + confirmation_type: "library_music", + edl_type: "all_tracks", + }, + matched_file_name: "matched_file_name", + }, xhr: true + + expect(EdlEventGateway).to have_received(:new).with( + instance_of(FilesForRequest), + "00:00:00:00", + "00:00:00:00", + channel_filter: "A", + ) + end + end + end + + describe "#create" do + it "responds successfully and creates audio_confirmation" do + expect { + post :create, params: { + video_id: video.id, + audio_confirmation: { + time_elapsed: "00:01:00", + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + music_type: "music_type", + music_category: "music_category", + composer_info: "composer_info", + publisher_info: "publisher_info", + catalog: "catalog", + title: "title", + confirmation_type: "library_music", + }, + }, xhr: true + }.to change(AudioConfirmation, :count).by(1) + + expect(response).to be_successful + end + + it "sets audio_confirmation_data, audio_confirmations_data" do + expect { + post :create, params: { + video_id: video.id, + audio_confirmation: { + time_elapsed: "00:01:00", + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + music_type: "music_type", + music_category: "music_category", + composer_info: "composer_info", + publisher_info: "publisher_info", + catalog: "catalog", + title: "title", + confirmation_type: "library_music", + }, + }, xhr: true + }.to change(AudioConfirmation, :count).by(1) + + expect(response).to be_successful + expect(assigns(:audio_confirmation_data)).to have_attributes( + source_file_name: "source_file_name", + presented_source_file_name: "(L) source_file_name", + timecode_in: "timecode_in", + should_toggle_checkmark: true, + is_valid: true, + ) + expect(assigns(:audio_confirmations_data)).to have_attributes( + audio_confirmations: [AudioConfirmation.last] + ) + end + end + + describe "#destroy" do + let!(:audio_confirmation) do + create(:audio_confirmation, + source_file_name: "source_file_name", + timecode_in: "timecode_in", + confirmation_type: "original_music" + ) + end + + it "sets audio_confirmation_data, audio_confirmations_data" do + delete :destroy, params: { id: audio_confirmation.id }, xhr: true + + expect(assigns(:audio_confirmation_data)).to have_attributes( + source_file_name: "source_file_name", + presented_source_file_name: "(O) source_file_name", + timecode_in: "timecode_in", + should_toggle_checkmark: true, + is_valid: true, + ) + expect(assigns(:audio_confirmations_data).audio_confirmations).to eq([]) + end + + it "destroys given audio_confirmation" do + expect { + delete :destroy, params: { id: audio_confirmation.id }, xhr: true + }.to change(AudioConfirmation, :count).from(1).to(0) + end + end +end diff --git a/spec/controllers/audio_reports_controller_spec.rb b/spec/controllers/audio_reports_controller_spec.rb new file mode 100644 index 0000000..a3ff2f5 --- /dev/null +++ b/spec/controllers/audio_reports_controller_spec.rb @@ -0,0 +1,56 @@ +require "rails_helper" + +RSpec.describe AudioReportsController, type: :controller do + let(:user) { create(:user) } + let(:video) { create(:video, project: create(:project, account: user.primary_account)) } + + before :each do + sign_in user + end + + describe "show" do + context "when type is big" do + it "builds the report and responds successfully" do + get :show, params: { video_id: video, format: "xlsx", type: "big" } + + expect(response).to be_successful + expect(response.body.unpack("C*").pack("U*")).to_not be_blank + expect(response.header["Content-Type"]).to eq "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + expect(response.header["Content-Disposition"]).to match "video_file-mp4_big-cue-sheet.xlsx" + end + end + + context "when type is discovery music" do + it "builds the report and responds successfully" do + get :show, params: { video_id: video, format: "xlsx", type: "discovery" } + + expect(response).to be_successful + expect(response.body.unpack("C*").pack("U*")).to_not be_blank + expect(response.header["Content-Type"]).to eq "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + expect(response.header["Content-Disposition"]).to match "discovery-cue-sheet.xlsx" + end + end + + context "when type is nat geo music" do + it "builds the report and responds successfully" do + get :show, params: { video_id: video, format: "xlsx", type: "nat_geo" } + + expect(response).to be_successful + expect(response.body.unpack("C*").pack("U*")).to_not be_blank + expect(response.header["Content-Type"]).to eq "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + expect(response.header["Content-Disposition"]).to match "music-cue-sheet.xlsx" + end + end + + context "when type is nat geo original music" do + it "builds the report and responds successfully" do + get :show, params: { video_id: video, format: "xlsx", type: "nat_geo-original" } + + expect(response).to be_successful + expect(response.body.unpack("C*").pack("U*")).to_not be_blank + expect(response.header["Content-Type"]).to eq "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + expect(response.header["Content-Disposition"]).to match "original-music-log.xlsx" + end + end + end +end diff --git a/spec/controllers/broadcasts_controller_spec.rb b/spec/controllers/broadcasts_controller_spec.rb new file mode 100644 index 0000000..c1c65f6 --- /dev/null +++ b/spec/controllers/broadcasts_controller_spec.rb @@ -0,0 +1,240 @@ +require 'rails_helper' + +RSpec.describe BroadcastsController, type: :controller do + render_views + + let(:user) { create(:user) } + let(:account) { user.primary_account } + let(:project) { create(:project, account: user.primary_account) } + + before do + sign_in user + end + + describe "#index" do + before do + allow(MuxLiveStream).to receive(:new).and_return(double(id: "id", key: "key", playback_id: "playback_id")) + end + + it "responds successfully" do + get :index, params: { project_id: project } + + expect(response).to be_successful + end + + it "renders content" do + create(:broadcast, project: project, name: "Another Broadcast") + + get :index, params: { project_id: project } + + expect(response.body).to have_content "Another Broadcast" + expect(response.body).to have_content "Created" + expect(response.body).to have_content "less than a minute ago" + expect(response.body).to have_link "Create New Live Stream" + end + + context "when there are no active broadcasts" do + it "renders an empty message" do + Broadcast.destroy_all + + get :index, params: { project_id: project } + + expect(response.body).to have_content("Live streams will appear here") + end + end + + context "when there are many records" do + it "paginates the table" do + create_list(:broadcast, 20, project: project) + + get :index, params: { project_id: project } + + expect(response.body).to have_link("2", href: project_broadcasts_path(project, page: 2)) + end + end + + context "for xhr request" do + it "filters the broadcasts by a query param" do + appearance_releases = [ + create(:broadcast, skip_create_callback: true, name: "Stream by Adam"), + create(:broadcast, skip_create_callback: true, name: "Stream by Zoe"), + ] + + get :index, params: { project_id: project, query: "Zoe" }, xhr: true + + expect(response.body).not_to have_content("Adam") + expect(response.body).to have_content("Zoe") + end + end + end + + describe "#new" do + it "responds successfully" do + get :new, params: { project_id: project } + + expect(response).to be_successful + expect(assigns(:broadcast)).to be_a_new(Broadcast) + expect(response).to render_template(:new) + end + end + + describe "#create" do + before do + allow(MuxLiveStream).to receive(:new).and_return(double(id: "id", key: "key", playback_id: "playback_id")) + end + + it "responds with a redirect" do + post :create, params: { project_id: project.id, broadcast: broadcast_params } + + expect(response).to be_redirect + expect(flash.notice).not_to be_nil + end + + it "does create a new record" do + expect { + post :create, params: { project_id: project.id, broadcast: broadcast_params } + }.to change(Broadcast, :count) + end + + it "logs an event" do + expect { + post :create, params: { project_id: project.id, broadcast: broadcast_params } + }.to have_enqueued_job(TrackAnalyticsJob).with(user, account, :track_create_live_stream, user_agent: "Rails Testing", user_ip: "0.0.0.0") + end + + context "with invalid data" do + it "does not create a new record" do + expect { + post :create, params: { project_id: project.id, broadcast: { name: "" } } + }.not_to change(Broadcast, :count) + end + end + end + + describe "#update" do + let!(:broadcast) { create(:broadcast, :with_stream, skip_create_callback: true, project: project ) } + + it "uploads files to broadcast" do + patch :update, params: { project_id: project.id, id: broadcast.id, broadcast: file_params }, xhr: true + + expect(broadcast.files.count).to eq(1) + expect(broadcast.files.first.filename).to eq("contract.pdf") + end + end + + describe "#show" do + let(:broadcast) { create(:broadcast, project: project, name: "Another Broadcast") } + + before do + allow(MuxLiveStream).to receive(:new).and_return(double(id: "id", key: "key", playback_id: "playback_id")) + end + + it "responds successfully" do + get :show, params: { project_id: project.id, id: broadcast.id } + + expect(response).to be_successful + expect(assigns(:broadcast)).to eq(broadcast) + end + + it "renders readonly share url" do + get :show, params: { project_id: project.id, id: broadcast.id } + + expect(response.body).to have_button "Copy URL" + expect(response.body).to have_xpath "//input[@readonly][@value='#{broadcast_url(broadcast.token)}']" + end + + it "displays zoom meeting button" do + get :show, params: { project_id: project.id, id: broadcast.id } + + expect(response.body).to have_link("Video Conference", href: project_broadcast_zoom_meeting_url(project, broadcast)) + end + + it "assigns required variables" do + get :show, params: { project_id: project.id, id: broadcast.id } + + expect(assigns(:conference_url)).to eq project_broadcast_zoom_meeting_url(project, broadcast) + expect(assigns(:broadcast)).to eq broadcast + end + + context "when there are no multi-view broadcasts" do + it "renders the view dropdown with just the current broadcast" do + get :show, params: { project_id: project, id: broadcast } + + expect(response.body).to have_content "Switch View" + expect(response.body).to have_selector(".dropdown-menu .dropdown-item.active", text: "Another Broadcast") + end + end + + context "when there are multi-view broadcasts" do + it "renders the view dropdown with all the broadcasts" do + other_broadcast = create(:broadcast, project: project, name: "Some Other Broadcast") + + get :show, params: { project_id: project, id: broadcast, multi_view_ids: [broadcast.id, other_broadcast.id] } + + expect(response.body).to have_content "Switch View" + expect(response.body).to have_selector(".dropdown-menu .dropdown-item.active", text: "Another Broadcast") + expect(response.body).to have_selector(".dropdown-menu a.dropdown-item", text: "Some Other Broadcast") + end + end + + context "when there are no recordings for the current broadcast" do + it "renders the view dropdown with a message" do + get :show, params: { project_id: project, id: broadcast } + + expect(response.body).to have_content "Switch View" + expect(response.body).to have_selector(".dropdown-menu .dropdown-item", text: "Recordings will appear here") + end + end + + context "when there are recordings available" do + it "renders the view dropdown with the recordings" do + recording = create(:broadcast_recording, broadcast: broadcast) + + get :show, params: { project_id: project, id: broadcast } + + expect(response.body).to have_content "Switch View" + expect(response.body).to have_selector(".dropdown-menu a.dropdown-item", text: recording.download_file_name) + end + end + end + + describe "#destroy" do + let!(:broadcast_2) { create(:broadcast, :with_stream, skip_create_callback: true, project: project, name: "Another Broadcast") } + + before do + allow_any_instance_of(MuxLiveStream).to receive(:destroy_stream).with(broadcast_2.stream_uid) + end + + it "responds with redirect" do + delete :destroy, params: { project_id: project.id, id: broadcast_2.id } + + expect(response).to be_redirect + expect(response).to redirect_to(project_broadcasts_path(project)) + expect(flash.alert).not_to be_nil + end + + it "destroys the record" do + expect { + delete :destroy, params: { project_id: project.id, id: broadcast_2.id } + }.to change(Broadcast, :count).by(-1) + end + end + + after do + # Set the callback again or it will affect other test cases where the callback is required + Broadcast.set_callback(:create, :after, :create_mux_live_stream) + end + + private + + def broadcast_params + attributes_for(:broadcast) + end + + def file_params + path = Rails.root.join("spec", "fixtures", "files", "contract.pdf") + contract_file = Rack::Test::UploadedFile.new(path, "application/pdf") + + { files: [contract_file] } + end +end diff --git a/spec/controllers/bulk_taggings_controller_spec.rb b/spec/controllers/bulk_taggings_controller_spec.rb new file mode 100644 index 0000000..c0e18f8 --- /dev/null +++ b/spec/controllers/bulk_taggings_controller_spec.rb @@ -0,0 +1,37 @@ +require "rails_helper" + +RSpec.describe BulkTaggingsController, type: :controller do + render_views + + let(:user) { create(:user) } + let(:project) { create(:project, account: user.primary_account) } + let(:acquired_media_releases) { create_list(:acquired_media_release, 5, project: project) } + + before do + sign_in user + end + + describe "#new" do + it "responds successfully" do + tagged_ids = acquired_media_releases.map(&:id).sample(3).to_s + get :new, params: { releasable_ids: tagged_ids, releasable_name: "acquired_media_releases", project_id: project.id }, xhr: true + + expect(response).to be_successful + end + end + + describe "#create" do + it "tags multiple releases" do + tagged_ids = acquired_media_releases.map(&:id).sample(3).to_s + post :create, params: acquired_media_release_params(tagged_ids), xhr: true + + expect(response.body).to have_content('Approved', count: 3) + end + end + + private + + def acquired_media_release_params(tagged_ids) + { name: "Approved", releasable_ids: tagged_ids, releasable_name: "acquired_media_releases", project_id: project.id } + end +end diff --git a/spec/controllers/contract_downloads_controller_spec.rb b/spec/controllers/contract_downloads_controller_spec.rb new file mode 100644 index 0000000..723861e --- /dev/null +++ b/spec/controllers/contract_downloads_controller_spec.rb @@ -0,0 +1,60 @@ +require "rails_helper" + +RSpec.describe ContractDownloadsController, type: :controller do + render_views + + let(:current_user) { create(:user) } + let(:project) { create(:project, :discovery_client, account: current_user.primary_account) } + + before do + sign_in current_user + end + + describe "#create" do + it "enqueues zip file generation job" do + create_list(:download, 4, project_id: project.id, name: "#{project.name.parameterize}_appearance-releases") + + expect { + post :create, params: { project_id: project.id, release_type: "AppearanceRelease" }, format: :js + }.to have_enqueued_job(GenerateContractsZipJob) + end + + it "creates a download record with 'not_started' status" do + create_list(:download, 4, project_id: project.id, name: "#{project.name.parameterize}_appearance-releases") + + expect { + post :create, params: { project_id: project.id, release_type: "AppearanceRelease" }, format: :js + }.to change(Download, :count).by(1) + + expect(Download.last.status).to eq('not_started') + end + + context "When there is no existing job" do + it "shows a notification to user" do + allow(ProjectsChannel).to receive(:broadcast_download_generation_update).with(be_kind_of(Download), I18n.t("contract_downloads.download.pending", release_type: "Appearance Release")) + + post :create, params: { project_id: project.id, release_type: "AppearanceRelease" }, format: :js + + expect(ProjectsChannel).to have_received(:broadcast_download_generation_update).with(be_kind_of(Download), I18n.t("contract_downloads.download.pending", release_type: "Appearance Release")) + end + end + + context "When there are existing jobs" do + let(:appearance_release_download) { create(:download, project_id: project.id, name: "#{project.name.parameterize}_appearance-releases") } + let(:acquired_media_release_download) { create(:download, project_id: project.id, name: "#{project.name.parameterize}_acquired-media-releases", release_type: "AcquiredMediaRelease") } + + before do + allow(Download).to receive_message_chain(:unfinished_desc_order, :offset).and_return([acquired_media_release_download, appearance_release_download]) + allow(ProjectsChannel).to receive(:broadcast_download_generation_update) + end + + it "shows names of other contracts in the notification, which are in progress" do + broadcast_message = "

    Your Music Release contracts are being prepared for download. You will be notified when it's ready.\n

    \n

    The following downloads are also in progress:

    \n
      \n
    • Acquired Media Release contracts (as of less than a minute ago)\n
    • \n
    • Appearance Release contracts (as of less than a minute ago)\n
    • \n
    \n" + + post :create, params: { project_id: project.id, release_type: "MusicRelease" }, format: :js + + expect(ProjectsChannel).to have_received(:broadcast_download_generation_update).with(be_kind_of(Download), broadcast_message) + end + end + end +end diff --git a/spec/controllers/contract_templates/blank_contracts_controller.rb b/spec/controllers/contract_templates/blank_contracts_controller.rb new file mode 100644 index 0000000..13b21ba --- /dev/null +++ b/spec/controllers/contract_templates/blank_contracts_controller.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe ContractTemplates::BlankContractsController do + let(:account) { build(:account) } + let(:current_user) { create(:user, :manager, primary_account: account) } + let(:project) { create(:project, members: [current_user], account: account) } + let(:contract_template) { create(:contract_template, project: project) } + + before do + sign_in(current_user) + end + + describe '#show' do + it 'responds with success' do + get :show, params: { contract_template_id: contract_template } + + expect(response).to be_successful + end + + it "responds with a PDF" do + pdf_body = Tempfile.new + allow_any_instance_of(Contract).to receive(:to_pdf).and_return(pdf_body) + + get :show, params: { contract_template_id: contract_template } + + expect(response).to be_successful + expect(content_type).to eq("application/pdf") + expect(content_disposition).to include("inline") + end + end + + describe '#new' do + it 'responds with success' do + get :new, params: { contract_template_id: contract_template } + + expect(response).to be_successful + end + end + + describe '#create' do + it "responds with redirect for invalid number of copies" do + post :create, params: { contract_template_id: contract_template, number_of_copies: -2 } + + expect(response).to redirect_to [:new, contract_template, :blank_contracts] + end + + it "responds with PDF" do + pdf_body = Tempfile.new + allow_any_instance_of(Contract).to receive(:to_pdf).and_return(pdf_body) + + get :show, params: { contract_template_id: contract_template, number_of_copies: 2 } + + expect(response).to be_successful + expect(content_type).to eq("application/pdf") + expect(content_disposition).to include("inline") + end + end +end diff --git a/spec/controllers/contract_templates/qr_codes_controller_spec.rb b/spec/controllers/contract_templates/qr_codes_controller_spec.rb new file mode 100644 index 0000000..7a23003 --- /dev/null +++ b/spec/controllers/contract_templates/qr_codes_controller_spec.rb @@ -0,0 +1,20 @@ +require 'rails_helper' + +describe ContractTemplates::QrCodesController do + let(:account) { build(:account) } + let(:current_user) { create(:user, :manager, primary_account: account) } + let(:project) { create(:project, members: [current_user], account: account) } + let(:contract_template) { create(:contract_template, project: project) } + + before do + sign_in(current_user) + end + + describe "#show" do + it "responds with success" do + get :show, params: { contract_template_id: contract_template } + + expect(response).to be_successful + end + end +end diff --git a/spec/controllers/contract_templates_controller_spec.rb b/spec/controllers/contract_templates_controller_spec.rb new file mode 100644 index 0000000..87ba377 --- /dev/null +++ b/spec/controllers/contract_templates_controller_spec.rb @@ -0,0 +1,169 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe ContractTemplatesController do + render_views + + let(:current_user) { create(:user, :manager) } + let(:project) { create(:project, members: current_user, account: current_user.primary_account) } + + before do + sign_in(current_user) + end + + describe '#index' do + it 'responds successfully' do + get :index, params: { project_id: project } + + expect(response).to be_successful + end + + it 'renders content' do + contract_template = create(:contract_template, + name: 'My Contract Template', fee: 50, release_type: 'appearance', + project: project) + sign_path = new_account_project_contract_template_appearance_release_path(project.account, project, contract_template) + sign_url = new_account_project_contract_template_appearance_release_url(project.account, project, contract_template) + + get :index, params: { project_id: project } + + expect(response.body).to have_content('My Contract Template') + expect(response.body).to have_link('Create New Release Template', href: new_project_contract_template_path(project)) + expect(response.body).to have_link('Import Release Template', href: new_project_release_template_imports_path(project)) + expect(response.body).to have_link('Copy Release URL', href: sign_url) + expect(response.body).to have_content('Manage') + expect(response.body).to have_link('Sign', href: sign_path) + expect(response.body).to have_link('Print') + + end + + context 'when there are no contract templates' do + it 'renders an empty message' do + get :index, params: { project_id: project } + + expect(response.body).to have_content('Release Templates will appear here') + end + end + + context 'when current user is an associate' do + let(:current_user) { create(:user, :associate) } + + it 'does not show the new contract template button' do + get :index, params: { project_id: project } + + expect(response.body).not_to have_link('Create New Release Template') + expect(response.body).not_to have_link('Import Release Template') + expect(response.body).not_to have_link('Delete') + end + end + + context 'when there are many records' do + it 'paginates the table' do + create_list(:contract_template, 20, project: project) + + get :index, params: { project_id: project } + + expect(response.body).to have_link('2', href: project_contract_templates_path(project, page: 2)) + end + end + end + + describe '#new' do + it 'responds ok' do + get :new, params: { project_id: project } + + expect(response).to be_successful + end + end + + describe '#create' do + it 'redirects' do + post :create, params: { project_id: project, contract_template: contract_template_params(valid: true) } + + expect(response).to redirect_to(project_contract_templates_path(project)) + end + + it 'sends pdf file for a preview' do + post :create, params: { project_id: project, contract_template: contract_template_params(valid: true), commit: 'preview' } + + expect(response.header['Content-Type']).to include('application/pdf') + expect(response.header['Content-Disposition']).to include('inline') + end + + it 'creates a new record' do + expect do + post :create, params: { project_id: project, contract_template: contract_template_params } + end.to change(ContractTemplate, :count).by(1) + end + + context 'when save fails' do + it 'responds ok' do + post :create, params: { project_id: project, contract_template: contract_template_params(valid: false) } + + expect(response).to be_successful + end + end + + context 'when current user is an associate' do + let(:current_user) { create(:user, :associate) } + + it 'raises exception' do + expect do + post :create, params: { project_id: project, contract_template: contract_template_params } + end.to raise_error(Pundit::NotAuthorizedError) + end + end + end + + describe '#destroy' do + let!(:contract_template) { create(:contract_template, project: project) } + + it 'redirects' do + delete :destroy, params: { id: contract_template } + + expect(response).to be_redirect + expect(response).to redirect_to [project, :contract_templates] + end + + it 'archives the record but does not delete' do + expect do + delete :destroy, params: { id: contract_template } + end.to change(ContractTemplate, :count).by(0) + expect(ContractTemplate.find(contract_template.id).archived_at).not_to be_nil + end + + context 'when contract template has associated releases' do + let(:contract_template) { create(:contract_template, appearance_releases: build_list(:appearance_release, 1), project: project) } + + it 'does not raise error' do + expect do + delete :destroy, params: { id: contract_template } + end.not_to raise_error + end + end + end + + private + + def contract_template_params(valid: true) + if valid + attributes_for(:contract_template).merge(exploitable_rights_params) + else + attributes_for(:contract_template).except(:name) + end + end + + def exploitable_rights_params + { + applicable_medium_id: ApplicableMedium.last.id, + applicable_medium_text: 'applicable_media', + territory_id: Territory.last.id, + territory_text: 'territory', + term_id: Term.last.id, + term_text: 'term', + restriction_id: Restriction.last.id, + restriction_text: 'restrictions' + } + end +end diff --git a/spec/controllers/contracts_controller_spec.rb b/spec/controllers/contracts_controller_spec.rb new file mode 100644 index 0000000..5456dcf --- /dev/null +++ b/spec/controllers/contracts_controller_spec.rb @@ -0,0 +1,66 @@ +require "rails_helper" + +RSpec.describe ContractsController, type: :controller do + let(:current_user) { create(:user) } + + before do + sign_in current_user + end + + shared_examples "a contracts controller" do + describe "#show" do + context "for a native release" do + it "responds with a PDF contract generated dynamically" do + pdf_body = Tempfile.new + allow_any_instance_of(Contract).to receive(:to_pdf).and_return(pdf_body) + + get :show, params: { format: :pdf, "#{native_releasable.model_name.singular}_id" => native_releasable } + + expect(response).to be_successful + expect(content_type).to eq("application/pdf") + expect(content_disposition).to include("inline") + end + end + + context "for a non-native release" do + it "responds with the attached contract" do + contract = double(:contract, service_url: "http://example.org/contract.pdf") + allow_any_instance_of(non_native_releasable.class).to receive(:contract).and_return(contract) + + get :show, params: { format: :pdf, "#{non_native_releasable.model_name.singular}_id" => non_native_releasable } + + expect(response).to be_redirect + expect(response).to redirect_to("http://example.org/contract.pdf") + end + end + end + end + + context "for appearance releases" do + let(:native_releasable) { create(:appearance_release_with_contract_template, :native) } + let(:non_native_releasable) { create(:appearance_release_with_contract_template, :non_native) } + + it_behaves_like "a contracts controller" + end + + context "for location releases" do + let(:native_releasable) { create(:location_release_with_contract_template, :native) } + let(:non_native_releasable) { create(:location_release_with_contract_template, :non_native) } + + it_behaves_like "a contracts controller" + end + + context "for material releases" do + let(:native_releasable) { create(:material_release_with_contract_template, :native) } + let(:non_native_releasable) { create(:material_release_with_contract_template, :non_native) } + + it_behaves_like "a contracts controller" + end + + context "for talent releases" do + let(:native_releasable) { create(:talent_release_with_contract_template, :native) } + let(:non_native_releasable) { create(:talent_release_with_contract_template, :non_native) } + + it_behaves_like "a contracts controller" + end +end diff --git a/spec/controllers/directories_controller_spec.rb b/spec/controllers/directories_controller_spec.rb new file mode 100644 index 0000000..e8ab73e --- /dev/null +++ b/spec/controllers/directories_controller_spec.rb @@ -0,0 +1,194 @@ +require 'rails_helper' + +RSpec.describe DirectoriesController, type: :controller do + render_views + + let(:user) { create(:user) } + let(:account) { user.primary_account } + let(:project) { create(:project, account: user.primary_account) } + let(:directory) { create(:directory, project: project) } + let(:directory_with_files) { create(:directory, :with_files, project: project) } + let(:directory_with_many_files) { create(:directory, :many_files, project: project) } + + before do + sign_in user + end + + describe "#new" do + it "responds successfully" do + get :new, params: { project_id: project } + + expect(response).to be_successful + expect(assigns(:directory)).to be_a_new(Directory) + expect(response).to render_template(:new) + end + end + + describe "#create" do + it "responds with a redirect" do + post :create, params: { project_id: project.id, directory: directory_params } + + expect(response).to be_redirect + expect(flash.notice).not_to be_nil + end + + it "does create a new record" do + expect { + post :create, params: { project_id: project.id, directory: directory_params } + }.to change(Directory, :count) + end + + it "logs an event" do + expect { + post :create, params: { project_id: project.id, directory: directory_params } + }.to have_enqueued_job(TrackAnalyticsJob).with(user, account, :track_create_custom_folder, user_agent: "Rails Testing", user_ip: "0.0.0.0") + end + + context "with invalid data" do + it "does not create a new record" do + expect { + post :create, params: { project_id: project.id, directory: { name: "", category: "Other" } } + }.not_to change(Directory, :count) + end + end + + context "with permissions" do + it "assigns selected permission to directory" do + post :create, params: { project_id: project.id, directory: directory_params_with_permissions } + + expect(Directory.last.permissions).to eq("Account Managers & Project Managers") + end + + it "sets permissions to everyone when no permission is selected by user" do + post :create, params: { project_id: project.id, directory: directory_params } + + expect(Directory.last.permissions).to eq("Everyone") + end + end + end + + describe "#show" do + it "responds successfully" do + get :show, params: { project_id: project.id, id: directory_with_files.id } + + expect(response).to be_successful + end + + it "renders files" do + get :show, params: { project_id: project.id, id: directory_with_files.id } + + expect(response.body).to have_content "location_photo.png" + expect(response.body).to have_link "Add File" + expect(response.body).to have_content "Manage" + end + + context "when there are no files" do + it "renders an empty message" do + get :show, params: { project_id: project.id, id: directory.id } + + expect(response.body).to have_content("Uploaded files will appear here") + end + end + + context "when there are many files" do + it "paginates the table" do + get :show, params: { project_id: project.id, id: directory_with_many_files.id } + + expect(response.body).to have_link("2", href: project_directory_path(project, directory_with_many_files.id, page: 2)) + end + end + + context "for xhr request" do + it "filters the files by a query param" do + get :show, params: { project_id: project.id, id: directory_with_many_files.id, query: "loc" }, xhr: true + + expect(response.body).to have_content("location_photo.png") + expect(response.body).not_to have_content("material_photo.png") + expect(response.body).not_to have_content("person_photo.png") + end + end + end + + describe "#edit" do + it "sets directory" do + get :edit, params: { project_id: project.id, id: directory.id } + + expect(response).to be_successful + expect(assigns(:directory)).to eq directory + end + end + + describe "#update" do + it "responds with redirect" do + patch :update, params: { project_id: project.id, id: directory.id, directory: directory_params } + + expect(response).to be_redirect + expect(flash.notice).not_to be_nil + end + + it "updates directory with files and redirects" do + patch :update, params: { project_id: project.id, id: directory.id, directory: directory_params_with_files } + + expect(directory.files.count).to eq(2) + expect(directory.files.first.filename).to eq("location_photo.png") + expect(response).to be_redirect + expect(flash.notice).not_to be_nil + end + + context "with permissions" do + let(:directory) { create(:directory, project: project) } + + it "updates directory with permissions" do + expect { + patch :update, params: { project_id: project.id, id: directory.id, directory: directory_params_with_permissions } + directory.reload + }.to change(directory, :permissions).from("Everyone").to("Account Managers & Project Managers") + end + end + end + + describe "#destroy_file" do + it "responds with redirect after deleting file" do + delete :destroy_file, params: { project_id: project.id, id: directory_with_files.id, file_id: directory_with_files.files.first.id } + + expect(directory_with_files.files.count).to eq(0) + expect(response).to be_redirect + expect(response).to redirect_to(project_directory_path(project, directory_with_files)) + expect(flash.alert).not_to be_nil + end + end + + describe "#destroy" do + let!(:directory_2) { create(:directory, project: project, name: "Stories") } + + it "responds with redirect" do + delete :destroy, params: { project_id: project.id, id: directory.id } + + expect(response).to be_redirect + expect(response).to redirect_to(project_path(project)) + expect(flash.alert).not_to be_nil + end + + it "destroys the record" do + expect { + delete :destroy, params: { project_id: project.id, id: directory_2.id } + }.to change(Directory, :count).by(-1) + end + end + + private + + def directory_params + attributes_for(:directory) + end + + def directory_params_with_files + files = 2.times.map { Rack::Test::UploadedFile.new(file_fixture("location_photo.png"), "image/png") } + + directory_params.merge({ files: files }) + end + + def directory_params_with_permissions + directory_params.merge({ permissions: "Account Managers & Project Managers" }) + end +end diff --git a/spec/controllers/downloads_controller_spec.rb b/spec/controllers/downloads_controller_spec.rb new file mode 100644 index 0000000..0d6838b --- /dev/null +++ b/spec/controllers/downloads_controller_spec.rb @@ -0,0 +1,92 @@ +require "rails_helper" + +RSpec.describe DownloadsController, type: :controller do + render_views + + let(:current_user) { create(:user) } + let(:project) { create(:project, :discovery_client, account: current_user.primary_account) } + + before do + sign_in current_user + end + + describe "#index" do + it "responds successfully" do + get :index, params: { project_id: project } + + expect(response).to be_successful + end + + context "when there are no reports" do + it "renders an empty message" do + get :index, params: { project_id: project } + + expect(response.body).to have_content("Downloads will appear here") + end + end + + context "when there are many records" do + it "paginates the table" do + create_list(:download, 20, project: project, name: "#{project.name.parameterize}_appearance-releases") + + get :index, params: { project_id: project } + + expect(response.body).to have_link("Download", href: project_downloads_path(project)) + expect(response.body).to have_link("2", href: project_downloads_path(project, page: 2)) + end + end + + describe "for xhr request" do + before do + downloads = [ + create(:download_release_type_appearance, project: project), + create(:download_release_type_music, project: project) + ] + end + + it "shows all downloads if search field is empty" do + get :index, params: { project_id: project }, xhr: true + + expect(response.body).to have_content("Natgeo Contract") + expect(response.body).to have_content("Discovery Contract") + end + + it "shows only downloads with file name or type matching search query" do + get :index, params: { project_id: project, query: "Natgeo" }, xhr: true + + expect(response.body).not_to have_content("Discovery Contract") + expect(response.body).to have_content("Natgeo Contract") + + get :index, params: { project_id: project, query: "Music" }, xhr: true + + expect(response.body).to have_content("Music Release") + expect(response.body).not_to have_content("Appearance Release") + end + + it "removes pagination in case of limited search results" do + create_list(:download_release_type_music, 20, project: project) + + get :index, params: { project_id: project, query: "Appearance" }, xhr: true + expect(response.body).to have_no_link("1", href: project_downloads_path(project, page: 1)) + end + end + end + + describe "#destroy" do + let!(:download) { create(:download, project: project) } + + it "responds with redirect" do + delete :destroy, params: { project_id: project.id, id: download.id } + + expect(response).to be_redirect + expect(response).to redirect_to(project_downloads_path(project)) + expect(flash.alert).not_to be_nil + end + + it "destroys the record" do + expect { + delete :destroy, params: { project_id: project.id, id: download.id } + }.to change(Download, :count).by(-1) + end + end +end diff --git a/spec/controllers/file_infos_controller_spec.rb b/spec/controllers/file_infos_controller_spec.rb new file mode 100644 index 0000000..aab3992 --- /dev/null +++ b/spec/controllers/file_infos_controller_spec.rb @@ -0,0 +1,73 @@ +require "rails_helper" + +RSpec.describe FileInfosController, type: :controller do + render_views + + let(:current_user) { create(:user) } + let(:project) { create(:project, account: current_user.accounts.first) } + let(:acquired_media_release) { create(:acquired_media_release, project: project) } + + before do + sign_in(current_user) + end + + shared_examples "a file infoable releases controller" do + describe "#edit" do + it "responds successfully" do + get :edit, params: { "#{subject.model_name.param_key}_id" => subject } + + expect(response).to be_successful + end + end + + describe "#update" do + context "when releasable updated successfully" do + it "responds with a redirect" do + patch :update, params: { "#{subject.model_name.param_key}_id": subject, subject.model_name.param_key => release_params } + + expect(response).to be_redirect + expect(response).to redirect_to [project, subject.model_name.plural] + expect(flash.notice).to eq(t("file_infos.update.notice")) + end + + it "enqueues tagging job" do + expect { + patch :update, params: { "#{subject.model_name.param_key}_id": subject, subject.model_name.param_key => {} } + }.to have_enqueued_job(SetTagsForReleasableJob).with(subject.class.last) + end + end + end + + context "when releasable NOT updated successfully" do + before do + allow_any_instance_of(subject.class).to receive(:update).and_return(false) + end + + it "renders edit with flash message" do + patch :update, params: { "#{subject.model_name.param_key}_id": subject, subject.model_name.param_key => release_params } + + expect(controller).to have_rendered(:edit) + end + + it "does not enqueue tagging job" do + expect { + patch :update, params: { "#{subject.model_name.param_key}_id": subject, subject.model_name.param_key => release_params } + }.not_to have_enqueued_job(SetTagsForReleasableJob) + end + end + end + + context "for acquired media releases" do + subject { create(:acquired_media_release, project: project) } + + it_behaves_like "a file infoable releases controller" + end + + private + + def release_params + files = 2.times.map { Rack::Test::UploadedFile.new(file_fixture("audio.mp3"), "audio/mp3") } + + { file_infos_attributes: files } + end +end diff --git a/spec/controllers/graphic_reports_controller_spec.rb b/spec/controllers/graphic_reports_controller_spec.rb new file mode 100644 index 0000000..1caab24 --- /dev/null +++ b/spec/controllers/graphic_reports_controller_spec.rb @@ -0,0 +1,34 @@ +require "rails_helper" + +RSpec.describe GraphicReportsController, type: :controller do + let(:user) { create(:user) } + let(:video) { create(:video, project: create(:project, account: user.primary_account)) } + + before :each do + sign_in user + end + + describe "show" do + context "when type is discovery" do + it "builds the report and responds successfully" do + get :show, params: { video_id: video, format: "xlsx", type: "discovery" } + + expect(response).to be_successful + expect(response.body.unpack("C*").pack("U*")).to_not be_blank + expect(response.header["Content-Type"]).to eq "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + expect(response.header["Content-Disposition"]).to match "video_file-mp4_gfx-cue-list.xlsx" + end + end + + context "when type is nat_geo" do + it "builds the report and responds successfully" do + get :show, params: { video_id: video, format: "xlsx", type: "nat_geo" } + + expect(response).to be_successful + expect(response.body.unpack("C*").pack("U*")).to_not be_blank + expect(response.header["Content-Type"]).to eq "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + expect(response.header["Content-Disposition"]).to match "video_file-mp4_text-graphics-log.xlsx" + end + end + end +end diff --git a/spec/controllers/issues_and_concerns_reports_controller_spec.rb b/spec/controllers/issues_and_concerns_reports_controller_spec.rb new file mode 100644 index 0000000..514cb66 --- /dev/null +++ b/spec/controllers/issues_and_concerns_reports_controller_spec.rb @@ -0,0 +1,21 @@ +require "rails_helper" + +RSpec.describe IssuesAndConcernsReportsController, type: :controller do + let(:user) { create(:user) } + let(:project) { create(:project, account: user.primary_account) } + let(:video) { create(:video, project: project) } + + before :each do + sign_in user + end + + describe "#show" do + context "when format is xls" do + it "returns xls file" do + get :show, params: { video_id: video, format: "xlsx" } + + expect(response).to be_successful + end + end + end +end diff --git a/spec/controllers/location_releases_controller_spec.rb b/spec/controllers/location_releases_controller_spec.rb new file mode 100644 index 0000000..e4d29e7 --- /dev/null +++ b/spec/controllers/location_releases_controller_spec.rb @@ -0,0 +1,203 @@ +require "rails_helper" + +RSpec.describe LocationReleasesController, type: :controller do + render_views + + let(:user) { create(:user) } + let(:account) { user.primary_account } + let(:project) { create(:project, account: user.primary_account) } + + before :each do + sign_in user + end + + describe "#index" do + it "responds successfully" do + get :index, params: { project_id: project } + + expect(response).to be_successful + end + + it "renders content" do + release = create(:location_release, project: project, + name: "My Release", + address_street1: "123 Test Lane", address_city: "New York", address_state: "NY", address_zip: "10000") + create(:note, notable: release, content: "Some notes here") + + get :index, params: { project_id: project } + + expect(response.body).to have_content "My Release" + expect(response.body).to have_content "123 Test Lane\nNew York, NY 10000" + expect(response.body).to have_content "Some notes here" + expect(response.body).to have_link "Import Release" + expect(response.body).to have_content "Manage" + end + + context "when there are no location releases" do + it "renders an empty message" do + get :index, params: { project_id: project } + + expect(response.body).to have_content("Location Releases will appear here") + end + end + + context "when there are many records" do + it "paginates the table" do + create_list(:location_release, 20, project: project) + get :index, params: { project_id: project } + + expect(response.body).to have_link("2", href: project_location_releases_path(project, page: 2)) + end + end + + context "for xhr request" do + it "filters the releases by a query param" do + location_releases = [ + create(:location_release, name: "Benny's Burritos", project: project), + create(:location_release, name: "Chipotle", project: project), + ] + + get :index, params: { project_id: project, query: "Benny" }, xhr: true + + expect(response.body).to have_content("Benny") + expect(response.body).not_to have_content("Chipotle") + end + end + end + + describe "#new" do + it "responds successfully" do + get :new, params: { project_id: project } + + expect(response).to be_successful + end + end + + describe "#create" do + it "responds successfully" do + expect { + post :create, params: { project_id: project, location_release: location_release_params } + }.to change(LocationRelease, :count).by(1) + + expect(response).to be_redirect + expect(response).to redirect_to [project, :location_releases] + expect(flash.notice).to be_present + end + + it "enqueues tagging job" do + expect { + post :create, params: { project_id: project, location_release: location_release_params } + }.to have_enqueued_job(SetTagsForReleasableJob).with(LocationRelease.last) + end + + it "logs analytics" do + expect { + post :create, params: { project_id: project, location_release: location_release_params } + }.to( + have_enqueued_job(TrackAnalyticsJob).with(user, account, :track_create_non_native_release, release_type: "LocationRelease", user_agent: "Rails Testing", user_ip: "0.0.0.0") + ) + end + + context "when the record would be invalid" do + before do + allow_any_instance_of(LocationRelease).to receive(:save).and_return(false) + end + + it "re-renders the form" do + expect { + post :create, params: { project_id: project, location_release: location_release_params } + }.not_to change(LocationRelease, :count) + + expect(response).to be_successful + expect(flash.notice).to be_nil + end + + it "does not enqueue any jobs" do + expect { + post :create, params: { project_id: project, location_release: location_release_params } + }.not_to have_enqueued_job + end + end + end + + describe "#edit" do + let(:non_native_location_release) { create(:location_release, project: project) } + let(:native_location_release) { create(:location_release, :native, project: project) } + + it "responds successfully" do + get :edit, params: { project_id: project, id: non_native_location_release } + + expect(response).to be_successful + end + + it "responds with error for native release" do + expect{get :edit, params: { id: native_location_release }}.to raise_exception Pundit::NotAuthorizedError + end + end + + describe "#update" do + let(:location_release) { create(:location_release, project: project) } + + it "responds successfully" do + patch :update, params: { project_id: project, id: location_release, location_release: location_release_params } + + expect(response).to redirect_to [project, :location_releases] + expect(flash.notice).to be_present + end + + context "when the record would be invalid" do + before do + allow_any_instance_of(LocationRelease).to receive(:save).and_return(false) + end + + it "re-renders the form" do + patch :update, params: { project_id: project, id: location_release, location_release: location_release_params } + + expect(response).to be_successful + expect(flash.notice).to be_nil + end + end + end + + describe "#destroy" do + let!(:location_release) { create(:location_release, project: project) } + + it "responds with redirect" do + delete :destroy, params: { project_id: project, id: location_release } + + expect(response).to be_redirect + expect(response).to redirect_to [project, :location_releases] + end + + it "sets the flash" do + delete :destroy, params: { project_id: project, id: location_release } + + expect(flash.alert).not_to be_nil + end + + it "destroys the record" do + expect { + delete :destroy, params: { project_id: project, id: location_release } + }.to change(LocationRelease, :count).by(-1) + end + end + + private + + def location_release_params + attributes_for(:location_release).merge(exploitable_rights_params) + end + + def exploitable_rights_params + { + applicable_medium_id: ApplicableMedium.last.id, + applicable_medium_text: "applicable_media", + territory_id: Territory.last.id, + territory_text: "territory", + term_id: Term.last.id, + term_text: "term", + restriction_id: Restriction.last.id, + restriction_text: "restrictions", + } + end +end diff --git a/spec/controllers/material_releases_controller_spec.rb b/spec/controllers/material_releases_controller_spec.rb new file mode 100644 index 0000000..3272c33 --- /dev/null +++ b/spec/controllers/material_releases_controller_spec.rb @@ -0,0 +1,200 @@ +require "rails_helper" + +RSpec.describe MaterialReleasesController, type: :controller do + render_views + + let(:user) { create(:user) } + let(:account) { user.primary_account } + let(:project) { create(:project, account: user.primary_account) } + + before :each do + sign_in user + end + + describe "#index" do + it "responds successfully" do + get :index, params: { project_id: project } + + expect(response).to be_successful + end + + it "renders content" do + release = create(:material_release, project: project, name: "My Release") + create(:note, notable: release, content: "Some notes here") + + get :index, params: { project_id: project } + + expect(response.body).to have_content "My Release" + expect(response.body).to have_content "Some notes here" + expect(response.body).to have_link "Import Release" + expect(response.body).to have_content "Manage" + end + + context "when there are no material releases" do + it "renders an empty message" do + get :index, params: { project_id: project } + + expect(response.body).to have_content("Material Releases will appear here") + end + end + + context "when there are many records" do + it "paginates the table" do + create_list(:material_release, 20, project: project) + get :index, params: { project_id: project } + + expect(response.body).to have_link("2", href: project_material_releases_path(project, page: 2)) + end + end + + context "for xhr request" do + it "filters the releases by a query param" do + material_releases = [ + create(:material_release, name: "Coke", project: project), + create(:material_release, name: "Pepsi", project: project), + ] + + get :index, params: { project_id: project, query: "Pepsi" }, xhr: true + + expect(response.body).not_to have_content("Coke") + expect(response.body).to have_content("Pepsi") + end + end + end + + describe "#new" do + it "responds successfully" do + get :new, params: { project_id: project } + + expect(response).to be_successful + end + end + + describe "#create" do + it "responds successfully" do + expect { + post :create, params: { project_id: project, material_release: material_release_params } + }.to change(MaterialRelease, :count).by(1) + + expect(response).to be_redirect + expect(response).to redirect_to [project, :material_releases] + expect(flash.notice).to be_present + end + + it "enqueues tagging job" do + expect { + post :create, params: { project_id: project, material_release: material_release_params } + }.to have_enqueued_job(SetTagsForReleasableJob).with(MaterialRelease.last) + end + + it "logs analytics" do + expect { + post :create, params: { project_id: project, material_release: material_release_params } + }.to( + have_enqueued_job(TrackAnalyticsJob).with(user, account, :track_create_non_native_release, release_type: "MaterialRelease", user_agent: "Rails Testing", user_ip: "0.0.0.0") + ) + end + + context "when the record would be invalid" do + before do + allow_any_instance_of(MaterialRelease).to receive(:save).and_return(false) + end + + it "re-renders the form" do + expect { + post :create, params: { project_id: project, material_release: material_release_params } + }.not_to change(MaterialRelease, :count) + + expect(response).to be_successful + expect(flash.notice).to be_nil + end + + it "does not enqueue any jobs" do + expect { + post :create, params: { project_id: project, material_release: material_release_params } + }.not_to have_enqueued_job + end + end + end + + describe "#edit" do + let(:non_native_material_release) { create(:material_release, project: project) } + let(:native_material_release) { create(:material_release, :native, project: project) } + + it "responds successfully for non-native release" do + get :edit, params: { project_id: project, id: non_native_material_release } + + expect(response).to be_successful + end + + it "responds with error for native release" do + expect{get :edit, params: { id: native_material_release }}.to raise_exception Pundit::NotAuthorizedError + end + end + + describe "#update" do + let(:material_release) { create(:material_release, project: project) } + + it "responds successfully" do + patch :update, params: { project_id: project, id: material_release, material_release: material_release_params } + + expect(response).to redirect_to [project, :material_releases] + expect(flash.notice).to be_present + end + + context "when the record would be invalid" do + before do + allow_any_instance_of(MaterialRelease).to receive(:save).and_return(false) + end + + it "re-renders the form" do + patch :update, params: { project_id: project, id: material_release, material_release: material_release_params } + + expect(response).to be_successful + expect(flash.notice).to be_nil + end + end + end + + describe "#destroy" do + let!(:material_release) { create(:material_release, project: project) } + + it "responds with redirect" do + delete :destroy, params: { project_id: project, id: material_release } + + expect(response).to be_redirect + expect(response).to redirect_to [project, :material_releases] + end + + it "sets the flash" do + delete :destroy, params: { project_id: project, id: material_release } + + expect(flash.alert).not_to be_nil + end + + it "destroys the record" do + expect { + delete :destroy, params: { project_id: project, id: material_release } + }.to change(MaterialRelease, :count).by(-1) + end + end + + private + + def material_release_params + attributes_for(:material_release).merge(exploitable_rights_params) + end + + def exploitable_rights_params + { + applicable_medium_id: ApplicableMedium.last.id, + applicable_medium_text: "applicable_media", + territory_id: Territory.last.id, + territory_text: "territory", + term_id: Term.last.id, + term_text: "term", + restriction_id: Restriction.last.id, + restriction_text: "restrictions", + } + end +end diff --git a/spec/controllers/music_releases_controller_spec.rb b/spec/controllers/music_releases_controller_spec.rb new file mode 100644 index 0000000..f8702c6 --- /dev/null +++ b/spec/controllers/music_releases_controller_spec.rb @@ -0,0 +1,277 @@ +require "rails_helper" + +RSpec.describe MusicReleasesController, type: :controller do + render_views + + let(:user) { create(:user) } + let(:account) { user.primary_account } + let(:project) { create(:project, account: user.primary_account) } + + before :each do + sign_in user + end + + describe "#index" do + it "responds successfully" do + get :index, params: { project_id: project } + + expect(response).to be_successful + end + + it "renders content" do + release = create(:music_release, project: project, name: "My Release") + create(:note, notable: release, content: "Some notes here") + + get :index, params: { project_id: project } + + expect(response.body).to have_content "My Release" + expect(response.body).to have_content "0" + expect(response.body).to have_content "1" + expect(response.body).to have_content "1" + expect(response.body).to have_content "Some notes here" + expect(response.body).to have_link "Import Release" + expect(response.body).to have_content "Manage" + end + + context "when there are no music releases" do + it "renders an empty message" do + get :index, params: { project_id: project } + + expect(response.body).to have_content("Music Releases will appear here") + end + end + + context "when there are many records" do + it "paginates the table" do + create_list(:music_release, 20, project: project) + + get :index, params: { project_id: project } + + expect(response.body).to have_link("2", href: project_music_releases_path(project, page: 2)) + end + end + + context "for xhr request" do + it "filters the releases by a query param" do + music_releases = [ + create(:music_release, name: "Extreme Music Collection", project: project), + create(:music_release, name: "Audio Networks Music Collection", project: project), + ] + + get :index, params: { project_id: project, query: "Audio" }, xhr: true + + expect(response.body).not_to have_content("Extreme") + expect(response.body).to have_content("Audio") + end + end + end + + describe "#new" do + it "responds successfully" do + get :new, params: { project_id: project } + + expect(response).to be_successful + end + + it "initializes with 5 composers and 2 publishers" do + get :new, params: { project_id: project } + + expect(assigns(:music_release).composers.size).to eq 5 + expect(assigns(:music_release).publishers.size).to eq 2 + end + end + + describe "#create" do + it "responds successfully" do + expect { + post :create, params: { project_id: project, music_release: music_release_params } + }.to change(MusicRelease, :count).by(1) + + expect(response).to be_redirect + expect(response).to redirect_to [project, :music_releases] + expect(flash.notice).to be_present + end + + it "creates nested file info records" do + expect { + post :create, params: { + project_id: project, + music_release: music_release_params.merge( + file_infos_attributes: { + 0 => attributes_for(:file_info) + } + ) + } + }.to change(FileInfo, :count).by(1) + expect(MusicRelease.last.file_infos.size).to eq(1) + end + + it "creates nested composer records" do + expect { + post :create, params: { + project_id: project, + music_release: music_release_params.merge( + composers_attributes: { + 0 => { + name: "name", + affiliation: "affiliation", + percentage: 100, + cae_number: "cae123456789", + }, + } + ) + } + }.to change(Composer, :count).by(1) + expect(MusicRelease.last.composers.size).to eq(1) + end + + it "creates nested publisher records" do + expect { + post :create, params: { + project_id: project, + music_release: music_release_params.merge( + publishers_attributes: { + 0 => attributes_for(:publisher), + } + ) + } + }.to change(Publisher, :count).by(1) + expect(MusicRelease.last.publishers.size).to eq(1) + end + + it "enqueues tagging job" do + expect { + post :create, params: { project_id: project, music_release: music_release_params } + }.to have_enqueued_job(SetTagsForReleasableJob).with(MusicRelease.last) + end + + it "logs analytics" do + expect { + post :create, params: { project_id: project, music_release: music_release_params } + }.to( + have_enqueued_job(TrackAnalyticsJob).with(user, account, :track_create_non_native_release, release_type: "MusicRelease", user_agent: "Rails Testing", user_ip: "0.0.0.0") + ) + end + + context "when the record would be invalid" do + before :each do + allow_any_instance_of(MusicRelease).to receive(:save).and_return(false) + end + + it "re-renders the form" do + post :create, params: { + project_id: project, + music_release: music_release_params.merge( + publishers_attributes: { + 0 => attributes_for(:publisher), + } + ) + } + + expect(response).to render_template :new + expect(flash.notice).to be_nil + end + + it "does not enqueue any jobs" do + expect { + post :create, params: { project_id: project, music_release: music_release_params } + }.not_to have_enqueued_job + end + + it "initializes with 5 composers and 2 publishers" do + post :create, params: { + project_id: project, + music_release: music_release_params.merge( + publishers_attributes: { + 0 => attributes_for(:publisher), + } + ) + } + + expect(assigns(:music_release).composers.size).to eq 5 + expect(assigns(:music_release).publishers.size).to eq 2 + end + end + end + + describe "#edit" do + let(:music_release) { create(:music_release, project: project) } + + it "responds successfully" do + get :edit, params: { id: music_release } + + expect(response).to be_successful + end + + it "initializes with 5 composers and 2 publishers" do + get :edit, params: { id: music_release } + + expect(assigns(:music_release).composers.size).to eq 5 + expect(assigns(:music_release).publishers.size).to eq 2 + end + end + + describe "#update" do + let(:music_release) { create(:music_release, project: project) } + + it "responds successfully" do + put :update, params: { id: music_release, music_release: { name: "Updated name" } } + + expect(response).to redirect_to [project, :music_releases] + expect(flash.notice).to eq "The music release has been updated" + expect(music_release.reload.name).to eq "Updated name" + end + + context "when the record would be invalid" do + before :each do + allow_any_instance_of(MusicRelease).to receive(:update).and_return(false) + end + + it "re-renders the form" do + put :update, params: { id: music_release, music_release: music_release_params } + + expect(response).to render_template :edit + expect(flash.notice).to be_nil + end + end + end + + describe "#destroy" do + let!(:music_release) { create(:music_release, project: project) } + + it "destroys the record" do + expect { + delete :destroy, params: { id: music_release } + }.to change(MusicRelease, :count).by(-1) + end + + it "redirects to project and sets alert" do + delete :destroy, params: { id: music_release } + + expect(response).to redirect_to [project, :music_releases] + expect(flash.alert).to eq "The music release has been deleted" + end + end + + private + + def music_release_params + attributes_for(:music_release).merge( + composers_attributes: [attributes_for(:composer)], + publishers_attributes: [attributes_for(:publisher)], + ).merge(exploitable_rights_params) + end + + def exploitable_rights_params + { + applicable_medium_id: ApplicableMedium.last.id, + applicable_medium_text: "applicable_media", + territory_id: Territory.last.id, + territory_text: "territory", + term_id: Term.last.id, + term_text: "term", + restriction_id: Restriction.last.id, + restriction_text: "restrictions", + } + end +end diff --git a/spec/controllers/notes_controller_spec.rb b/spec/controllers/notes_controller_spec.rb new file mode 100644 index 0000000..ce6adce --- /dev/null +++ b/spec/controllers/notes_controller_spec.rb @@ -0,0 +1,56 @@ +require "rails_helper" + +RSpec.describe NotesController, type: :controller do + let(:current_user) { create(:user) } + let(:releasable) { create(:appearance_release) } + + before do + sign_in current_user + end + + describe "#index" do + it "responds successfully" do + get :index, xhr: true, params: { "#{releasable.model_name.singular}_id" => releasable } + + expect(response).to be_successful + end + end + + describe "#new" do + it "responds successfully" do + get :new, xhr: true, params: { "#{releasable.model_name.singular}_id" => releasable } + end + end + + describe "#create" do + it "responds successfully" do + post :create, xhr: true, params: { "#{releasable.model_name.singular}_id" => releasable, note: note_params } + + expect(response).to be_successful + end + + it "creates a new Note record" do + expect { + post :create, xhr: true, params: { "#{releasable.model_name.singular}_id" => releasable, note: note_params } + }.to change(Note, :count).by(1) + end + + context "when the new note is not valid" do + before do + allow_any_instance_of(Note).to receive(:save).and_return(false) + end + + it "does not create a new Note record" do + expect { + post :create, xhr: true, params: { "#{releasable.model_name.singular}_id" => releasable, note: note_params } + }.not_to change(Note, :count) + end + end + end + + private + + def note_params + attributes_for(:note) + end +end diff --git a/spec/controllers/notifications_controller_spec.rb b/spec/controllers/notifications_controller_spec.rb new file mode 100644 index 0000000..e1a457e --- /dev/null +++ b/spec/controllers/notifications_controller_spec.rb @@ -0,0 +1,147 @@ +require "rails_helper" + +RSpec.describe NotificationsController, type: :controller do + describe "#create" do + context "for a subscription confirmation" do + it "responds ok" do + post :create, body: subscription_confirmation_body_json + + expect(response).to be_ok + end + + it "log the subscribe url" do + allow(Rails.logger).to receive(:info) + + post :create, body: subscription_confirmation_body_json + + expect(Rails.logger).to have_received(:info).with("http://example.com") + end + end + + context "for a notification" do + context "for video analysis" do + let!(:video) { create(:video, analysis_uid: "job_id") } + + context "when successful" do + it "responds ok" do + post :create, body: successful_notification_body_json + + expect(response).to be_ok + end + + it "updates video analysis status" do + post :create, body: successful_notification_body_json + + expect(video.reload).to be_analysis_success + end + + it "broadcasts analysis update" do + allow(ProjectsChannel).to receive(:broadcast_video_analysis_update) + + post :create, body: successful_notification_body_json + + expect(ProjectsChannel).to have_received(:broadcast_video_analysis_update).with(video) + end + end + + context "when failed" do + it "responds ok" do + post :create, body: failure_notification_body_json + + expect(response).to be_ok + end + + it "updates video analysis status" do + post :create, body: failure_notification_body_json + + expect(video.reload).to be_analysis_failure + end + + it "broadcasts analysis update" do + allow(ProjectsChannel).to receive(:broadcast_video_analysis_update) + + post :create, body: successful_notification_body_json + + expect(ProjectsChannel).to have_received(:broadcast_video_analysis_update).with(video) + end + end + end + + context "for audio analysis" do + let!(:video) { create(:video, audio_analysis_uid: "job_id") } + + context "when successful" do + it "responds ok" do + post :create, body: successful_notification_body_json("audio") + + expect(response).to be_ok + end + + it "updates video analysis status" do + post :create, body: successful_notification_body_json("audio") + + expect(video.reload).to be_audio_analysis_success + end + + it "broadcasts analysis update" do + allow(ProjectsChannel).to receive(:broadcast_video_analysis_update) + + post :create, body: successful_notification_body_json("audio") + + expect(ProjectsChannel).to have_received(:broadcast_video_analysis_update).with(video) + end + end + + context "when failed" do + it "responds ok" do + post :create, body: failure_notification_body_json("audio") + + expect(response).to be_ok + end + + it "updates video analysis status" do + post :create, body: failure_notification_body_json("audio") + + expect(video.reload).to be_audio_analysis_failure + end + + it "broadcasts analysis update" do + allow(ProjectsChannel).to receive(:broadcast_video_analysis_update) + + post :create, body: successful_notification_body_json("audio") + + expect(ProjectsChannel).to have_received(:broadcast_video_analysis_update).with(video) + end + end + end + end + end + + private + + def subscription_confirmation_body_json + { "Type" => "SubscriptionConfirmation", "SubscribeURL" => "http://example.com" }.to_json + end + + def successful_notification_body_json(analysis_type = "video") + { + "Type" => "Notification", + "Message" => { + "JobId" => "job_id", + "Status" => "SUCCEEDED", + "AnalysisType" => analysis_type, + }.to_json + }.to_json + end + + def failure_notification_body_json(analysis_type = "video") + { + "Type" => "Notification", + "Message" => { + "JobId" => "job_id", + "Status" => "FAILED", + "AnalysisType" => analysis_type, + }.to_json + }.to_json + end +end diff --git a/spec/controllers/password_resets_controller_spec.rb b/spec/controllers/password_resets_controller_spec.rb new file mode 100644 index 0000000..b664891 --- /dev/null +++ b/spec/controllers/password_resets_controller_spec.rb @@ -0,0 +1,113 @@ +require "rails_helper" + +RSpec.describe PasswordResetsController, type: :controller do + render_views + + let(:user) { create(:user) } + + describe "#new" do + it "responds successfully" do + get :new + + expect(response).to be_successful + end + + it "renders the content" do + get :new + + expect(response.body).to have_content("Password Reset") + expect(response.body).to have_field("Email") + end + end + + describe "#create" do + it "redirects to new_session_path" do + post :create, params: { password_reset: { email: user.email } } + + expect(response).to be_redirect + expect(response).to redirect_to(new_session_path) + end + + it "shows an alert message" do + post :create, params: { password_reset: { email: user.email } } + + expect(flash.notice).not_to be_nil + end + + it "sends the reset email" do + assert_enqueued_emails 1 do + post :create, params: { password_reset: { email: user.email } } + end + end + end + + describe "#edit" do + it "responds successfully" do + get :edit, params: { id: user.password_reset_token } + + expect(response).to be_successful + end + + it "renders the right content" do + user = create(:user) + + get :edit, params: { id: user.password_reset_token } + + expect(response.body).to have_content("Password Reset") + expect(response.body).to have_selector("input[name='password_reset[password]']") + end + + context "when reset token is invalid" do + it "redirects to the sign in page" do + get :edit, params: { id: "bad token" } + + expect(response).to be_redirect + expect(response).to redirect_to(new_session_path) + expect(flash.notice).to be_present + end + end + end + + describe "#update" do + it "redirects to new_session_path" do + patch :update, params: { id: user.password_reset_token, password_reset: { password: "newpassword" } } + + expect(response).to be_redirect + expect(response).to redirect_to(new_session_path) + end + + it "shows an alert message" do + patch :update, params: { id: user.password_reset_token, password_reset: { password: "newpassword" } } + + expect(flash.notice).not_to be_nil + end + + it "resets the user password" do + expect { + patch :update, params: { id: user.password_reset_token, password_reset: { password: "newpassword" } } + }.to change { user.reload.password_digest } + end + + it "generates a new password reset token for the user" do + user = create(:user) + + expect { + patch :update, params: { id: user.password_reset_token, password_reset: { password: "foo" } } + }.to change { user.reload.password_reset_token } + end + + context "when no new password is set" do + it "responds successfully" do + patch :update, params: { id: user.password_reset_token, password_reset: { password: "" } } + + expect(response).to be_successful + end + + it "shows an alert message" do + patch :update, params: { id: user.password_reset_token, password_reset: { password: "" } } + + expect(flash.alert).not_to be_nil + end + end + end +end diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb new file mode 100644 index 0000000..f6f8db3 --- /dev/null +++ b/spec/controllers/photos_controller_spec.rb @@ -0,0 +1,85 @@ +require "rails_helper" + +RSpec.describe PhotosController, type: :controller do + render_views + + let(:current_user) { create(:user) } + let(:project) { create(:project, account: current_user.primary_account) } + let(:location_release) { create(:location_release, project: project) } + + before do + sign_in(current_user) + end + + shared_examples "a photoable releases controller" do + describe "#edit" do + it "responds successfully" do + get :edit, params: { "#{subject.model_name.param_key}_id" => subject } + + expect(response).to be_successful + end + end + + describe "#update" do + context "when releasable updated successfully" do + it "responds with a redirect" do + patch :update, params: { "#{subject.model_name.param_key}_id": subject, subject.model_name.param_key => release_params } + + expect(response).to be_redirect + expect(response).to redirect_to [project, subject.model_name.plural] + expect(flash.notice).to eq(t("photos.update.notice")) + end + + it "enqueues tagging job" do + expect { + patch :update, params: { "#{subject.model_name.param_key}_id": subject, subject.model_name.param_key => {} } + }.to have_enqueued_job(SetTagsForReleasableJob).with(subject.class.last) + end + end + end + + context "when releasable NOT updated successfully" do + before do + allow_any_instance_of(subject.class).to receive(:update).and_return(false) + end + + it "renders edit with flash message" do + patch :update, params: { "#{subject.model_name.param_key}_id": subject, subject.model_name.param_key => release_params } + + expect(controller).to have_rendered(:edit) + end + + it "does not enqueue tagging job" do + expect { + patch :update, params: { "#{subject.model_name.param_key}_id": subject, subject.model_name.param_key => release_params } + }.not_to have_enqueued_job(SetTagsForReleasableJob) + end + end + end + + context "for talent releases" do + subject { create(:talent_release, project: project) } + + it_behaves_like "a photoable releases controller" + end + + context "for location releases" do + subject { create(:location_release, project: project) } + + it_behaves_like "a photoable releases controller" + end + + context "for material releases" do + subject { create(:material_release, project: project) } + + it_behaves_like "a photoable releases controller" + end + + private + + def release_params + files = 2.times.map { Rack::Test::UploadedFile.new(file_fixture("location_photo.png"), "image/png") } + + { photos: files } + end +end diff --git a/spec/controllers/profiles_controller_spec.rb b/spec/controllers/profiles_controller_spec.rb new file mode 100644 index 0000000..7acad1b --- /dev/null +++ b/spec/controllers/profiles_controller_spec.rb @@ -0,0 +1,85 @@ +require "rails_helper" + +RSpec.describe ProfilesController, type: :controller do + render_views + + let(:current_user) { create(:user) } + + before do + sign_in current_user + end + + describe "#show" do + it "responds successfully" do + get :show + + expect(response).to be_successful + end + + it "renders content" do + get :show + + expect(response.body).to have_content(current_user.primary_account.name) + expect(response.body).to have_content "Account Manager" + end + + it "renders form for updating first, last name and time zone" do + get :show + + expect(response.body).to have_content("First name") + expect(response.body).to have_content("Last name") + expect(response.body).to have_content("Time zone") + end + end + + describe "#update" do + it "responds with redirect" do + patch :update, params: { user: user_params } + + expect(response).to be_redirect + expect(flash.notice).not_to be_nil + end + + it "updates user's first and last name" do + patch :update, params: { user: user_params } + + expect(current_user.first_name).to eq("John") + expect(current_user.last_name).to eq("Doe") + expect(current_user.full_name).to eq("John Doe") + expect(response).to be_redirect + expect(flash.notice).not_to be_nil + end + + it "updates user's avatar" do + patch :update, params: { user: user_params_with_avatar } + + expect(current_user.first_name).to eq("John") + expect(current_user.last_name).to eq("Doe") + expect(current_user.full_name).to eq("John Doe") + expect(current_user.avatar.filename).to eq("person_photo.png") + expect(current_user.avatar.signed_id).not_to be_nil + expect(response).to be_redirect + expect(flash.notice).not_to be_nil + end + + it "updates user's time zone" do + patch :update, params: { user: user_params } + + expect(current_user.time_zone).to eq("Berlin") + expect(response).to be_redirect + expect(flash.notice).not_to be_nil + end + end + + private + + def user_params + { first_name: "John", last_name: "Doe", time_zone: "Berlin" } + end + + def user_params_with_avatar + avatar = Rack::Test::UploadedFile.new(file_fixture("person_photo.png"), "image/png") + + user_params.merge({ avatar: avatar }) + end +end diff --git a/spec/controllers/project_memberships_controller_spec.rb b/spec/controllers/project_memberships_controller_spec.rb new file mode 100644 index 0000000..d896447 --- /dev/null +++ b/spec/controllers/project_memberships_controller_spec.rb @@ -0,0 +1,223 @@ +require "rails_helper" + +RSpec.describe ProjectMembershipsController, type: :controller do + render_views + + let(:current_user) { create(:user, :manager) } + let!(:project) { create(:project, members: current_user, account: current_user.primary_account) } + + before do + sign_in current_user + end + + describe "#index" do + it "responds successfully" do + get :index, params: { project_id: project } + + expect(response).to be_successful + end + + it "renders content" do + project = create(:project, name: "My Project", account: current_user.primary_account, members: [ + current_user, + create(:user, :manager, email: "jane.doe@test.com", accounts: current_user.accounts), + create(:user, :associate, email: "john.doe@test.com", accounts: current_user.accounts), + ]) + + get :index, params: { project_id: project } + + expect(response.body).to have_content "Team" + expect(response.body).to have_content "3 Team Members" + expect(response.body).to have_content current_user.email + expect(response.body).to have_content "jane.doe@test.com" + expect(response.body).to have_content "Project Manager" + expect(response.body).to have_content "john.doe@test.com" + expect(response.body).to have_content "Associate" + end + + it "includes account managers in the team roster" do + project = create(:project, name: "My Project", account: current_user.primary_account, members: current_user) + account_manager = create(:user, :account_manager, accounts: current_user.accounts) + + get :index, params: { project_id: project } + + expect(response.body).to have_content "Team" + expect(response.body).to have_content "2 Team Members" + expect(response.body).to have_content current_user.email + expect(response.body).to have_content account_manager.email + end + + context "for an associate" do + let(:current_user) { create(:user, :associate) } + + it "does not render invite form" do + get :index, params: { project_id: project } + + expect(response.body).not_to have_content "Invite New Member" + expect(response.body).not_to have_field "project_membership[user_email]" + expect(response.body).not_to have_button "Send Invite" + end + + it "does not render remove links for any users" do + project.users << create(:user, :associate, email: "another.user@test.com", primary_account: current_user.primary_account) + + get :index, params: { project_id: project } + + expect(response.body).to have_content("another.user@test.com") + expect(response.body).not_to have_link("Remove") + end + end + + context "for a manager" do + let(:current_user) { create(:user, :manager) } + + it "renders invite form" do + get :index, params: { project_id: project } + + expect(response.body).to have_content "Invite New Member" + expect(response.body).to have_field "project_membership[user_email]" + expect(response.body).to have_button "Send Invite" + end + + it "renders a remove link for other users" do + my_project_membership = project.project_memberships.first + their_project_membership = create(:project_membership, + project: project, + user: create(:user, :associate, + email: "another.user@test.com", + primary_account: project.account)) + + get :index, params: { project_id: project } + + expect(response.body).to have_content("another.user@test.com") + expect(response.body).to have_link("Remove", href: project_membership_path(their_project_membership)) + expect(response.body).not_to have_link("Remove", href: project_membership_path(my_project_membership)) + end + end + end + + describe "#create" do + let!(:user) { create(:user, email: "user@test.com") } + + it "responds with redirect" do + post :create, params: { project_id: project, project_membership: { user_email: "user@test.com" } } + + expect(response).to be_redirect + expect(response).to redirect_to [project, :project_memberships] + expect(flash.notice).to be_present + end + + it "creates a new membership for the invited user" do + expect { + post :create, params: { project_id: project, project_membership: { user_email: "user@test.com" } } + }.to change(ProjectMembership, :count).by(1) + + membership = ProjectMembership.last + + expect(membership.project).to eq(project) + expect(membership.user).to eq(user) + end + + it "sends an email notification" do + post :create, params: { project_id: project, project_membership: { user_email: "user@test.com" } } + + assert_enqueued_email_with UserMailer, :project_invitation, args: [user, project, user_is_new: false] + end + + context "when invitation cannot be saved" do + before do + allow_any_instance_of(ProjectMembership).to receive(:save_and_update_account_membership).and_return(false) + end + + it "responds with redirect" do + post :create, params: { project_id: project, project_membership: { user_email: "user@test.com" } } + + expect(response).to be_redirect + expect(response).to redirect_to [project, :project_memberships] + expect(flash.alert).to be_present + end + + it "creates a new membership for the invited user" do + expect { + post :create, params: { project_id: project, project_membership: { user_email: "user@test.com" } } + }.not_to change(ProjectMembership, :count) + end + end + + context "when invited user does not exist yet" do + it "creates a new user record" do + expect { + post :create, params: { project_id: project, project_membership: { user_email: "new.user@test.com" } } + }.to change(User, :count) + + project_membership = ProjectMembership.last + user = User.last + + expect(project_membership.project).to eq(project) + expect(project_membership.user).to eq(user) + expect(user.email).to eq("new.user@test.com") + expect(user.accounts).to include(current_user.primary_account) + expect(user.associate?(current_user.primary_account)).to be_truthy + end + + it "sends an email to the new user" do + post :create, params: { project_id: project, project_membership: { user_email: "new.user@test.com" } } + + assert_enqueued_email_with UserMailer, :project_invitation, args: [User.last, project, user_is_new: true] + end + end + + context "when invited user does not have access to the account yet" do + let!(:existing_user) { create(:user, email: "existing.user@test.com") } + + it "creates a new account membership record" do + expect { + post :create, params: { project_id: project, project_membership: { user_email: "existing.user@test.com" } } + }.to change(AccountAuth, :count) + + account_auth = AccountAuth.last + project_membership = ProjectMembership.last + + expect(account_auth.user).to eq existing_user + expect(account_auth.account).to eq project.account + expect(account_auth.role).to eq "associate" + expect(project_membership.user).to eq existing_user + expect(project_membership.project).to eq project + end + + it "sends an email to the new user" do + post :create, params: { project_id: project, project_membership: { user_email: "new.user@test.com" } } + + assert_enqueued_email_with UserMailer, :project_invitation, args: [User.last, project, user_is_new: true] + end + end + + context "for an associate" do + let(:current_user) { create(:user, :associate) } + + it "raises exception" do + expect { + post :create, params: { project_id: project, project_membership: { user_email: "new.user@test.com" } } + }.to raise_error(Pundit::NotAuthorizedError) + end + end + end + + describe "#destroy" do + let(:project_membership) { current_user.project_memberships.first } + + it "responds with redirect" do + delete :destroy, params: { id: project_membership } + + expect(response).to be_redirect + expect(response).to redirect_to([project, :project_memberships]) + expect(flash.alert).to be_present + end + + it "destroys the project membership record" do + expect { + delete :destroy, params: { id: project_membership } + }.to change(ProjectMembership, :count).by(-1) + end + end +end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb new file mode 100644 index 0000000..2b3ba62 --- /dev/null +++ b/spec/controllers/projects_controller_spec.rb @@ -0,0 +1,307 @@ +require "rails_helper" + +RSpec.describe ProjectsController, type: :controller do + render_views + + let(:current_user) { create(:user, :account_manager) } + let(:user) { current_user } + let(:account) { user.primary_account } + let!(:project) { create(:project, name: "My Project", client_name: "My Client", members: current_user, account: current_user.primary_account) } + + before do + sign_in(current_user) + end + + describe "#index" do + let!(:project_one) { create(:project, name: "Avengers", members: current_user, account: current_user.primary_account) } + let!(:project_two) { create(:project, name: "Justice League", members: [], account: current_user.primary_account) } + + it "responds successfully" do + get :index + + expect(response).to be_successful + end + + it "redirects to accountless page if user is accountless" do + sign_in(create(:user, :accountless)) + + get :index + + expect(response).to redirect_to(accountless_user_path) + end + + context "for an associate" do + let(:current_user) { create(:user, :associate) } + + it "only shows projects that the user has been invited to" do + get :index + + expect(response.body).to have_content("Avengers") + expect(response.body).not_to have_content("Justice League") + end + end + + context "for a project manager" do + let(:current_user) { create(:user, :manager) } + + it "only shows projects that the user has been invited to" do + get :index + + expect(response.body).to have_content("Avengers") + expect(response.body).not_to have_content("Justice League") + end + end + + context "for an account manager" do + let(:current_user) { create(:user, :account_manager) } + + it "shows all projects" do + get :index + + expect(response.body).to have_content("Avengers") + expect(response.body).to have_content("Justice League") + end + end + end + + describe "#new" do + it "sets project" do + get :new + expect(response).to be_successful + expect(assigns(:project)).to have_attributes(account: current_user.primary_account) + end + + it "renders project content" do + get :index + + expect(response.body).to have_link("Create New Project", href: new_project_path) + expect(response.body).to have_link("My Project", href: project_path(project)) + expect(response.body).to have_content("My Client") + expect(response.body).to have_link(href: project_project_memberships_path(project)) + end + + context "when there are no projects" do + it "renders prompt to create new one" do + Project.destroy_all + + get :index + + expect(response.body).to have_link("Create Your First Project", href: new_project_path) + end + end + + context "for an associate" do + let(:current_user) { create(:user, :associate) } + + it "renders doesn't prompt to create new one" do + current_user.project_memberships.delete_all + + get :index + + expect(response.body).to have_content("Click on a project you are invited to and get started.") + expect(response.body).not_to have_content("Create New Project") + end + end + end + + describe "#show" do + it "responds successfully" do + get :show, params: { id: project } + + expect(response).to be_successful + end + + it "renders content" do + create(:talent_release, project: project) + create(:appearance_release, project: project) + create(:location_release, project: project) + create(:acquired_media_release, project: project) + create(:material_release, project: project) + create(:music_release, project: project) + + get :show, params: { id: project } + + expect(response.body).to have_content("My Project") + expect(response.body).to have_content("My Client") + expect(response.body).to have_link("Files", href: project_path(project)) + expect(response.body).to have_link("Release", href: project_contract_templates_path(project)) + expect(response.body).to have_link("Edit", href: "#") + expect(response.body).to have_link("Deliver", href: project_videos_path(project)) + expect(response.body).to have_content("1 Team Member") + expect(response.body).to have_link("Talent Releases (1)", href: project_talent_releases_path(project)) + expect(response.body).to have_link("Appearance Releases (1)", href: project_appearance_releases_path(project)) + expect(response.body).to have_link("Location Releases (1)", href: project_location_releases_path(project)) + expect(response.body).to have_link("Acquired Media Releases (1)", href: project_acquired_media_releases_path(project)) + expect(response.body).to have_link("Material Releases (1)", href: project_material_releases_path(project)) + expect(response.body).to have_link("Music Releases (1)", href: project_music_releases_path(project)) + expect(response.body).to have_link("Reports", href: project_reports_path(project)) + expect(response.body).to have_link(href: project_project_memberships_path(project)) + end + + context "for associates" do + let(:current_user) { create(:user, :associate) } + let!(:project) { create(:project_with_directories, name: "My Project", client_name: "My Client", members: current_user, account: current_user.primary_account) } + + it "only returns directories with 'Everyone' permissons" do + get :show, params: { id: project } + + expect(response.body).to have_content("Shared") + expect(response.body).not_to have_content("Financial Documents") + expect(response.body).not_to have_content("Salaries") + end + + it "does not show reports folder" do + get :show, params: { id: project } + + expect(response.body).not_to have_content("Reports") + end + end + + context "for project managers" do + let(:current_user) { create(:user, :manager) } + let!(:project) { create(:project_with_directories, name: "My Project", client_name: "My Client", members: current_user, account: current_user.primary_account) } + + it "only returns directories with 'Everyone' & 'Account Managers & Project Managers' permissons" do + get :show, params: { id: project } + + expect(response.body).to have_content("Shared") + expect(response.body).to have_content("Financial Documents") + expect(response.body).not_to have_content("Salaries") + end + end + + context "for account managers" do + let(:current_user) { create(:user, :account_manager) } + let!(:project) { create(:project_with_directories, name: "My Project", client_name: "My Client", account: current_user.primary_account) } + + it "returns all directories" do + get :show, params: { id: project } + + expect(response.body).to have_content("Shared") + expect(response.body).to have_content("Financial Documents") + expect(response.body).to have_content("Salaries") + end + end + end + + describe "#new" do + it "sets project" do + get :new + + expect(response).to be_successful + end + end + + describe "#create" do + it "responds with redirect" do + post :create, params: { project: project_params } + + expect(response).to be_redirect + expect(flash.notice).not_to be_nil + end + + it "creates a new record" do + expect { + post :create, params: { project: project_params } + }.to change(Project, :count).by(1) + end + + it "logs an event" do + expect { + post :create, params: { project: project_params } + }.to have_enqueued_job(TrackAnalyticsJob).with(user, account, :track_create_project, user_agent: "Rails Testing", user_ip: "0.0.0.0") + end + + context "with invalid data" do + before do + allow_any_instance_of(Project).to receive(:save).and_return(false) + end + + it "shows the video analysis status and button" do + project = create(:project, account: current_user.primary_account, videos: create_list(:video, 1)) + + expect(response).to be_successful + end + + it "does not create a new record" do + expect { + post :create, params: { project: project_params } + }.not_to change(Project, :count) + end + end + + context "for associates" do + let(:current_user) { create(:user, :associate) } + + it "raises exception" do + expect { + post :create, params: { project: project_params } + }.to raise_error(Pundit::NotAuthorizedError) + end + end + end + + describe "#edit" do + it "sets project, available_release_types" do + get :edit, params: { id: project } + + expect(response).to be_successful + expect(assigns(:project)).to eq project + end + end + + describe "#update" do + it "responds with redirect" do + patch :update, params: { id: project, project: project_params } + + expect(response).to be_redirect + expect(flash.notice).not_to be_nil + end + + context "for associates" do + let(:current_user) { create(:user, :associate) } + + it "raises exception" do + expect { + patch :update, params: { id: project, project: project_params } + }.to raise_error(Pundit::NotAuthorizedError) + end + end + end + + describe "#destroy" do + it "responds with redirect" do + skip "This functionality is no longer available" + + delete :destroy, params: { id: project } + + expect(response).to be_redirect + expect(response).to redirect_to(signed_in_root_path) + expect(flash.alert).not_to be_nil + end + + it "destroys the record" do + skip "This functionality is no longer available" + + expect { + delete :destroy, params: { id: project } + }.to change(Project, :count).by(-1) + end + + context "for associates" do + let(:current_user) { create(:user, :associate) } + + it "raises exception" do + expect { + delete :destroy, params: { id: project } + }.to raise_error(Pundit::NotAuthorizedError) + end + end + end + + private + + def project_params + attributes_for(:project) + end +end diff --git a/spec/controllers/public/acquired_media_releases_controller_spec.rb b/spec/controllers/public/acquired_media_releases_controller_spec.rb new file mode 100644 index 0000000..eb7e9a9 --- /dev/null +++ b/spec/controllers/public/acquired_media_releases_controller_spec.rb @@ -0,0 +1,74 @@ +require "rails_helper" + +RSpec.describe Public::AcquiredMediaReleasesController, type: :controller do + let(:user) { create(:user) } + let(:project) { create(:project, account: user.primary_account) } + + render_views + + describe "#create" do + it "logs analytics" do + contract_template = create(:contract_template, project: project) + + expect { + post :create, params: { account_id: project.account.to_param, project_id: project, contract_template_id: contract_template, acquired_media_release: acquired_media_release_params } + }.to( + have_enqueued_job(TrackAnalyticsJob) + .with(nil, nil, :track_create_native_release, release_type: "AcquiredMediaRelease", account: project.account, user_agent: "Rails Testing", user_ip: "0.0.0.0") + ) + end + + it "displays validation errors" do + contract_template = create(:contract_template, project: project) + sign_in(user) + + post :create, params: { account_id: user.primary_account.to_param, project_id: project, contract_template_id: contract_template, acquired_media_release: { name: "" } } + body = CGI.unescape_html(response.body) + expect(body).to match /Name can't be blank/ + expect(body).to match />can't be blankcan't be blankcan't be blankcan't be blankcan't be blank releasable } + end + end + + describe "#create" do + it "responds successfully" do + post :create, xhr: true, params: { "#{releasable.model_name.singular}_id" => releasable, acts_as_taggable_on_tag: tag_params } + + expect(response).to be_successful + end + + it "creates a new tag" do + expect { + post :create, xhr: true, params: { "#{releasable.model_name.singular}_id" => releasable, acts_as_taggable_on_tag: tag_params } + }.to change(releasable.taggings, :count).by(1) + end + end + + private + + def tag_params + { name: "Fresh" } + end +end diff --git a/spec/controllers/talent_releases_controller_spec.rb b/spec/controllers/talent_releases_controller_spec.rb new file mode 100644 index 0000000..2b3b685 --- /dev/null +++ b/spec/controllers/talent_releases_controller_spec.rb @@ -0,0 +1,239 @@ +require "rails_helper" + +RSpec.describe TalentReleasesController, type: :controller do + render_views + + let(:user) { create(:user) } + let(:account) { user.primary_account } + let(:project) { create(:project, account: user.primary_account) } + + before do + sign_in user + end + + describe "#index" do + it "responds successfully" do + get :index, params: { project_id: project } + + expect(response).to be_successful + end + + it "renders content" do + release = create(:talent_release, project: project, + person_first_name: "My", + person_last_name: "Release", + person_phone: "5551234567", + person_email: "jane.doe@test.com") + create(:note, notable: release, content: "Some notes here") + + get :index, params: { project_id: project } + + expect(response.body).to have_content "My Release" + expect(response.body).to have_content "555-123-4567" + expect(response.body).to have_content "jane.doe@test.com" + expect(response.body).to have_content "Some notes here" + expect(response.body).to have_link "Import Release" + expect(response.body).to have_content "Manage" + end + + context "when there are no talent releases" do + it "renders an empty message" do + get :index, params: { project_id: project } + + expect(response.body).to have_content("Talent Releases will appear here") + end + end + + context "when there are many records" do + it "paginates the table" do + create_list(:talent_release, 20, project: project) + + get :index, params: { project_id: project } + + expect(response.body).to have_link("2", href: project_talent_releases_path(project, page: 2)) + end + end + + context "for xhr request" do + it "filters the releases by a query param" do + talent_releases = [ + create(:talent_release, person_name: "Adam Sandler", project: project), + create(:talent_release, person_name: "Zoe Perry", project: project), + ] + + get :index, params: { project_id: project, query: "Zoe" }, xhr: true + + expect(response.body).not_to have_content("Adam Sandler") + expect(response.body).to have_content("Zoe Perry") + end + end + end + + describe "#new" do + it "responds successfully" do + get :new, params: { project_id: project } + + expect(response).to be_successful + end + end + + describe "#create" do + it "responds successfully when guardian photo is not sent" do + expect { + post :create, params: { project_id: project, talent_release: talent_release_params } + }.to change(TalentRelease, :count).by(1) + + expect(response).to be_redirect + expect(response).to redirect_to [project, :talent_releases] + expect(flash.notice).to be_present + end + + it "responds successfully when guardian photo is sent" do + expect { + post :create, params: { project_id: project, talent_release: minor_talent_release_params } + }.to change(TalentRelease, :count).by(1) + + expect(response).to be_redirect + expect(response).to redirect_to [project, :talent_releases] + expect(flash.notice).to be_present + end + + it "enqueues headshot collection job" do + expect { + post :create, params: { project_id: project, talent_release: talent_release_params } + }.to have_enqueued_job(AddHeadshotCollectionUidToProjectJob).with(project) + end + + it "enqueues tagging job" do + expect { + post :create, params: { project_id: project, talent_release: talent_release_params } + }.to have_enqueued_job(SetTagsForReleasableJob).with(TalentRelease.last) + end + + it "logs analytics" do + expect { + post :create, params: { project_id: project, talent_release: talent_release_params } + }.to( + have_enqueued_job(TrackAnalyticsJob).with(user, account, :track_create_non_native_release, release_type: "TalentRelease", user_agent: "Rails Testing", user_ip: "0.0.0.0") + ) + end + + context "when the record would be invalid" do + before do + allow_any_instance_of(TalentRelease).to receive(:save).and_return(false) + end + + it "re-renders the form" do + expect { + post :create, params: { project_id: project, talent_release: talent_release_params } + }.not_to change(TalentRelease, :count) + + expect(response).to be_successful + expect(flash.notice).to be_nil + end + + it "does not enqueue any jobs" do + expect { + post :create, params: { project_id: project, talent_release: talent_release_params } + }.not_to have_enqueued_job + end + end + end + + describe "#edit" do + let(:non_native_talent_release) { create(:talent_release, project: project) } + let(:native_talent_release) { create(:talent_release, :native, project: project) } + + it "responds successfully for non-native release" do + get :edit, params: { project_id: project, id: non_native_talent_release } + + expect(response).to be_successful + end + + it "responds with error for native release" do + expect{get :edit, params: { id: native_talent_release }}.to raise_exception Pundit::NotAuthorizedError + end + end + + describe "#update" do + let(:talent_release) { create(:talent_release, project: project) } + + it "responds successfully" do + patch :update, params: { project_id: project, id: talent_release, talent_release: talent_release_params } + + expect(response).to redirect_to [project, :talent_releases] + expect(flash.notice).to be_present + end + + it "enqueues headshot collection job" do + expect { + patch :update, params: { project_id: project, id: talent_release, talent_release: talent_release_params } + }.to have_enqueued_job(AddHeadshotCollectionUidToProjectJob).with(project) + end + + context "when the record would be invalid" do + before do + allow_any_instance_of(TalentRelease).to receive(:save).and_return(false) + end + + it "re-renders the form" do + patch :update, params: { project_id: project, id: talent_release, talent_release: talent_release_params } + + expect(response).to be_successful + expect(flash.notice).to be_nil + end + + it "does not enqueue any jobs" do + expect { + patch :update, params: { project_id: project, id: talent_release, talent_release: talent_release_params } + }.not_to have_enqueued_job(AddHeadshotCollectionUidToProjectJob) + end + end + end + + describe "#destroy" do + let!(:talent_release) { create(:talent_release, project: project) } + + it "responds with redirect" do + delete :destroy, params: { project_id: project, id: talent_release } + + expect(response).to be_redirect + expect(response).to redirect_to [project, :talent_releases] + end + + it "sets the flash" do + delete :destroy, params: { project_id: project, id: talent_release } + + expect(flash.alert).not_to be_nil + end + + it "destroys the record" do + expect { + delete :destroy, params: { project_id: project, id: talent_release } + }.to change(TalentRelease, :count).by(-1) + end + end + + private + + def talent_release_params + attributes_for(:talent_release).merge(exploitable_rights_params) + end + + def minor_talent_release_params + attributes_for(:talent_release, :minor_with_guardian_photo).merge(exploitable_rights_params) + end + + def exploitable_rights_params + { + applicable_medium_id: ApplicableMedium.last.id, + applicable_medium_text: "applicable_media", + territory_id: Territory.last.id, + territory_text: "territory", + term_id: Term.last.id, + term_text: "term", + restriction_id: Restriction.last.id, + restriction_text: "restrictions", + } + end +end diff --git a/spec/controllers/unreleased_appearances_controller_spec.rb b/spec/controllers/unreleased_appearances_controller_spec.rb new file mode 100644 index 0000000..20e9d7a --- /dev/null +++ b/spec/controllers/unreleased_appearances_controller_spec.rb @@ -0,0 +1,203 @@ +require "rails_helper" + +RSpec.describe VideoAnalyses::UnreleasedAppearancesController, type: :controller do + let(:user) { create(:user) } + let(:project) { create(:project, account: user.primary_account) } + let!(:video) { create(:video, project: project) } + + before :each do + sign_in user + end + + render_views + + describe "#new" do + let(:edl_event_gateway) { instance_double(EdlEventGateway) } + + before :each do + allow(EdlEventGateway).to receive(:new).and_return(edl_event_gateway) + allow(edl_event_gateway).to receive(:edl_events).and_return(build_list(:edl_event, 1)) + end + + it "sets unreleased_appearance, edl_events_data" do + get :new, params: { + unreleased_appearance: { time_elapsed: "1", notes: "great work" }, + video_id: video, + }, xhr: true + + expect(response).to be_successful + expect(assigns(:unreleased_appearance)).to have_attributes({ + video_id: video.id, + time_elapsed: "1", + notes: "great work", + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + }) + expect(assigns(:edl_events_data)).to eq({ + edl_events: build_list(:edl_event, 1), + edl_attributes: { + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + }, + info_message: "An EDL event was found. Data is shown below", + }) + end + end + + describe "#create" do + it "creates unreleased_appearance" do + expect { + post :create, params: { + unreleased_appearance: { + time_elapsed: "1", + notes: "great work", + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + note_category: "other", + }, + video_id: video, + }, xhr: true + }.to change(UnreleasedAppearance, :count).from(0).to(1) + end + + it "sets unreleased_appearances_data" do + post :create, params: { + unreleased_appearance: { + time_elapsed: "1", + notes: "great work", + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + note_category: "other", + }, + video_id: video, + }, xhr: true + + expect(assigns(:unreleased_appearances_data).unreleased_appearances.first).to have_attributes( + notes: "great work", + time_elapsed: "1", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + note_category: "other", + ) + end + end + + describe "#edit" do + let(:edl_gateway) { double(:edl_gateway) } + + before :each do + allow(EdlEventGateway).to receive(:new).and_return(edl_gateway) + allow(edl_gateway).to receive(:edl_events).and_return(build_list(:edl_event, 1)) + end + + it "sets unreleased_appearance, edl_events_data" do + unreleased_appearance = create(:unreleased_appearance, video: video) + + get :edit, params: { id: unreleased_appearance }, xhr: true + + expect(EdlEventGateway).to have_received(:new).with( + instance_of(FilesForRequest), + "00:00:01:13", + "00:00:01:13", + ) + + expect(response).to be_successful + expect(assigns(:unreleased_appearance)).to eq unreleased_appearance + expect(assigns(:edl_events_data)).to eq({ + edl_events: build_list(:edl_event, 1), + edl_attributes: { + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + }, + info_message: "An EDL event was found. Data is shown below", + }) + end + end + + describe "#update" do + let(:edl_gateway) { double(:edl_gateway) } + + before :each do + allow(EdlEventGateway).to receive(:new).and_return(edl_gateway) + allow(edl_gateway).to receive(:edl_events).and_return(build_list(:edl_event, 1)) + end + + it "sets unreleased_appearances_data" do + unreleased_appearance = create(:unreleased_appearance, video: video) + + put :update, params: { + id: unreleased_appearance, + unreleased_appearance: { + notes: "notes", + time_elapsed: "time_elapsed", + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + note_category: "other", + } + }, xhr: true + + expect(response).to be_successful + expect(assigns(:unreleased_appearances_data).unreleased_appearances.first).to have_attributes( + notes: "notes", + time_elapsed: "time_elapsed", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + note_category: "other", + ) + end + end + + describe "#destroy" do + let!(:unreleased_appearance) { create(:unreleased_appearance) } + + it "sets unreleased_appearances_data" do + delete :destroy, params: { id: unreleased_appearance.id }, xhr: true + + expect(assigns(:unreleased_appearances_data).unreleased_appearances).to eq([]) + end + + it "destroys given unreleased_appearance" do + expect { + delete :destroy, params: { id: unreleased_appearance.id }, xhr: true + }.to change(UnreleasedAppearance, :count).from(1).to(0) + end + end +end diff --git a/spec/controllers/video_analyses/acquired_media_releases_controller_spec.rb b/spec/controllers/video_analyses/acquired_media_releases_controller_spec.rb new file mode 100644 index 0000000..fc195a7 --- /dev/null +++ b/spec/controllers/video_analyses/acquired_media_releases_controller_spec.rb @@ -0,0 +1,19 @@ +require "rails_helper" + +describe VideoAnalyses::AcquiredMediaReleasesController do + let(:current_user) { create(:user) } + let(:project) { create(:project, account: current_user.primary_account) } + let(:video) { create(:video, project: project) } + + before do + sign_in(current_user) + end + + describe "#index" do + it "responds with success" do + get :index, params: { video_id: video }, xhr: true + + expect(response).to be_successful + end + end +end diff --git a/spec/controllers/video_analyses/edl_events_controller_spec.rb b/spec/controllers/video_analyses/edl_events_controller_spec.rb new file mode 100644 index 0000000..eef4838 --- /dev/null +++ b/spec/controllers/video_analyses/edl_events_controller_spec.rb @@ -0,0 +1,116 @@ +require "rails_helper" + +describe VideoAnalyses::EdlEventsController do + let(:user) { create(:user) } + let(:project) { create(:project, account: user.primary_account) } + let(:video) { create(:video, project: project) } + + before :each do + sign_in(user) + end + + describe "#create" do + context "when 1 EDL event" do + before :each do + allow(EdlEventGateway).to receive(:new).and_return( + double("gateway", edl_events: [ + EdlEvent.new( + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + ) + ]) + ) + end + + it "sets timecode, edl_events, info_message" do + post :create, xhr: true, params: { video_id: video, edl_event: { time_elapsed: 2.5 } } + + expect(response).to be_successful + expect(assigns(:timecode)).to eq(Timecode.from_seconds(2.5).to_s) + expect(assigns(:edl_events)).to eq([ + EdlEvent.new( + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + ) + ]) + expect(assigns(:info_message)).to eq "One EDL event for this timecode was found and is shown below" + end + end + + context "when no EDL events" do + before :each do + allow(EdlEventGateway).to receive(:new).and_return( + double("gateway", edl_events: []) + ) + end + + it "sets timecode, edl_events, info_message" do + post :create, xhr: true, params: { video_id: video, edl_event: { time_elapsed: 2.5 } } + + expect(response).to be_successful + expect(assigns(:timecode)).to eq(Timecode.from_seconds(2.5).to_s) + expect(assigns(:edl_events)).to eq([]) + expect(assigns(:info_message)).to eq "No EDL events for this timecode were found" + end + end + + context "when multiple EDL events" do + before :each do + allow(EdlEventGateway).to receive(:new).and_return( + double("gateway", edl_events: [ + EdlEvent.new( + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + ), + EdlEvent.new( + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + ), + ]) + ) + end + + it "sets timecode, edl_events, info_message" do + post :create, xhr: true, params: { video_id: video, edl_event: { time_elapsed: 2.5 } } + + expect(response).to be_successful + expect(assigns(:timecode)).to eq(Timecode.from_seconds(2.5).to_s) + expect(assigns(:edl_events)).to eq([ + EdlEvent.new( + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + ), + EdlEvent.new( + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + ), + ]) + expect(assigns(:info_message)).to eq "Multiple EDL events for this timecode were found and are shown below" + end + end + end +end diff --git a/spec/controllers/video_analyses/graphics_elements_controller_spec.rb b/spec/controllers/video_analyses/graphics_elements_controller_spec.rb new file mode 100644 index 0000000..b74148b --- /dev/null +++ b/spec/controllers/video_analyses/graphics_elements_controller_spec.rb @@ -0,0 +1,289 @@ +require "rails_helper" + +RSpec.describe VideoAnalyses::GraphicsElementsController, type: :controller do + let(:user) { create(:user) } + let(:project) { create(:project, account: user.primary_account) } + let(:video) { create(:video, project: project) } + + before :each do + sign_in(user) + end + + render_views + + describe "#new" do + let(:edl_gateway) { double(:edl_gateway) } + + before :each do + allow(EdlEventGateway).to receive(:new).and_return(edl_gateway) + allow(edl_gateway).to receive(:edl_events).and_return(build_list(:edl_event, 1)) + end + + it "responds successfully and sets graphics_element, edl_events_data, matched_filename" do + get :new, params: { + video_id: video.id, + graphics_element: { + text: "asdf", + time_elapsed: "00:01:00", + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + edl_type: "edl_type", + }, + matched_file_name: "matched_file_name", + }, xhr: true + + expect(EdlEventGateway).to have_received(:new).with( + instance_of(GraphicsFilesForRequest), + "00:00:00:00", + "00:00:00:00", + channel_filter: "V", + ) + + expect(response).to be_successful + expect(assigns(:graphics_element)).to have_attributes( + id: nil, + text: "asdf", + time_elapsed: "00:01:00", + clip_name: "clip_name", + description: "description", + duration: "duration", + source_file_name: "source_file_name", + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + edl_type: "edl_type", + ) + expect(assigns(:edl_events_data)).to eq({ + edl_events: build_list(:edl_event, 1), + edl_attributes: { + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + }, + info_message: "An EDL event was found. Data is shown below", + }) + expect(assigns(:matched_file_name)).to eq "matched_file_name" + end + + context "when edl_type param is all_tracks" do + it "uses the all tracks edl" do + get :new, params: { + video_id: video.id, + graphics_element: { + text: "asdf", + time_elapsed: "00:01:00", + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + edl_type: "all_tracks", + }, + matched_file_name: "matched_file_name", + }, xhr: true + + expect(EdlEventGateway).to have_received(:new).with( + instance_of(FilesForRequest), + "00:00:00:00", + "00:00:00:00", + channel_filter: "V", + ) + end + end + end + + describe "#create" do + it "responds successfully and creates graphics_element" do + expect { + post :create, params: { + video_id: video.id, + graphics_element: { + graphic_type: "Subtitle", + text: "asdf", + time_elapsed: "00:01:00", + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + edl_type: "edl_type", + }, + }, xhr: true + }.to change(GraphicsElement, :count).by(1) + + expect(response).to be_successful + end + + it "sets graphics_element_data, graphics_elements_data" do + post :create, params: { + video_id: video.id, + graphics_element: { + graphic_type: "Subtitle", + text: "asdf", + time_elapsed: "00:01:00", + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + edl_type: "edl_type", + }, + }, xhr: true + + expect(assigns(:graphics_element_data)).to have_attributes( + source_file_name: "source_file_name", + timecode_in: "timecode_in", + should_toggle_checkmark: true, + is_valid: true, + ) + expect(assigns(:graphics_elements_data)).to have_attributes( + graphics_elements: [GraphicsElement.last] + ) + end + end + + describe "#edit" do + let(:edl_gateway) { double(:edl_gateway) } + + before :each do + allow(EdlEventGateway).to receive(:new).and_return(edl_gateway) + allow(edl_gateway).to receive(:edl_events).and_return(build_list(:edl_event, 1)) + end + + it "sets graphics_element, edl_events_data" do + graphics_element = create(:graphics_element, video: video) + + get :edit, params: { id: graphics_element }, xhr: true + + expect(EdlEventGateway).to have_received(:new).with( + instance_of(GraphicsFilesForRequest), + "00:00:05:00", + "00:00:05:00", + channel_filter: "V", + ) + + expect(response).to be_successful + expect(assigns(:graphics_element)).to eq graphics_element + expect(assigns(:edl_events_data)).to eq({ + edl_events: build_list(:edl_event, 1), + edl_attributes: { + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + }, + info_message: "An EDL event was found. Data is shown below", + }) + end + + context "when edl_type param is all_tracks" do + it "uses the all tracks edl" do + graphics_element = create(:graphics_element, video: video, edl_type: "all_tracks") + + get :edit, params: { id: graphics_element }, xhr: true + + expect(EdlEventGateway).to have_received(:new).with( + instance_of(FilesForRequest), + "00:00:05:00", + "00:00:05:00", + channel_filter: "V", + ) + end + end + end + + describe "#update" do + let(:edl_gateway) { double(:edl_gateway) } + + before :each do + allow(EdlEventGateway).to receive(:new).and_return(edl_gateway) + allow(edl_gateway).to receive(:edl_events).and_return(build_list(:edl_event, 1)) + end + + it "sets graphics_element_data, graphics_elements_data" do + graphics_element = create(:graphics_element, video: video) + + put :update, params: { + id: graphics_element, + graphics_element: { + graphic_type: "Subtitle", + text: "text", + time_elapsed: "time_elapsed", + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + } + }, xhr: true + + expect(response).to be_successful + expect(assigns(:graphics_element_data)).to have_attributes( + source_file_name: "source_file_name", + timecode_in: "timecode_in", + should_toggle_checkmark: true, + is_valid: true, + ) + expect(assigns(:graphics_elements_data).graphics_elements.first).to have_attributes( + text: "text", + time_elapsed: "time_elapsed", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + ) + end + end + + describe "#delete" do + it "deletes given graphics_element" do + graphics_element = create(:graphics_element) + + expect { + delete :destroy, params: { id: graphics_element }, xhr: true }.to change { GraphicsElement.count + }.from(1).to(0) + end + + it "responds with graphics element data, graphics elements data" do + graphics_element = create(:graphics_element, + video: video, + source_file_name: "source_file_name", + timecode_in: "timecode_in", + ) + + delete :destroy, params: { id: graphics_element }, xhr: true + + expect(assigns(:graphics_element_data)).to have_attributes( + source_file_name: "source_file_name", + timecode_in: "timecode_in", + should_toggle_checkmark: true, + is_valid: true, + ) + expect(assigns(:graphics_elements_data)).to have_attributes( + graphics_elements: [] + ) + end + end +end diff --git a/spec/controllers/video_analyses/talent_releases_controller_spec.rb b/spec/controllers/video_analyses/talent_releases_controller_spec.rb new file mode 100644 index 0000000..0bcf546 --- /dev/null +++ b/spec/controllers/video_analyses/talent_releases_controller_spec.rb @@ -0,0 +1,20 @@ +require "rails_helper" + +describe VideoAnalyses::TalentReleasesController do + let(:current_user) { create(:user) } + let(:project) { create(:project, account: current_user.primary_account) } + let(:video) { create(:video, project: project) } + + before do + sign_in(current_user) + end + + + describe "#index" do + it "responds with success" do + get :index, params: { video_id: video }, xhr: true + + expect(response).to be_successful + end + end +end diff --git a/spec/controllers/video_analyses_controller_spec.rb b/spec/controllers/video_analyses_controller_spec.rb new file mode 100644 index 0000000..b806289 --- /dev/null +++ b/spec/controllers/video_analyses_controller_spec.rb @@ -0,0 +1,106 @@ +require "rails_helper" + +RSpec.describe VideoAnalysesController do + let(:admin) { create(:user, :admin) } + let(:project) { create(:project, account: admin.accounts.first) } + let(:video) { create(:video, project: project) } + + before do + allow(AnalyzeVideoJob).to receive(:perform_later) + allow(BrayniacAI::EdlParse).to receive(:create) + sign_in admin + end + + render_views + + describe "#create" do + it "redirects to video analyses" do + post :create, params: { video_id: video, use_route: video_video_analyses_path(video) } + + expect(response).to redirect_to(video_video_analyses_path(video)) + end + + it "reanalyzes the video" do + expect(AnalyzeVideoJob).to receive(:perform_later).with(video, reanalysis: true) + + post :create, params: { video_id: video, use_route: video_video_analyses_path(video) } + end + + it "reanalyzes the audio" do + expect(AnalyzeAudioJob).to receive(:perform_later).with(video) + + post :create, params: { video_id: video, use_route: video_video_analyses_path(video) } + end + end + + describe "#show" do + it "sets video_release_confirmations, talent_releases, appearance_releases, location_releases, acquired_media_releases, music_releases, material_releases, graphics_elements_data, audio_confirmations" do + talent_releases = [create(:talent_release)] + appearance_releases = [create(:appearance_release)] + location_releases = [create(:location_release)] + acquired_media_releases = [create(:acquired_media_release)] + music_releases = [create(:music_release)] + material_releases = [create(:material_release)] + + project = create(:project, + name: "All the releases", + account: admin.accounts.first, + talent_releases: talent_releases, + appearance_releases: appearance_releases, + location_releases: location_releases, + acquired_media_releases: acquired_media_releases, + music_releases: music_releases, + material_releases: material_releases, + ) + video = create(:video, + project: project, + video_release_confirmations: [ + create(:video_release_confirmation, releasable: talent_releases.first), + ] + ) + create(:audio_confirmation, video: video) + + get :show, params: { video_id: video, use_route: video_video_analyses_path(video) } + + expect(assigns(:video_release_confirmations)).to eq [VideoReleaseConfirmation.first] + expect(assigns(:talent_releases)).to eq talent_releases + expect(assigns(:appearance_releases)).to eq appearance_releases + expect(assigns(:location_releases)).to eq location_releases + expect(assigns(:acquired_media_releases)).to eq acquired_media_releases + expect(assigns(:music_releases)).to eq music_releases + expect(assigns(:material_releases)).to eq material_releases + expect(assigns(:video_analysis_presenter)).to be_an_instance_of(VideoAnalysisPresenter) + expect(assigns(:graphics_elements_data)).to have_attributes(graphics_elements: []) + expect(assigns(:unreleased_appearances_data)).to have_attributes(unreleased_appearances: []) + expect(assigns(:audio_confirmations)).to eq [AudioConfirmation.last] + end + + it "sorts confirmations by appears_at" do + talent_releases = [create(:talent_release), create(:talent_release), create(:talent_release)] + + project = create(:project, + name: "All the releases", + account: admin.accounts.first, + talent_releases: talent_releases, + ) + video = create(:video, + project: project, + video_release_confirmations: [ + create(:video_release_confirmation, releasable: talent_releases.first, time_elapsed: "10"), + create(:video_release_confirmation, releasable: talent_releases.second, time_elapsed: "50"), + create(:video_release_confirmation, releasable: talent_releases.last, time_elapsed: "30"), + ] + ) + + get :show, params: { video_id: video, use_route: video_video_analyses_path(video) } + + body = response.body + first = body =~ /00:00:10:00/ + second = body =~ /00:00:30:00/ + last = body =~ /00:00:50:00/ + + expect(first < second).to eq true + expect(second < last).to eq true + end + end +end diff --git a/spec/controllers/video_release_confirmations_controller_spec.rb b/spec/controllers/video_release_confirmations_controller_spec.rb new file mode 100644 index 0000000..49cb2dd --- /dev/null +++ b/spec/controllers/video_release_confirmations_controller_spec.rb @@ -0,0 +1,321 @@ +require "rails_helper" + +RSpec.describe VideoReleaseConfirmationsController, type: :controller do + let(:admin) { create(:user, :admin) } + let(:project) { create(:project, account: admin.accounts.first) } + let(:video) { create(:video, project: project) } + + before :each do + sign_in admin + end + + describe "acquired_media_release" do + let(:acquired_media_release) { create(:acquired_media_release) } + + describe "#new" do + let(:edl_event_gateway) { instance_double(EdlEventGateway) } + + before :each do + allow(EdlEventGateway).to receive(:new).and_return(edl_event_gateway) + allow(edl_event_gateway).to receive(:edl_events).and_return(build_list(:edl_event, 1)) + end + + it "assigns video_release_confirmation, video, edl_events_data" do + file_info = create(:file_info) + + post :new, + params: { + video_id: video, + acquired_media_release_id: acquired_media_release, + video_release_confirmation: { + time_elapsed: nil, + timecode_in: nil, + timecode_out: nil, + duration: nil, + source_file_name: nil, + clip_name: nil, + description: nil, + file_info_id: file_info, + }, + use_route: new_video_acquired_media_release_video_release_confirmation_path(video, acquired_media_release) + }, + xhr: true + + expect(assigns(:video_release_confirmation)).to have_attributes({ + video_id: video.id, + releasable_id: acquired_media_release.id, + time_elapsed: "", + releasable_type: "AcquiredMediaRelease", + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + file_info_id: file_info.id, + }) + expect(assigns(:video)).to have_attributes({ + id: video.id, + project_id: project.id, + analysis_status: "not_started", + analysis_started_at: nil, + name: nil, + number: nil, + report_published_at: nil + }) + expect(assigns(:edl_events_data)).to eq({ + edl_events: build_list(:edl_event, 1), + edl_attributes: { + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + }, + info_message: "An EDL event was found. Data is shown below", + }) + end + end + + describe "#create" do + it "assigns video_release_confirmations, video_release_confirmation" do + file_info = create(:file_info) + + post :create, + params: { + video_id: video, + acquired_media_release_id: acquired_media_release, + video_release_confirmation: { + channel: "V", + time_elapsed: "5", + timecode_in: "01:00:00:00", + timecode_out: "01:00:05:00", + duration: "5.0", + source_file_name: "DISCLAIMER.AVI", + clip_name: "DISCLAIMER.AVI.NEW.02", + description: "Boat", + file_info_id: file_info, + }, + use_route: video_acquired_media_release_video_release_confirmations_path(video, acquired_media_release) + }, + xhr: true + + expect(assigns(:video_release_confirmations)).to eq(video.video_release_confirmations) + expect(assigns(:video_release_confirmation)).to have_attributes({ + time_elapsed: "5", + timecode_in: "01:00:00:00", + timecode_out: "01:00:05:00", + duration: "5.0", + source_file_name: "DISCLAIMER.AVI", + clip_name: "DISCLAIMER.AVI.NEW.02", + description: "Boat", + file_info_id: file_info.id, + }) + end + + it "creates a video confirmation" do + expect { + post :create, + params: { + video_id: video, + acquired_media_release_id: acquired_media_release, + video_release_confirmation: { + channel: "V", + time_elapsed: "5", + timecode_in: "01:00:00:00", + timecode_out: "01:00:05:00", + duration: "5.0", + source_file_name: "DISCLAIMER.AVI", + clip_name: "DISCLAIMER.AVI.NEW.02", + description: "Boat", + }, + use_route: video_acquired_media_release_video_release_confirmations_path(video, acquired_media_release) + }, + xhr: true + }.to change { VideoReleaseConfirmation.count }.from(0).to(1) + end + end + + describe "#destroy" do + let!(:video_release_confirmation) { create(:video_release_confirmation, video: video, releasable: acquired_media_release) } + + it "assigns releasable, video, video_release_confirmations" do + delete :destroy, + params: { + id: video_release_confirmation, + video_id: video, + use_route: video_acquired_media_release_video_release_confirmations_path(video, video_release_confirmation) + }, xhr: true + + expect(assigns(:releasable)).to eq(video_release_confirmation.releasable) + expect(assigns(:video)).to eq(video) + expect(assigns(:video_release_confirmations).to_a).to eq([]) + end + + it "destroys video confirmation" do + expect { + delete :destroy, + params: { + id: video_release_confirmation, + video_id: video, + use_route: video_acquired_media_release_video_release_confirmations_path(video, video_release_confirmation) + }, + xhr: true + }.to change { VideoReleaseConfirmation.count }.from(1).to(0) + end + end + end + + describe "location_releases" do + let(:location_release) { create(:location_release) } + let(:edl_event_gateway) { instance_double(EdlEventGateway) } + + describe "#new" do + before :each do + allow(EdlEventGateway).to receive(:new).and_return(edl_event_gateway) + allow(edl_event_gateway).to receive(:edl_events).and_return(build_list(:edl_event, 1)) + end + + it "assigns video_release_confirmation, video, edl_events_data" do + post :new, + params: { + video_id: video, + location_release_id: location_release, + video_release_confirmation: { + time_elapsed: nil, + timecode_in: nil, + timecode_out: nil, + duration: nil, + source_file_name: nil, + clip_name: nil, + description: nil, + }, + use_route: new_video_location_release_video_release_confirmation_path(video, location_release) + }, + xhr: true + + expect(assigns(:video_release_confirmation)).to have_attributes({ + video_id: video.id, + releasable_id: location_release.id, + time_elapsed: "", + releasable_type: "LocationRelease", + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + }) + expect(assigns(:video)).to have_attributes({ + id: video.id, + project_id: project.id, + analysis_status: "not_started", + analysis_started_at: nil, + name: nil, + number: nil, + report_published_at: nil + }) + expect(assigns(:edl_events_data)).to eq({ + edl_events: build_list(:edl_event, 1), + edl_attributes: { + channel: "V", + timecode_in: "timecode_in", + timecode_out: "timecode_out", + duration: "duration", + source_file_name: "source_file_name", + clip_name: "clip_name", + description: "description", + }, + info_message: "An EDL event was found. Data is shown below", + }) + end + end + + describe "#create" do + it "assigns video_release_confirmations, video_release_confirmation" do + post :create, + params: { + video_id: video, + location_release_id: location_release, + video_release_confirmation: { + channel: "V", + time_elapsed: "5", + timecode_in: "01:00:00:00", + timecode_out: "01:00:05:00", + duration: "5.0", + source_file_name: "DISCLAIMER.AVI", + clip_name: "DISCLAIMER.AVI.NEW.02", + description: "Boat", + }, + use_route: video_location_release_video_release_confirmations_path(video, location_release) + }, + xhr: true + + expect(assigns(:video_release_confirmations)).to eq(video.video_release_confirmations) + expect(assigns(:video_release_confirmation)).to have_attributes({ + time_elapsed: "5", + timecode_in: "01:00:00:00", + timecode_out: "01:00:05:00", + duration: "5.0", + source_file_name: "DISCLAIMER.AVI", + clip_name: "DISCLAIMER.AVI.NEW.02", + description: "Boat", + }) + end + + it "creates a video confirmation" do + expect { + post :create, + params: { + video_id: video, + location_release_id: location_release, + video_release_confirmation: { + time_elapsed: "5", + timecode_in: "01:00:00:00", + timecode_out: "01:00:05:00", + duration: "5.0", + source_file_name: "DISCLAIMER.AVI", + clip_name: "DISCLAIMER.AVI.NEW.02", + description: "Boat", + }, + use_route: video_location_release_video_release_confirmations_path(video, location_release) + }, + xhr: true + }.to change { VideoReleaseConfirmation.count }.from(0).to(1) + end + end + + describe "#destroy" do + let!(:video_release_confirmation) { create(:video_release_confirmation, video: video, releasable: location_release) } + + it "assigns releasable, video, video_release_confirmations" do + delete :destroy, + params: { + id: video_release_confirmation, + video_id: video, + use_route: video_location_release_video_release_confirmations_path(video, video_release_confirmation) + }, xhr: true + + expect(assigns(:releasable)).to eq(video_release_confirmation.releasable) + expect(assigns(:video)).to eq(video) + expect(assigns(:video_release_confirmations).to_a).to eq([]) + end + + it "destroys video confirmation" do + expect { + delete :destroy, + params: { + id: video_release_confirmation, + video_id: video, + use_route: video_location_release_video_release_confirmations_path(video, video_release_confirmation) + }, + xhr: true + }.to change { VideoReleaseConfirmation.count }.from(1).to(0) + end + end + end +end diff --git a/spec/controllers/video_reports_controller_spec.rb b/spec/controllers/video_reports_controller_spec.rb new file mode 100644 index 0000000..ceb9438 --- /dev/null +++ b/spec/controllers/video_reports_controller_spec.rb @@ -0,0 +1,34 @@ +require "rails_helper" + +RSpec.describe VideoReportsController, type: :controller do + let(:user) { create(:user) } + let(:video) { create(:video, project: create(:project, account: user.primary_account)) } + + before :each do + sign_in user + end + + describe "show" do + context "when type is discovery" do + it "builds the report and responds successfully" do + get :show, params: { video_id: video, format: "xlsx", type: "discovery" } + + expect(response).to be_successful + expect(response.body.unpack("C*").pack("U*")).to_not be_blank + expect(response.header["Content-Type"]).to eq "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + expect(response.header["Content-Disposition"]).to match "video_file-mp4_production-elements-log.xlsx" + end + end + + context "when type is nat_geo" do + it "builds the report and responds successfully" do + get :show, params: { video_id: video, format: "xlsx", type: "nat_geo" } + + expect(response).to be_successful + expect(response.body.unpack("C*").pack("U*")).to_not be_blank + expect(response.header["Content-Type"]).to eq "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + expect(response.header["Content-Disposition"]).to match "video_file-mp4_legal-binder-log.xlsx" + end + end + end +end diff --git a/spec/controllers/videos/report_publications_controller_spec.rb b/spec/controllers/videos/report_publications_controller_spec.rb new file mode 100644 index 0000000..8eca572 --- /dev/null +++ b/spec/controllers/videos/report_publications_controller_spec.rb @@ -0,0 +1,30 @@ +require "rails_helper" + +describe Videos::ReportPublicationsController do + let(:admin) { create(:user, :admin) } + let(:project) { create(:project, account: admin.accounts.first) } + + before do + sign_in(admin) + end + + describe "#create" do + it "sets the video report publication date" do + video = create(:video, report_published_at: nil, project: project) + + post :create, params: { video_id: video } + + expect(video.reload).to be_report_published + end + end + + describe "#destroy" do + it "unsets the video report publication date" do + video = create(:video, report_published_at: Time.zone.now, project: project) + + delete :destroy, params: { video_id: video } + + expect(video.reload).not_to be_report_published + end + end +end diff --git a/spec/controllers/videos_controller_spec.rb b/spec/controllers/videos_controller_spec.rb new file mode 100644 index 0000000..63cf92b --- /dev/null +++ b/spec/controllers/videos_controller_spec.rb @@ -0,0 +1,280 @@ +require "rails_helper" + +RSpec.describe VideosController, type: :controller do + render_views + + let(:user) { create(:user) } + let(:account) { user.primary_account } + let(:project) { create(:project, account: user.primary_account) } + let(:video) { create(:video, project: project) } + + before do + sign_in(user) + end + + describe "#index" do + it "responds successfully" do + get :index, params: { project_id: project } + + expect(response).to be_successful + end + + it "has a search form" do + get :index, params: { project_id: project } + + expect(response.body).to have_button("search-button") + end + + it "filters the videos by a query param" do + create(:video, project: project, name: "First Video") + create(:video, project: project, name: "Second Video") + + get :index, params: { project_id: project, query: "Second"}, xhr: true + + expect(response.body).not_to have_content("First Video") + expect(response.body).to have_content("Second Video") + end + + it "paginates the videos" do + create_list(:video, 20, project: project) + + get :index, params: { project_id: project } + expect(response.body).to have_link("2", href: project_videos_path(project, page: 2)) + end + + + it "renders content" do + video = create(:video, project: project, name: "My Video", number: "001", created_at: 1.day.ago) + + get :index, params: { project_id: project } + + expect(response.body).to have_content("1 day ago") + expect(response.body).to have_content("My Video") + expect(response.body).to have_content("001") + expect(response.body).to have_content("video_file.mp4") + expect(response.body).to have_content("Generating...") + expect(response.body).to have_link("Upload New Video", href: landing_project_videos_path(project)) + expect(response.body).to have_link("Edit", href: edit_video_path(video)) + end + + context "when there are no records" do + it "renders an empty message" do + get :index, params: { project_id: project } + + expect(response.body).to have_content("Videos will appear here") + end + end + + context "when there are many records" do + it "paginates the table" do + create_list(:video, 20, project: project) + + get :index, params: { project_id: project } + + expect(response.body).to have_link("2", href: project_videos_path(project, page: 2)) + end + end + end + + describe "#new" do + it "responds successfully" do + get :new, params: { project_id: project } + + expect(response).to be_successful + end + end + + describe "#landing" do + it "responds successfully" do + get :landing, params: { project_id: project } + + expect(response).to be_successful + end + end + + describe "#create" do + it "responds with redirect" do + post :create, params: { project_id: project, video: video_create_params } + + expect(response).to redirect_to(project_videos_path(project)) + end + + it "creates a new video" do + expect { + post :create, params: { project_id: project, video: video_create_params } + }.to change(Video, "count").by(1) + + expect(Video.last.video_editing_system).to eq "avid" + end + + it "begins analyzing the video" do + expect { + post :create, params: { project_id: project, video: video_create_params } + }.to have_enqueued_job(AnalyzeVideoJob).with(Video.last).and have_enqueued_job(AnalyzeAudioJob).with(Video.last) + end + + it "delivers an admin notification" do + assert_enqueued_emails 1 do + post :create, params: { project_id: project, video: video_create_params } + end + end + + it "sets the flash" do + post :create, params: { project_id: project, video: video_create_params } + + expect(flash.notice).to eq t("videos.create.notice") + end + + it "logs analytics" do + expect { + post :create, params: { project_id: project, video: video_create_params } + }.to have_enqueued_job(TrackAnalyticsJob).with(user, account, :track_video_upload, user_agent: "Rails Testing", user_ip: "0.0.0.0") + end + + context "when the record is invalid" do + it "responds successfully" do + post :create, params: { project_id: project, video: video_create_params(valid: false) } + + expect(response).to be_successful + end + end + end + + describe "#edit" do + it "responds successfully" do + get :edit, params: { id: video } + + expect(response).to be_successful + end + end + + describe "#update" do + it "redirects to the DeliverME page" do + patch :update, params: { id: video, video: video_update_params } + + expect(response).to redirect_to(project_videos_path(project)) + end + + it "updates the video attributes" do + patch :update, params: { id: video, video: { name: "New Name" } } + + expect(video.reload.name).to eq "New Name" + end + + it "sets the flash" do + patch :update, params: { id: video, video: video_update_params } + + expect(flash.notice).to eq t("videos.update.notice") + end + + context "when params are invalid" do + it "re-renders the edit view" do + allow_any_instance_of(Video).to receive(:update).and_return(false) + + patch :update, params: { id: video, video: video_update_params } + + expect(response).to be_successful + end + end + + context "with unpermitted file param" do + it "does not update the record" do + new_file = Rack::Test::UploadedFile.new file_fixture("video_file.mp4") + + expect { + patch :update, params: { id: video, video: { name: "New Name", file: new_file } } + }.to raise_error ActionController::UnpermittedParameters + end + end + + context "when edl_file changes" do + let(:new_edl_file) do + Rack::Test::UploadedFile.new file_fixture("sample-edl.edl"), "application/octet-stream" + end + + it "delivers an admin notification" do + assert_enqueued_email_with AdminMailer, :updated_video_edl_file, args: video do + patch :update, params: { id: video, video: { edl_file: new_edl_file } } + end + end + + it "unpublishes the video" do + video.publish_report! + + patch :update, params: { id: video, video: { edl_file: new_edl_file } } + + expect(video.reload).not_to be_report_published + end + end + + context "when graphics_only_edl_file changes" do + let(:new_graphics_only_edl_file) do + Rack::Test::UploadedFile.new file_fixture("sample-edl.edl"), "application/octet-stream" + end + + it "delivers an admin notification" do + assert_enqueued_email_with AdminMailer, :updated_video_graphics_only_edl_file, args: video do + patch :update, params: { id: video, video: { graphics_only_edl_file: new_graphics_only_edl_file } } + end + end + + it "unpublishes the video" do + video.publish_report! + + patch :update, params: { id: video, video: { graphics_only_edl_file: new_graphics_only_edl_file } } + + expect(video.reload).not_to be_report_published + end + end + + context "when audio_only_edl_file changes" do + let(:new_audio_only_edl_file) do + Rack::Test::UploadedFile.new file_fixture("sample-edl.edl"), "application/octet-stream" + end + + it "delivers an admin notification" do + assert_enqueued_email_with AdminMailer, :updated_video_audio_only_edl_file, args: video do + patch :update, params: { id: video, video: { audio_only_edl_file: new_audio_only_edl_file } } + end + end + + it "unpublishes the video" do + video.publish_report! + + patch :update, params: { id: video, video: { audio_only_edl_file: new_audio_only_edl_file } } + + expect(video.reload).not_to be_report_published + end + end + + context "when edl_file and graphics_only_edl_file and audio_only_edl_file does not change" do + it "does not deliver an admin notification" do + assert_no_enqueued_emails do + patch :update, params: { id: video, video: { name: "New Name" } } + end + end + + it "does not unpublish the video" do + video.publish_report! + + patch :update, params: { id: video, video: { name: "New Name" } } + + expect(video.reload).to be_report_published + end + end + end + + private + + def video_create_params(valid: true) + if valid + attributes_for(:video, :with_graphics_only_edl_file, :with_audio_only_edl_file, name: "Test Video", video_editing_system: "avid") + else + attributes_for(:video, :with_graphics_only_edl_file, :with_audio_only_edl_file, name: "Test Video").except(:file) + end + end + + def video_update_params + attributes_for(:video, :with_graphics_only_edl_file, :with_audio_only_edl_file, name: "Test Video").except(:file) + end +end diff --git a/spec/controllers/zoom_meetings_controller_spec.rb b/spec/controllers/zoom_meetings_controller_spec.rb new file mode 100644 index 0000000..2048f4d --- /dev/null +++ b/spec/controllers/zoom_meetings_controller_spec.rb @@ -0,0 +1,31 @@ +require 'rails_helper' +require 'zoom_gateway' + +RSpec.describe ZoomMeetingsController, type: :controller do + let(:user) { create(:user) } + let(:account) { user.primary_account } + let(:project) { create(:project, account: user.primary_account) } + let(:broadcast) { create(:broadcast, name: "Broadcast", project: project) } + + let(:meeting_start_url) { "http://meeting_start_url" } + let(:meeting_hash) { HashWithIndifferentAccess.new(start_url: meeting_start_url) } + let(:user_create_response) { {"id" => "new_host_id"} } + let(:roles_assign_response) { {"ids" => ["new_host_id"]} } + let(:roles_list_response) { {"roles" => [{"name" => "directme-host"}]} } + + before :each do + allow_any_instance_of(ZoomGateway).to receive(:find_meeting).and_return(meeting_hash) + allow_any_instance_of(ZoomGateway).to receive(:create_meeting).and_return("meeting_id") + allow_any_instance_of(ZoomGateway).to receive(:create_host).and_return("host_id") + allow(MuxLiveStream).to receive(:new).and_return OpenStruct.new(id: 'id', key: 'key', playback_id: 'playback_id') + end + + describe "#show" do + before { sign_in user } + + it "redirects to meeting start url" do + get :show, params: { project_id: project.id, broadcast_id: broadcast.id } + expect(response).to redirect_to(meeting_start_url) + end + end +end diff --git a/spec/controllers/zoom_notifications_controller_spec..rb b/spec/controllers/zoom_notifications_controller_spec..rb new file mode 100644 index 0000000..2686957 --- /dev/null +++ b/spec/controllers/zoom_notifications_controller_spec..rb @@ -0,0 +1,82 @@ +require "rails_helper" + +RSpec.describe ZoomNotificationsController, type: :controller do + render_views + + let!(:zoom_meeting) { create(:zoom_meeting, api_meeting_id: 'meeting_id') } + + let(:started_status) { {event: 'meeting.started', payload: {object: {id: 'meeting_id' }}} } + let(:ended_status) { {event: 'meeting.ended', payload: {object: {id: 'meeting_id' }}} } + let(:wrong_meeting_id) { {payload: {object: {id: 'wrong_id' }}} } + + let(:authorization_header) { {'Authorization' => 'xxx-xxx-xxx'} } + let(:wrong_authorization_header) { {'Authorization' => 'yyy-yyy-yyy'} } + + before do + allow(ENV).to receive(:[]).with('ZOOM_VERIFICATION_TOKEN').and_return('xxx-xxx-xxx') + end + + describe '#create' do + context 'with no authorization key' do + it 'raises 403 response' do + post :create, params: started_status + expect(response).to have_http_status(403) + end + end + + context 'with wrong authorization key' do + it 'raises 403 response' do + request.headers.merge!(wrong_authorization_header) + post :create, params: started_status + expect(response).to have_http_status(403) + end + end + + context 'authorized' do + before do + request.headers.merge!(authorization_header) + end + + context 'with wrong meeting id' do + it 'raises RecordNotFound' do + expect { + post :create, params: wrong_meeting_id + }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context 'with right meeting id' do + it 'responds with 200' do + post :create, params: started_status + + expect(response).to have_http_status(200) + end + + it 'assigns the zoom meeting' do + post :create, params: started_status + + expect(assigns(:zoom_meeting)).to eq(zoom_meeting) + end + + it 'updates the zoom_meeting when started status is received in notification' do + post :create, params: started_status + + expect(zoom_meeting.reload).to be_started + end + + it 'updates the zoom_meeting when ended status is received in notification' do + post :create, params: ended_status + + expect(zoom_meeting.reload).to be_ended + end + + it 'updates the recording when recording complete notification is received' do + expect { + post :create, params: recording_complete + }.to change { zoom_meeting.recording } + end + + end + end + end +end diff --git a/spec/factories/account_auths.rb b/spec/factories/account_auths.rb new file mode 100644 index 0000000..e2c8bf1 --- /dev/null +++ b/spec/factories/account_auths.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :account_auth do + association :user + association :account + end +end diff --git a/spec/factories/accounts.rb b/spec/factories/accounts.rb new file mode 100644 index 0000000..51464bf --- /dev/null +++ b/spec/factories/accounts.rb @@ -0,0 +1,18 @@ +FactoryBot.define do + factory :account do + name "My Team" + plan_uid "me_suite" + + trait :mesuite do + plan_uid :me_suite + end + + trait :releaseme do + plan_uid :releaseme + end + + trait :deliverme do + plan_uid :deliverme + end + end +end diff --git a/spec/factories/acquired_media_releases.rb b/spec/factories/acquired_media_releases.rb new file mode 100644 index 0000000..699d7ac --- /dev/null +++ b/spec/factories/acquired_media_releases.rb @@ -0,0 +1,37 @@ +FactoryBot.define do + factory :acquired_media_release do + association :project + + name "Test Acquired Media Release" + + trait :native do + signature do + path = Rails.root.join("spec", "fixtures", "files", "signature.png") + Rack::Test::UploadedFile.new(path, "image/png") + end + end + + trait :non_native do + contract do + path = Rails.root.join("spec", "fixtures", "files", "contract.pdf") + Rack::Test::UploadedFile.new(path, "application/pdf") + end + end + + factory :acquired_media_release_with_contract_template do + after(:build) do |acquired_media_release, _| + acquired_media_release.contract_template = build(:acquired_media_release_contract_template) + end + end + + factory :acquired_media_release_with_file_infos do + transient do + file_infos_count { 3 } + end + + after(:create) do |acquired_media_release, evaluator| + create_list(:file_info, evaluator.file_infos_count, releasable: acquired_media_release) + end + end + end +end diff --git a/spec/factories/appearance_releases.rb b/spec/factories/appearance_releases.rb new file mode 100644 index 0000000..fa39d2c --- /dev/null +++ b/spec/factories/appearance_releases.rb @@ -0,0 +1,69 @@ +FactoryBot.define do + factory :appearance_release do + association :project + + person_first_name "Jane" + person_last_name "Doe" + + person_photo do + path = Rails.root.join("spec", "fixtures", "files", "person_photo.png") + Rack::Test::UploadedFile.new(path, "image/png") + end + + trait :native do + person_address "100 Test Lane, New York, NY 10001" + person_phone "123-555-6789" + + signature do + path = Rails.root.join("spec", "fixtures", "files", "signature.png") + Rack::Test::UploadedFile.new(path, "image/png") + end + end + + trait :non_native do + contract do + path = Rails.root.join("spec", "fixtures", "files", "contract.pdf") + Rack::Test::UploadedFile.new(path, "application/pdf") + end + end + + trait :minor do + minor true + guardian_first_name "Jamie" + guardian_last_name "Doe" + guardian_address "100 Test Lane, New York, 10001" + guardian_phone "123-555-1234" + end + + trait :minor_with_guardian_photo do + minor true + guardian_first_name "Jamie" + guardian_last_name "Doe" + guardian_address "100 Test Lane, New York, 10001" + guardian_phone "123-555-1234" + guardian_photo do + path = Rails.root.join("spec", "fixtures", "files", "pratt.jpg") + Rack::Test::UploadedFile.new(path, "image/jpeg") + end + end + + trait :without_person_photo do + person_photo nil + end + trait :with_person_photo do + person_photo do + path = Rails.root.join("spec", "fixtures", "files", "person_photo.png") + Rack::Test::UploadedFile.new(path, "image/png") + end + end + + trait :with_person_photo_only do + end + + factory :appearance_release_with_contract_template do + after(:build) do |appearance_release, _| + appearance_release.contract_template = build(:appearance_release_contract_template) + end + end + end +end diff --git a/spec/factories/applicable_media.rb b/spec/factories/applicable_media.rb new file mode 100644 index 0000000..b974baf --- /dev/null +++ b/spec/factories/applicable_media.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :applicable_medium do + label "All" + + trait :other do + label "Other" + end + end +end diff --git a/spec/factories/audio_confirmations.rb b/spec/factories/audio_confirmations.rb new file mode 100644 index 0000000..19c5d2f --- /dev/null +++ b/spec/factories/audio_confirmations.rb @@ -0,0 +1,15 @@ +FactoryBot.define do + factory :audio_confirmation do + association :video + time_elapsed "5" + confirmation_type "original_music" + + trait :library do + confirmation_type "library_music" + end + + trait :original do + confirmation_type "original_music" + end + end +end diff --git a/spec/factories/bookmarks.rb b/spec/factories/bookmarks.rb new file mode 100644 index 0000000..fad8ba5 --- /dev/null +++ b/spec/factories/bookmarks.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :bookmark do + association :video + time_elapsed "5" + end +end diff --git a/spec/factories/broadcast_recordings.rb b/spec/factories/broadcast_recordings.rb new file mode 100644 index 0000000..1cead9a --- /dev/null +++ b/spec/factories/broadcast_recordings.rb @@ -0,0 +1,8 @@ +FactoryBot.define do + factory :broadcast_recording do + association :broadcast + file_name "high.mp4" + asset_uid "asset_uid" + asset_playback_uid "asset_playback_uid" + end +end diff --git a/spec/factories/broadcasts.rb b/spec/factories/broadcasts.rb new file mode 100644 index 0000000..0784d1d --- /dev/null +++ b/spec/factories/broadcasts.rb @@ -0,0 +1,28 @@ +FactoryBot.define do + factory :broadcast do + association :project + name "My Live Stream" + + transient do + skip_create_callback false + end + + trait :with_stream do + stream_uid "mux_stream" + stream_key "mux_key" + stream_playback_uid "mux_playback_id" + status "created" + streamer_status "idle" + end + + trait :with_files do + files { [Rack::Test::UploadedFile.new('spec/fixtures/files/contract.pdf', 'application/pdf')] } + end + + after(:build) do |broadcast, evaluator| + if evaluator.skip_create_callback + broadcast.class.skip_callback(:create, :after, :create_mux_live_stream, raise: false) + end + end + end +end diff --git a/spec/factories/composers.rb b/spec/factories/composers.rb new file mode 100644 index 0000000..8a6663c --- /dev/null +++ b/spec/factories/composers.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :composer do + association :music_release + + name "Beethoven" + affiliation "Affiliation" + percentage 100.0 + end +end diff --git a/spec/factories/contract_templates.rb b/spec/factories/contract_templates.rb new file mode 100644 index 0000000..782bea9 --- /dev/null +++ b/spec/factories/contract_templates.rb @@ -0,0 +1,31 @@ +FactoryBot.define do + factory :contract_template do + association :project + + name "Test template" + release_type "appearance" + body "This is a test contract template." + guardian_clause "Is the signer a minor?" + fee "$0.00" + + factory :appearance_release_contract_template do + release_type "appearance" + end + + factory :talent_release_contract_template do + release_type "talent" + end + + factory :material_release_contract_template do + release_type "material" + end + + factory :location_release_contract_template do + release_type "location" + end + + factory :acquired_media_release_contract_template do + release_type "acquired_media" + end + end +end diff --git a/spec/factories/directories.rb b/spec/factories/directories.rb new file mode 100644 index 0000000..32c19aa --- /dev/null +++ b/spec/factories/directories.rb @@ -0,0 +1,27 @@ +FactoryBot.define do + factory :directory do + association :project + association :user + + name "Payrolls" + category "Finance" + + trait :for_manager do + permissions "Account Managers & Project Managers" + end + + trait :for_account_manager do + permissions "Account Managers Only" + end + + trait :with_files do + files { [Rack::Test::UploadedFile.new('spec/fixtures/files/location_photo.png', 'image/png')] } + end + + file_names = ['location_photo.png', 'material_photo.png', 'person_photo.png'] + + trait :many_files do + files { 30.times.map { Rack::Test::UploadedFile.new("spec/fixtures/files/#{file_names.sample}", 'image/png') } } + end + end +end diff --git a/spec/factories/downloads.rb b/spec/factories/downloads.rb new file mode 100644 index 0000000..b8a98f1 --- /dev/null +++ b/spec/factories/downloads.rb @@ -0,0 +1,24 @@ +FactoryBot.define do + factory :download do + association :project + + release_type "AppearanceRelease" + + trait :with_file do + file do + path = Rails.root.join("spec", "fixtures", "files", "contract.pdf") + Rack::Test::UploadedFile.new(path, "application/pdf") + end + end + + factory :download_release_type_appearance do + name "Natgeo Contract" + release_type "AppearanceRelease" + end + + factory :download_release_type_music do + name "Discovery Contract" + release_type "MusicRelease" + end + end +end diff --git a/spec/factories/edl_events.rb b/spec/factories/edl_events.rb new file mode 100644 index 0000000..26f445d --- /dev/null +++ b/spec/factories/edl_events.rb @@ -0,0 +1,15 @@ +FactoryBot.define do + factory :edl_event do + channel "V" + start_time "start_time" + timecode_in "timecode_in" + timecode_out "timecode_out" + duration "duration" + source_file_name "source_file_name" + clip_name "clip_name" + description "description" + matches [] + + initialize_with { new(attributes) } + end +end diff --git a/spec/factories/file_infos.rb b/spec/factories/file_infos.rb new file mode 100644 index 0000000..a5b5a72 --- /dev/null +++ b/spec/factories/file_infos.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :file_info do + association :releasable, factory: :acquired_media_release + + filename "filename.txt" + content_type "text/plain" + byte_size 1_000 + end +end diff --git a/spec/factories/graphics_elements.rb b/spec/factories/graphics_elements.rb new file mode 100644 index 0000000..977a8c0 --- /dev/null +++ b/spec/factories/graphics_elements.rb @@ -0,0 +1,8 @@ +FactoryBot.define do + factory :graphics_element do + association :video + graphic_type "Logo" + text "some text" + time_elapsed "5" + end +end diff --git a/spec/factories/imports.rb b/spec/factories/imports.rb new file mode 100644 index 0000000..f380da8 --- /dev/null +++ b/spec/factories/imports.rb @@ -0,0 +1,12 @@ +FactoryBot.define do + factory :import do + association :project + end + + trait :with_file do + file do + path = Rails.root.join("spec", "fixtures", "files", "contract.pdf") + Rack::Test::UploadedFile.new(path, "application/pdf") + end + end +end diff --git a/spec/factories/location_releases.rb b/spec/factories/location_releases.rb new file mode 100644 index 0000000..3431b77 --- /dev/null +++ b/spec/factories/location_releases.rb @@ -0,0 +1,59 @@ +FactoryBot.define do + factory :location_release do + association :project + + name "Test Premises" + + trait :native do + person_first_name "Jane" + person_last_name "Doe" + person_phone "100-555-1000" + + signature do + path = Rails.root.join("spec", "fixtures", "files", "signature.png") + Rack::Test::UploadedFile.new(path, "image/png") + end + end + + trait :non_native do + contract do + path = Rails.root.join("spec", "fixtures", "files", "contract.pdf") + Rack::Test::UploadedFile.new(path, "application/pdf") + end + end + + factory :location_release_with_contract_template do + after(:build) do |location_release, _| + location_release.contract_template = build(:location_release_contract_template) + end + end + + factory :location_release_with_contract_template_and_photo do + after(:build) do |location_release, _| + location_release.contract_template = build(:location_release_contract_template) + path = Rails.root.join("spec", "fixtures", "files", "location_photo.png") + location_release.photos.attach Rack::Test::UploadedFile.new(path, "image/png") + end + end + + factory :location_release_with_photo do + after(:build) do |location_release, _| + path = Rails.root.join("spec", "fixtures", "files", "location_photo.png") + location_release.photos.attach Rack::Test::UploadedFile.new(path, "image/png") + end + end + + factory :location_release_with_photos do + transient do + photos_count { 2 } + end + + after(:build) do |location_release, evaluator| + location_release.photos = evaluator.photos_count.times.map do + path = Rails.root.join("spec", "fixtures", "files", "location_photo.png") + Rack::Test::UploadedFile.new(path, "image/png") + end + end + end + end +end diff --git a/spec/factories/material_releases.rb b/spec/factories/material_releases.rb new file mode 100644 index 0000000..9f6339a --- /dev/null +++ b/spec/factories/material_releases.rb @@ -0,0 +1,59 @@ +FactoryBot.define do + factory :material_release do + association :project + + name "Test Materials" + + trait :native do + person_first_name "Jane" + person_last_name "Doe" + person_phone "100-555-1001" + + signature do + path = Rails.root.join("spec", "fixtures", "files", "signature.png") + Rack::Test::UploadedFile.new(path, "image/png") + end + end + + trait :non_native do + contract do + path = Rails.root.join("spec", "fixtures", "files", "contract.pdf") + Rack::Test::UploadedFile.new(path, "application/pdf") + end + end + + factory :material_release_with_contract_template do + after(:build) do |material_release, _| + material_release.contract_template = build(:material_release_contract_template) + end + end + + factory :material_release_with_contract_template_and_photo do + after(:build) do |material_release, _| + material_release.contract_template = build(:material_release_contract_template) + path = Rails.root.join("spec", "fixtures", "files", "material_photo.png") + material_release.photos.attach Rack::Test::UploadedFile.new(path, "image/png") + end + end + + factory :material_release_with_photo do + after(:build) do |material_release, _| + path = Rails.root.join("spec", "fixtures", "files", "material_photo.png") + material_release.photos.attach Rack::Test::UploadedFile.new(path, "image/png") + end + end + + factory :material_release_with_photos do + transient do + photos_count { 2 } + end + + after(:build) do |material_release, evaluator| + material_release.photos = evaluator.photos_count.times.map do + path = Rails.root.join("spec", "fixtures", "files", "material_photo.png") + Rack::Test::UploadedFile.new(path, "image/png") + end + end + end + end +end diff --git a/spec/factories/music_releases.rb b/spec/factories/music_releases.rb new file mode 100644 index 0000000..b71a607 --- /dev/null +++ b/spec/factories/music_releases.rb @@ -0,0 +1,29 @@ +FactoryBot.define do + factory :music_release do + association :project + name "OST Test" + person_first_name "John" + person_last_name "Doe" + + after(:build) do |music_release, _| + music_release.composers << build(:composer, music_release: music_release) if music_release.composers.empty? + music_release.publishers << build(:publisher, music_release: music_release) if music_release.publishers.empty? + end + + factory :music_release_with_contract_template do + after(:build) do |music_release, _| + music_release.contract_template = build(:contract_template) + end + end + + factory :music_release_with_file_infos do + transient do + file_infos_count { 3 } + end + + after(:create) do |release, evaluator| + create_list(:file_info, evaluator.file_infos_count, releasable: release) + end + end + end +end diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb new file mode 100644 index 0000000..fe951a6 --- /dev/null +++ b/spec/factories/notes.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :note do + association :user + association :notable, factory: :appearance_release + content "My note text" + end +end diff --git a/spec/factories/project_memberships.rb b/spec/factories/project_memberships.rb new file mode 100644 index 0000000..c0979f0 --- /dev/null +++ b/spec/factories/project_memberships.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :project_membership do + association :project + association :user + end +end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb new file mode 100644 index 0000000..c720785 --- /dev/null +++ b/spec/factories/projects.rb @@ -0,0 +1,64 @@ +FactoryBot.define do + factory :project do + association :account + + client_name "My Client" + description "This is the video project description." + details "These are the details of the video project. " + name "My Video Project" + producer_address "123 Corporate Lane, New York, NY 10001" + producer_name "Production Company, LLC" + + # Enable all release category sections by default + after(:build) do |project, _| + project.settings(:features).attributes = { + acquired_media_release: true, + appearance_release: true, + location_release: true, + material_release: true, + music_release: true, + talent_release: true, + video_analysis: true, + } + end + + # Allow team members to be set for a given project + after(:build) do |project, evaluator| + Array.wrap(evaluator.members).each do |member| + next if member.account_manager?(project.account) + project.project_memberships.build(user: member) + end + end + + transient do + members { [] } + end + + trait :sample do + name "My Sample Video Project" + sample true + end + + trait :discovery_client do + predefined_client_name "discovery" + end + + trait :nat_geo_client do + predefined_client_name "nat_geo" + end + + factory :project_with_contract_template do + after(:build) do |project, _| + project.contract_templates << build(:contract_template, project: nil) + end + end + + factory :project_with_directories do + after(:build) do |project, _| + project.directories << build(:directory, project: nil, name: "Shared") + project.directories << build(:directory, :for_manager, project: nil, name: "Financial Documents") + project.directories << build(:directory, :for_account_manager, project: nil, name: "Salaries") + end + end + end +end diff --git a/spec/factories/publishers.rb b/spec/factories/publishers.rb new file mode 100644 index 0000000..dcf4c26 --- /dev/null +++ b/spec/factories/publishers.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :publisher do + association :music_release + + name "Jingle Punks" + affiliation "Affiliation" + percentage 100.0 + end +end diff --git a/spec/factories/restrictions.rb b/spec/factories/restrictions.rb new file mode 100644 index 0000000..95d2794 --- /dev/null +++ b/spec/factories/restrictions.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :restriction do + label "None" + + trait :other do + label "Other" + end + end +end diff --git a/spec/factories/talent_releases.rb b/spec/factories/talent_releases.rb new file mode 100644 index 0000000..1ac62ea --- /dev/null +++ b/spec/factories/talent_releases.rb @@ -0,0 +1,65 @@ +FactoryBot.define do + factory :talent_release do + association :project + + person_first_name "Jane" + person_last_name "Doe" + + photos [Rack::Test::UploadedFile.new(Rails.root.join("spec", "fixtures", "files", "person_photo.png"), "image/png")] + + trait :native do + person_phone "123-555-6789" + + signature do + path = Rails.root.join("spec", "fixtures", "files", "signature.png") + Rack::Test::UploadedFile.new(path, "image/png") + end + end + + trait :non_native do + contract do + path = Rails.root.join("spec", "fixtures", "files", "contract.pdf") + Rack::Test::UploadedFile.new(path, "application/pdf") + end + end + + trait :minor do + minor true + guardian_first_name "Jamie" + guardian_last_name "Doe" + guardian_phone "123-555-1234" + end + + trait :minor_with_guardian_photo do + minor true + guardian_first_name "Jamie" + guardian_last_name "Doe" + guardian_phone "123-555-1234" + guardian_photo do + path = Rails.root.join("spec", "fixtures", "files", "pratt.jpg") + Rack::Test::UploadedFile.new(path, "image/jpeg") + end + end + + factory :talent_release_with_contract_template do + after(:build) do |talent_release, _| + talent_release.contract_template = build(:talent_release_contract_template) + end + end + + factory :talent_release_with_contract_template_and_photos do + after(:build) do |talent_release, _| + talent_release.contract_template = build(:talent_release_contract_template) + path = Rails.root.join("spec", "fixtures", "files", "person_photo.png") + talent_release.photos.attach Rack::Test::UploadedFile.new(path, "image/png") + end + end + + factory :talent_release_with_photo do + after(:build) do |talent_release, _| + path = Rails.root.join("spec", "fixtures", "files", "person_photo.png") + talent_release.photos.attach Rack::Test::UploadedFile.new(path, "image/png") + end + end + end +end diff --git a/spec/factories/terms.rb b/spec/factories/terms.rb new file mode 100644 index 0000000..1b37ec9 --- /dev/null +++ b/spec/factories/terms.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :term do + label "Worldwide" + + trait :other do + label "Other" + end + end +end diff --git a/spec/factories/territories.rb b/spec/factories/territories.rb new file mode 100644 index 0000000..a480e84 --- /dev/null +++ b/spec/factories/territories.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :territory do + label "In Perpetuity" + + trait :other do + label "Other" + end + end +end diff --git a/spec/factories/unreleased_appearances.rb b/spec/factories/unreleased_appearances.rb new file mode 100644 index 0000000..c5d4e35 --- /dev/null +++ b/spec/factories/unreleased_appearances.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :unreleased_appearance do + association :video + time_elapsed "1.5" + end +end diff --git a/spec/factories/user_contexts.rb b/spec/factories/user_contexts.rb new file mode 100644 index 0000000..7cde397 --- /dev/null +++ b/spec/factories/user_contexts.rb @@ -0,0 +1,8 @@ +FactoryBot.define do + factory :user_context do + association :user + association :account + + initialize_with { new(user, account) } + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb new file mode 100644 index 0000000..1d60546 --- /dev/null +++ b/spec/factories/users.rb @@ -0,0 +1,57 @@ +FactoryBot.define do + factory :user do + sequence(:email) { |num| "user-#{num}@test.com" } + password_digest { 'password' } + + trait :with_name do + first_name "John" + last_name "Doe" + email "funny@mail.com" + end + + trait :with_different_name do + first_name "Specimen" + last_name "Simpson" + email "different@mail.com" + end + + after(:create) do |user| + if user.accounts.empty? + account = create(:account) + auth = create(:account_auth, user: user, account: account, role: :account_manager) + user.account_auths << auth + end + end + + trait :accountless do + after(:create) do |user| + user.accounts.delete_all + end + end + + trait :admin do + admin true + end + + trait :account_manager do + after(:create) do |user| + user.update(admin: false) + user.account_auths.update(role: :account_manager) + end + end + + trait :manager do + after(:create) do |user| + user.update(admin: false) + user.account_auths.update(role: :project_manager) + end + end + + trait :associate do + after(:create) do |user| + user.update(admin: false) + user.account_auths.update(role: :associate) + end + end + end +end diff --git a/spec/factories/video_release_confirmations.rb b/spec/factories/video_release_confirmations.rb new file mode 100644 index 0000000..a9848fe --- /dev/null +++ b/spec/factories/video_release_confirmations.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :video_release_confirmation do + association :video + association :releasable, factory: :appearance_release + end +end diff --git a/spec/factories/videos.rb b/spec/factories/videos.rb new file mode 100644 index 0000000..b522a96 --- /dev/null +++ b/spec/factories/videos.rb @@ -0,0 +1,33 @@ +FactoryBot.define do + factory :video do + association :project + + file do + path = Rails.root.join("spec", "fixtures", "files", "video_file.mp4") + Rack::Test::UploadedFile.new(path, "video/mp4") + end + + edl_file do + path = Rails.root.join("spec", "fixtures", "files", "sample-edl.edl") + Rack::Test::UploadedFile.new(path, "application/octet-stream") + end + + trait :with_graphics_only_edl_file do + graphics_only_edl_file do + path = Rails.root.join("spec", "fixtures", "files", "sample-edl.edl") + Rack::Test::UploadedFile.new(path, "application/octet-stream") + end + end + + trait :with_audio_only_edl_file do + audio_only_edl_file do + path = Rails.root.join("spec", "fixtures", "files", "sample-edl.edl") + Rack::Test::UploadedFile.new(path, "application/octet-stream") + end + end + + trait :published do + report_published_at 1.day.ago + end + end +end diff --git a/spec/factories/zoom_meetings.rb b/spec/factories/zoom_meetings.rb new file mode 100644 index 0000000..1e28d9d --- /dev/null +++ b/spec/factories/zoom_meetings.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :zoom_meeting do + api_meeting_id "zoom_meeting_id" + association :zoom_user, :with_api_id + end +end diff --git a/spec/factories/zoom_users.rb b/spec/factories/zoom_users.rb new file mode 100644 index 0000000..188b749 --- /dev/null +++ b/spec/factories/zoom_users.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :zoom_user do + trait :with_api_id do + api_id "api_user_id" + end + end +end diff --git a/spec/features/account_manager_managing_account_members_spec.rb b/spec/features/account_manager_managing_account_members_spec.rb new file mode 100644 index 0000000..5ee6ffd --- /dev/null +++ b/spec/features/account_manager_managing_account_members_spec.rb @@ -0,0 +1,117 @@ +require "rails_helper" + +feature "Account manager managing account members" do + scenario "promoting an asssociate to project manager" do + account = create(:account) + account_manager = create(:user, :account_manager, primary_account: account) + associate = create(:user, :associate, primary_account: account, email: "associate.user@test.com") + + sign_in account_manager + visit account_auths_path + + expect(page).to have_content "Associate" + + within "#account_auth_#{associate.account_auths.first.id}" do + select "Project Manager", from: "Role" + click_on "Change Role" + end + + expect(page).to have_content "Role has been updated" + expect(page).to have_content "Project Manager" + end + + scenario "demoting a project manager to associate" do + account = create(:account) + account_manager = create(:user, :account_manager, primary_account: account) + associate = create(:user, :manager, primary_account: account, email: "associate.user@test.com") + + sign_in account_manager + visit account_auths_path + + expect(page).to have_content "Project Manager" + + within "#account_auth_#{associate.account_auths.first.id}" do + select "Associate", from: "Role" + click_on "Change Role" + end + + expect(page).to have_content "Role has been updated" + expect(page).to have_content "Associate" + end + + scenario "promoting an asssociate to account manager" do + account = create(:account) + account_manager = create(:user, :account_manager, primary_account: account) + associate = create(:user, :associate, primary_account: account, email: "associate.user@test.com") + + sign_in account_manager + visit account_auths_path + + expect(page).to have_content "Associate" + + within "#account_auth_#{associate.account_auths.first.id}" do + select "Account Manager", from: "Role" + click_on "Change Role" + end + + expect(page).to have_content "Role has been updated" + expect(page).to have_content "Account Manager" + end + + scenario "demoting an account manager to associate" do + account = create(:account) + account_manager = create(:user, :account_manager, primary_account: account) + associate = create(:user, :account_manager, primary_account: account, email: "associate.user@test.com") + + sign_in account_manager + visit account_auths_path + + expect(page).to have_content "Account Manager" + + within "#account_auth_#{associate.account_auths.first.id}" do + select "Associate", from: "Role" + click_on "Change Role" + end + + expect(page).to have_content "Role has been updated" + expect(page).to have_content "Associate" + end + + scenario "promoting a project_manager to account manager" do + account = create(:account) + account_manager = create(:user, :account_manager, primary_account: account) + associate = create(:user, :manager, primary_account: account, email: "associate.user@test.com") + + sign_in account_manager + visit account_auths_path + + expect(page).to have_content "Project Manager" + + within "#account_auth_#{associate.account_auths.first.id}" do + select "Account Manager", from: "Role" + click_on "Change Role" + end + + expect(page).to have_content "Role has been updated" + expect(page).to have_content "Account Manager" + end + + scenario "demoting an account manager to associate" do + account = create(:account) + account_manager = create(:user, :account_manager, primary_account: account) + associate = create(:user, :account_manager, primary_account: account, email: "associate.user@test.com") + + sign_in account_manager + visit account_auths_path + + expect(page).to have_content "Account Manager" + + within "#account_auth_#{associate.account_auths.first.id}" do + select "Project Manager", from: "Role" + click_on "Change Role" + end + + expect(page).to have_content "Role has been updated" + expect(page).to have_content "Project Manager" + end +end diff --git a/spec/features/admin_managing_accounts_spec.rb b/spec/features/admin_managing_accounts_spec.rb new file mode 100644 index 0000000..0d8b45a --- /dev/null +++ b/spec/features/admin_managing_accounts_spec.rb @@ -0,0 +1,164 @@ +require "rails_helper" + +feature "Admin managing accounts" do + let(:current_user) { create(:user, admin: true, email: "user@test.com") } + let(:another_user) { create(:user, admin: false, email: "user2@test.com", accounts: current_user.accounts) } + let(:project) { create(:project, account: current_user.primary_account) } + + scenario "creates a new account" do + sign_in current_user + visit admin_signed_in_root_path + + click_link "New Account" + fill_in "Name", with: "MGM" + select "ME Suite", from: "Plan" + click_on "Create Account" + + expect(page).to have_content "The account was created" + expect(page).to have_content /MGM/ + expect(page).to have_content /Add Account Manager/ + end + + scenario "sees account details" do + sign_in current_user + visit admin_signed_in_root_path + click_button "Manage" + click_link "Overview" + + expect(page).to have_content "ME Suite" + expect(page).to have_content "Users 1" + expect(page).to have_content "Created at less than a minute ago" + end + + scenario "sees videos for an account in the system" do + visit_account_overview_page + + expect(page).to have_content "Videos" + expect(page).to have_content "Name My Video" + + by "clicking on Analysis link can navigate to video analysis page" do + click_button "Manage" + click_link "Analysis" + + expect(page.current_path).to eq video_video_analyses_path(I18n.locale, @video_1) + end + end + + scenario "re-analyzes the video" do + visit_account_overview_page + + by "clicking on Re-analyze link can navigate to video analysis page" do + click_button "Manage" + click_link "Re-analyze" + + expect(page).to have_content t("video_analyses.create.notice") + expect(page.current_path).to eq video_video_analyses_path(I18n.locale, @video_1) + end + end + + scenario "manages users in an account" do + another_account = create(:account, name: "New Account") + user = create(:user, admin: false, email: "user@example.com") + user.account_auths << AccountAuth.create(user: user, account: current_user.primary_account, role: :account_manager) + + sign_in current_user + visit admin_signed_in_root_path + within "#account_#{current_user.primary_account.id}" do + click_button "Manage" + click_link "Account Managers" + end + + expect(page).to have_content "user@example.com" + end + + scenario "manages an account" do + sign_in current_user + visit admin_signed_in_root_path + click_button "Manage" + click_link "Edit" + + expect(page).to have_content "Edit Account" + + by "updating an account" do + fill_in "Name", with: "Another Team" + click_button "Update Account" + end + + expect(page).to have_content "Another Team" + end + + scenario "sees information for each account in the system" do + mgm = create(:account, name: "MGM", users: build_list(:user, 1, email: "user@mgm.com"), projects: build_list(:project, 1)) + discovery = create(:account, name: "Discovery", users: [build(:user, email: "user1@discovery.com"), build(:user, email: "user2@discovery.com")]) + + video_1 = create(:video, project: mgm.projects.first) + path = Rails.root.join("spec", "fixtures", "files", "video_file_1m.mp4") + longer_file = Rack::Test::UploadedFile.new(path, "video/mp4") + video_2 = create(:video, project: mgm.projects.first, file: longer_file) + video_3 = create(:video, project: mgm.projects.first, file: longer_file, created_at: 1.month.ago) + video_1.file.blob.analyze + video_2.file.blob.analyze + video_3.file.blob.analyze + + sign_in current_user + visit profile_path + click_link "Admin" + + expect(page).to have_content /Name Plan # Projects Monthly Video Upload Minutes Total Video Upload Minutes Total Storage Created At/ + expect(page).to have_content /MGM ME Suite 1 1 minutes 2 minutes 6.74 MB/ + expect(page).to have_content /Discovery ME Suite 0 0 minutes 0 minutes 0 Bytes/ + end + + scenario "Uses the search button to filter accounts", js: true do + sign_in current_user + + create(:account, name: "First account") + create(:account, name: "Second account") + + visit admin_accounts_path + + expect(page).to have_content("First account") + expect(page).to have_content("Second account") + + fill_in "query", with: "First" + click_button "search-button" + + expect(page).to have_content("First account") + expect(page).not_to have_content("Second_account") + end + + scenario "Uses search button to filter videos in account overview", js: true do + sign_in current_user + + account = create(:account, name: "User accound") + project = create(:project, account: account) + create(:video, name: "First Video", project: project) + create(:video, name: "Second Video", project: project) + + visit admin_account_path(account) + + expect(page).to have_content("First Video") + expect(page).to have_content("Second Video") + + fill_in "query", with: "First" + click_button "search-button" + + expect(page).to have_content("First Video") + expect(page).not_to have_content("Second Video") + end + + def visit_account_overview_page + @video_1 = create(:video, project: project) + @video_1.file.blob.analyze + + VideoAnalysis.use_overlay_video = false + allow(BrayniacAI::EdlParse).to receive(:create).and_return( + BrayniacAI::EdlParse.new({ results: [], edl_timecode_start: "00:00:00:00" }) + ) + + sign_in current_user + visit admin_signed_in_root_path + click_button "Manage" + click_link "Overview" + end +end diff --git a/spec/features/admin_managing_users_spec.rb b/spec/features/admin_managing_users_spec.rb new file mode 100644 index 0000000..8d80d10 --- /dev/null +++ b/spec/features/admin_managing_users_spec.rb @@ -0,0 +1,123 @@ +require "rails_helper" + +feature "Admin managing users" do + let(:current_user) { create(:user, admin: true, email: "user@test.com") } + let(:project) { create(:project, account: current_user.primary_account) } + + before do + sign_in current_user + end + + scenario "creates a new user" do + visit admin_users_path + + click_link "New User" + fill_in "Email", with: "bob@example.com" + fill_in "Password", with: "password" + select "My Team", from: "Account" + check "Admin User" + select "Account Manager", from: "Role" + click_on "Create User" + + expect(page).to have_content "The user was created" + end + + scenario "sees list of users" do + visit admin_users_path + + expect(page).to have_content "user@test.com" + expect(page).to have_content "account_manager" + expect(page).to have_content current_user.primary_account.name + end + + context "searchs for users" do + before do + create(:user, :with_name) + create(:user, :with_different_name) + visit admin_users_path + end + + scenario "shows all users when query is empty", js: true do + expect(page).to have_selector "form#search" + expect(page).to have_content "John" + expect(page).to have_content "Specimen" + end + + scenario "shows only users matching search query", js: true do + within "form#search" do + fill_in "Search users", with: "John" + click_on "button" + end + expect(page).to have_content "John" + expect(page).not_to have_content "Specimen" + end + + scenario "shows no users matching search query", js: true do + within "form#search" do + fill_in "Search users", with: "nonexisting" + click_on "button" + end + expect(page).not_to have_content "John" + expect(page).not_to have_content "Specimen" + end + end + + scenario "manages users" do + another_user = create(:user, admin: false, email: "user2@test.com", accounts: current_user.accounts) + note_author_user = create(:user, admin: false, email: "writer@test.com", accounts: current_user.accounts) + create(:note, user: note_author_user, email: note_author_user.email) + + visit admin_users_path + + by "clicking Edit button can update existing users" do + within "#user_#{another_user.id}" do + click_on "Manage" + click_link "Edit" + end + + expect(page).to have_content("Edit User") + + fill_in "Email", with: "user-updated@example.com" + check "Admin User" + click_button "Update User" + + expect(page).not_to have_content("user2@test.com") + + click_link "Users" + + expect(page).to have_content("user-updated@example.com associate") + end + + by "clicking Delete button can remove existing users" do + sign_in current_user + visit admin_users_path + + within "#user_#{another_user.id}" do + click_on "Manage" + click_link "Delete" + end + + expect(page).not_to have_content "user2@test.com" + expect(page).to have_content user_deleted_message + end + + by "clicking Delete button can remote users who posted ntoes" do + sign_in current_user + visit admin_users_path + + within "#user_#{note_author_user.id}" do + click_on "Manage" + click_link "Delete" + end + + expect(page).not_to have_content "writer@test.com" + expect(page).to have_content user_deleted_message + end + end + + private + + def user_deleted_message + t "admin.users.destroy.alert" + end +end diff --git a/spec/features/admin_masquerading_as_another_user_spec.rb b/spec/features/admin_masquerading_as_another_user_spec.rb new file mode 100644 index 0000000..62fb798 --- /dev/null +++ b/spec/features/admin_masquerading_as_another_user_spec.rb @@ -0,0 +1,36 @@ +require "rails_helper" + +feature "Admin masquerading as an user" do + let!(:current_user) { create(:user, admin: true, email: "me@test.com") } + let!(:another_user) { create(:user, admin: false, email: "them@test.com") } + let(:project) { create(:project, account: current_user.primary_account) } + + scenario "masquerades as user and stops" do + my_project = create(:project, name: "My Project", account: current_user.primary_account) + another_project = create(:project, name: "Their Project", account: another_user.primary_account) + sign_in current_user + visit admin_users_path + + click_link "Masquerade" + + expect(page).not_to have_content("me@test.com") + expect(page).to have_content("them@test.com") + expect(page).to have_content("Their Project") + expect(page).not_to have_content("My Project") + expect(page).to have_content "Stop Masquerading" + + click_link "Stop Masquerading" + + expect(page).to have_content("me@test.com") + expect(page).to have_content("them@test.com") + expect(page).to have_content("Masquerade") + + visit projects_path + + expect(page).to have_content("me@test.com") + expect(page).not_to have_content("them@test.com") + expect(page).to have_content("My Project") + expect(page).not_to have_content("Their Project") + expect(page).not_to have_content "Stop Masquerading" + end +end diff --git a/spec/features/guest_account_signup_spec.rb b/spec/features/guest_account_signup_spec.rb new file mode 100644 index 0000000..4e1177a --- /dev/null +++ b/spec/features/guest_account_signup_spec.rb @@ -0,0 +1,41 @@ +require "rails_helper" + +feature "Guest account sign up" do + scenario "creates a new account and signs in successfully" do + visit new_account_path + + fill_in "Email", with: "user+1@test.com" + fill_in "Password", with: "password" + fill_in "Account Name", with: "Test Account" + + click_on "Start Free Trial" + + expect(page).to have_content "We are excited to help you organize and automate your media projects. Click below to create your first project and get started." + expect(page).to have_content "Welcome" + expect(page).to have_link "Create Your First Project" + end + + scenario "navivates to new account page when account creation fails" do + visit new_account_path + + fill_in "Email", with: "user+1@test.com" + fill_in "Password", with: "password" + fill_in "Account Name", with: "" + + click_on "Start Free Trial" + + expect(page).to have_content "Sign Up" + end + + scenario "navivates to new account page when user creation fails" do + visit new_account_path + + fill_in "Email", with: "" + fill_in "Password", with: "password" + fill_in "Account Name", with: "Test Account" + + click_on "Start Free Trial" + + expect(page).to have_content "Sign Up" + end +end diff --git a/spec/features/user_creates_note_spec.rb b/spec/features/user_creates_note_spec.rb new file mode 100644 index 0000000..9ab6ffa --- /dev/null +++ b/spec/features/user_creates_note_spec.rb @@ -0,0 +1,106 @@ +require "rails_helper" + +feature "User creates notes" do + let(:current_user) { create(:user) } + let(:project) { create(:project, account: current_user.primary_account) } + + shared_examples "a notable collection UI", js: true do + specify do + sign_in current_user + visit polymorphic_path [subject.project, subject.model_name.plural] + + by "not adding content to the note" do + open_notes_modal(subject) + submit_notes_modal(subject, notes: "") + expect(page).to have_css(".invalid-feedback", text: "can't be blank") + end + + by "adding content to the note" do + submit_notes_modal(subject, notes: "Test note!") + expect(page).to have_note_for(subject, "Test note!") + end + + by "adding another note" do + add_notes_to(subject, notes: "Another note") + expect(page).to have_note_for(subject, "Another note") + expect(page).to have_more_notes_for(subject) + end + + by "viewing all the notes" do + open_notes_list_for(subject) + expect(page).to have_css(".list-group-item", text: "Test note!") + expect(page).to have_css(".list-group-item", text: "Another note") + end + end + + def open_notes_modal(release) + click_button "Manage" + click_link "Notes" + end + + def submit_notes_modal(release, notes:) + action = url_for([release, :notes, only_path: true]) + notes_form = "form[action='#{action}']" + + within notes_form do + fill_in "Content", with: notes + click_button "Create Note" + end + end + + def add_notes_to(release, notes:) + open_notes_modal(release) + submit_notes_modal(release, notes: notes) + end + + def have_note_for(release, notes) + have_selector("td", text: notes) + end + + def have_more_notes_for(release) + notes_path = url_for [release, :notes, only_path: true] + have_link("more note", href: notes_path) # , visible: :all) + end + + def open_notes_list_for(release) + notes_path = url_for [release, :notes, only_path: true] + click_link "more note", href: notes_path + end + end + + context "for appearance releases" do + subject! { create(:appearance_release, project: project, notes: []) } + + it_behaves_like "a notable collection UI" + end + + context "for talent releases" do + subject! { create(:talent_release, project: project, notes: []) } + + it_behaves_like "a notable collection UI" + end + + context "for location releases" do + subject! { create(:location_release, project: project, notes: []) } + + it_behaves_like "a notable collection UI" + end + + context "for material releases" do + subject! { create(:material_release, project: project, notes: []) } + + it_behaves_like "a notable collection UI" + end + + context "for acquired media releases" do + subject! { create(:acquired_media_release, project: project, notes: []) } + + it_behaves_like "a notable collection UI" + end + + context "for music releases" do + subject! { create(:music_release, project: project, notes: []) } + + it_behaves_like "a notable collection UI" + end +end diff --git a/spec/features/user_creates_tags_spec.rb b/spec/features/user_creates_tags_spec.rb new file mode 100644 index 0000000..028ee2c --- /dev/null +++ b/spec/features/user_creates_tags_spec.rb @@ -0,0 +1,89 @@ +require "rails_helper" + +feature "User creates tags" do + let(:current_user) { create(:user) } + let(:project) { create(:project, account: current_user.primary_account) } + + shared_examples "a taggable collection UI", js: true do + specify do + sign_in current_user + visit polymorphic_path [subject.project, subject.model_name.plural] + + by "adding tag" do + open_tags_modal(subject) + submit_tags_modal(subject, tags: "Test tag!") + expect(page).to have_content("Test tag!") + click_button "Close" + within "##{ActionController::Base.helpers.dom_id(subject, "tags_preview")}" do + expect(page).to have_content("Test tag!") + end + end + + by "removing tag" do + open_tags_modal(subject) + submit_tags_modal(subject, tags: "Test tag!") + remove_tags + expect(page).not_to have_content("Test tag!") + click_button "Close" + within "##{ActionController::Base.helpers.dom_id(subject, "tags_preview")}" do + expect(page).not_to have_content("Test tag!") + end + end + end + + def open_tags_modal(release) + click_button "Manage" + click_link "Tags" + end + + def submit_tags_modal(release, tags:) + action = url_for([release, :acts_as_taggable_on_tags, only_path: true]) + tags_form = "form[action='#{action}']" + + within tags_form do + fill_in "Name", with: tags + click_button "Add" + end + end + + def remove_tags + click_link "Remove" + end + end + + context "for appearance releases" do + subject! { create(:appearance_release, project: project, tags: []) } + + it_behaves_like "a taggable collection UI" + end + + context "for talent releases" do + subject! { create(:talent_release, project: project, tags: []) } + + it_behaves_like "a taggable collection UI" + end + + context "for location releases" do + subject! { create(:location_release, project: project, tags: []) } + + it_behaves_like "a taggable collection UI" + end + + context "for material releases" do + subject! { create(:material_release, project: project, tags: []) } + + it_behaves_like "a taggable collection UI" + end + + context "for acquired media releases" do + subject! { create(:acquired_media_release, project: project, tags: []) } + + it_behaves_like "a taggable collection UI" + end + + context "for music releases" do + subject! { create(:music_release, project: project, tags: []) } + + it_behaves_like "a taggable collection UI" + end +end diff --git a/spec/features/user_has_cookies_disabled_spec.rb b/spec/features/user_has_cookies_disabled_spec.rb new file mode 100644 index 0000000..eedc9ae --- /dev/null +++ b/spec/features/user_has_cookies_disabled_spec.rb @@ -0,0 +1,9 @@ +require "rails_helper" + +feature "User has cookies disabled" do + scenario "the cookie disabled page", js: true do + visit cookies_disabled_path + + expect(page).to have_content("Cookies are disabled") + end +end diff --git a/spec/features/user_imports_release_templates_spec.rb b/spec/features/user_imports_release_templates_spec.rb new file mode 100644 index 0000000..f9e0131 --- /dev/null +++ b/spec/features/user_imports_release_templates_spec.rb @@ -0,0 +1,54 @@ +require "rails_helper" + +feature "User imports release templates", type: :feature do + let(:current_user) { create(:user, :account_manager) } + let(:project) { create(:project, members: current_user, account: current_user.primary_account) } + let(:project_one) { create(:project, name: "Avengers", members: [current_user], account: current_user.primary_account) } + let!(:project_one_template) { create(:contract_template, name: "First Contract Template", fee: 50, release_type: "appearance", project: project_one) } + let(:project_two) { create(:project, name: "Justice League", members: [], account: current_user.primary_account) } + let!(:project_two_template) { create(:contract_template, name: "Second Contract Template", fee: 50, release_type: "talent", project: project_two) } + + before do + sign_in(current_user) + end + + scenario "importing two existing templates into a project" do + visit project_contract_templates_path(project) + click_on "Import Release Template" + select_templates([project_one_template.id, project_two_template.id]) + click_on "Import Selected Templates" + expect(page).to have_content("Selected templates were imported with success") + expect(page).to have_content("First Contract Template") + expect(page).to have_content("Second Contract Template") + end + + scenario "preventing import of already imported template" do + release_template_ids = [project_one_template.id, project_two_template.id] + project.import_contract_templates(release_template_ids) + + visit project_contract_templates_path(project) + expect(page).not_to have_button("Import Selected Templates") + end + + scenario "searching for a template", js: true do + visit project_contract_templates_path(project) + click_on "Import Release Template" + fill_in "query", with: "Second" + click_on "search-button" + expect(page).not_to have_content("First Contract Template") + expect(page).to have_content("Second Contract Template") + + fill_in "query", with: "Avengers" + click_on "search-button" + expect(page).to have_content("First Contract Template") + expect(page).not_to have_content("Second Contract Template") + end + + private + + def select_templates(template_ids) + template_ids.each do |id| + find(:css, "input[name='template_ids[]'][value='#{id}']").set(true) + end + end +end \ No newline at end of file diff --git a/spec/features/user_lists_directories_spec.rb b/spec/features/user_lists_directories_spec.rb new file mode 100644 index 0000000..e1ff55b --- /dev/null +++ b/spec/features/user_lists_directories_spec.rb @@ -0,0 +1,49 @@ +require "rails_helper" + +RSpec.feature "User can see directories on project page", type: :feature do + before do + sign_in(user) + end + + let!(:project) { create(:project_with_directories, members: user, account: user.primary_account) } + + context "User is an associate" do + let(:user) { create(:user, :associate) } + + scenario "user is associate" do + visit project_path(project) + + expect(page).to have_link("Add Folder") + expect(page).to have_content("Shared") + expect(page).not_to have_content("Financial Documents") + expect(page).not_to have_content("Salaries") + end + end + + context "User is a project manager" do + let(:user) { create(:user, :manager) } + + scenario "user is associate" do + visit project_path(project) + + expect(page).to have_link("Add Folder") + expect(page).to have_content("Shared") + expect(page).to have_content("Financial Documents") + expect(page).not_to have_content("Salaries") + end + end + + context "User is a account manager" do + let(:user) { create(:user, :account_manager) } + let!(:project) { create(:project_with_directories, account: user.primary_account) } + + scenario "user is associate" do + visit project_path(project) + + expect(page).to have_link("Add Folder") + expect(page).to have_content("Shared") + expect(page).to have_content("Financial Documents") + expect(page).to have_content("Salaries") + end + end +end diff --git a/spec/features/user_manages_contract_templates_spec.rb b/spec/features/user_manages_contract_templates_spec.rb new file mode 100644 index 0000000..1af8733 --- /dev/null +++ b/spec/features/user_manages_contract_templates_spec.rb @@ -0,0 +1,240 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.feature 'User manages contract templates', type: :feature do + let(:current_user) { create(:user, :manager) } + let(:project) { create(:project, members: current_user, account: current_user.primary_account) } + + before do + sign_in(current_user) + end + + scenario 'creating a new release template' do + visit new_project_contract_template_path(project) + + fill_in 'Name', with: 'My Release Template' + select 'Appearance Release', from: 'Release type' + fill_in_trix body_field, with: 'You agree to this release.' + fill_hidden guardian_clause_field, with: 'Your minor agrees to this release.' + select 'All', from: 'Applicable Media' + select 'Other', from: 'Territory' + fill_in 'Describe other territory', with: 'North America only' + select 'In perpetuity', from: 'Term' + select 'None', from: 'Restriction' + click_on 'Create Release Template' + + expect(page).to have_content('The release template has been created') + end + + scenario 'preview new talent release template without guardian clause' do + visit new_project_contract_template_path(project) + select 'Talent Release', from: 'Release type' + + click_on 'Preview' + expect(content_type).to eq('application/pdf') + expect(content_disposition).to include('inline') + expect(pdf_body).to have_content('PREVIEW ONLY') + expect(pdf_body).to have_content('Talent Release') + expect(pdf_body).to have_content('Body text goes here') + expect(pdf_body).to have_content('Guardian') + end + + scenario 'preview new talent release template with guardian clause' do + visit new_project_contract_template_path(project) + select 'Talent Release', from: 'Release type' + fill_hidden guardian_clause_field, with: 'Your minor agrees to this release.' + + click_on 'Preview' + expect(content_type).to eq('application/pdf') + expect(content_disposition).to include('inline') + expect(pdf_body).to have_content('PREVIEW ONLY') + expect(pdf_body).to have_content('Talent Release') + expect(pdf_body).to have_content('Body text goes here') + expect(pdf_body).to have_content('Guardian') + expect(pdf_body).to have_content('Your minor') + end + + scenario 'preview new appearance release template without guardian clause' do + visit new_project_contract_template_path(project) + select 'Appearance Release', from: 'Release type' + + click_on 'Preview' + expect(content_type).to eq('application/pdf') + expect(content_disposition).to include('inline') + expect(pdf_body).to have_content('PREVIEW ONLY') + expect(pdf_body).to have_content('Appearance Release') + expect(pdf_body).to have_content('Body text goes here') + expect(pdf_body).to have_content('Guardian') + end + + scenario 'preview new appearance release template with guardian clause' do + visit new_project_contract_template_path(project) + select 'Appearance Release', from: 'Release type' + fill_hidden guardian_clause_field, with: 'Your minor agrees to this release.' + + click_on 'Preview' + expect(content_type).to eq('application/pdf') + expect(content_disposition).to include('inline') + expect(pdf_body).to have_content('PREVIEW ONLY') + expect(pdf_body).to have_content('Appearance Release') + expect(pdf_body).to have_content('Body text goes here') + expect(pdf_body).to have_content('Guardian') + expect(pdf_body).to have_content('Your minor') + end + + scenario 'different release type generates different pdf' do + visit new_project_contract_template_path(project) + select 'Acquired Media Release', from: 'Release type' + click_on 'Preview' + expect(content_type).to eq('application/pdf') + expect(content_disposition).to include('inline') + expect(pdf_body).to have_content('PREVIEW ONLY') + expect(pdf_body).to have_content('Acquired Media Release') + expect(pdf_body).not_to have_content('Guardian') + + visit new_project_contract_template_path(project) + select 'Location Release', from: 'Release type' + click_on 'Preview' + expect(content_type).to eq('application/pdf') + expect(content_disposition).to include('inline') + expect(pdf_body).to have_content('PREVIEW ONLY') + expect(pdf_body).to have_content('Location Release') + expect(pdf_body).not_to have_content('Guardian') + + visit new_project_contract_template_path(project) + select 'Material Release', from: 'Release type' + click_on 'Preview' + expect(content_type).to eq('application/pdf') + expect(content_disposition).to include('inline') + expect(pdf_body).to have_content('PREVIEW ONLY') + expect(pdf_body).to have_content('Material Release') + expect(pdf_body).not_to have_content('Guardian') + end + + context 'preventing creation of release template with wrong fee value' do + before do + visit new_project_contract_template_path(project) + + fill_in 'Name', with: 'My Release Template' + select 'Appearance Release', from: 'Release type' + fill_in_trix body_field, with: 'You agree to this release.' + fill_hidden guardian_clause_field, with: 'Your minor agrees to this release.' + select 'All', from: 'Applicable Media' + select 'Other', from: 'Territory' + fill_in 'Describe other territory', with: 'North America only' + select 'In perpetuity', from: 'Term' + select 'None', from: 'Restriction' + end + + scenario 'Should not allow negative fees' do + fill_in 'Fee', with: '-200' + click_on 'Create Release Template' + expect(page).not_to have_content('The release template has been created') + end + + scenario 'Should not allow fees with more than 9 digits' do + fill_in 'Fee', with: '9999999999' + click_on 'Create Release Template' + expect(page).not_to have_content('The release template has been created') + end + end + + scenario 'contract template preview is shown before printing' do + create(:appearance_release_contract_template, body: 'Contract legal language', project: project) + visit project_contract_templates_path(project) + click_on 'Manage' + expect(page).to have_content('Print') + + click_link 'Print' + + expect(page).to have_content preview_heading + expect(page).to have_selector 'embed' + end + + scenario 'printing blank release template' do + create(:appearance_release_contract_template, body: 'Contract legal language', project: project) + visit project_contract_templates_path(project) + click_on 'Manage' + expect(page).to have_content('Print') + + click_link 'Print' + + fill_in 'Number of copies', with: '-1' + click_on t('shared.print') + expect(page).to have_content(t('contract_templates.blank_contracts.create.number_of_copies_invalid_notice')) + + fill_in 'Number of copies', with: 2 + click_on t('shared.print') + + expect(content_type).to eq('application/pdf') + expect(content_disposition).to include('inline') + expect(pdf_body).to have_content('Contract legal language', count: 2) + expect(pdf_body).to have_content('Appearance Release', count: 2) + expect(pdf_body).to have_content('Name: _____', count: 4) #2 times for person name and 2 times for guardian name + expect(pdf_body).to have_content(t 'blank_contracts.pdf.do_not_copy_warning', count: 2) + end + + scenario 'archiving an existing release template', js: true do + create(:contract_template, project: project) + + visit project_contract_templates_path(project) + + expect(page).to have_content('Test template') + + click_on 'Manage' + accept_alert do + click_on 'Archive' + end + + expect(page).to have_content('The release template has been archived') + expect(page).not_to have_content('Test template') + end + + context 'When the user is associate' do + let(:current_user) { create(:user, :associate) } + + it 'does not show management buttons for release templates' do + visit project_contract_templates_path(project) + + expect(page).not_to have_content('Create New Release Template') + expect(page).not_to have_content('Import Release Template') + expect(page).not_to have_content('Delete') + end + end + + context 'When the user is account manager' do + let(:current_user) { create(:user, :account_manager) } + + it 'shows archive button for release template' do + create(:contract_template, project: project) + + visit project_contract_templates_path(project) + + click_on 'Manage' + expect(page).to have_content('Archive') + end + end + + private + + def preview_heading + t 'blank_contracts.new.preview_heading' + end + + def fill_in_trix(id, with:) + find("trix-editor##{id}").click.set(with) + end + + def body_field + 'contract_template_body' + end + + def guardian_clause_field + 'contract_template_guardian_clause_trix_input_contract_template' + end + + def fill_hidden(id, with:) + find(:xpath, "//input[@id='#{id}']", visible: false).set with + end +end diff --git a/spec/features/user_manages_profile_spec.rb b/spec/features/user_manages_profile_spec.rb new file mode 100644 index 0000000..c225831 --- /dev/null +++ b/spec/features/user_manages_profile_spec.rb @@ -0,0 +1,97 @@ +require "rails_helper" + +RSpec.feature "User can update his profile", type: :feature do + let(:user) { create(:user, :with_name) } + + before do + sign_in(user) + end + + scenario "user clicks profile dropdown" do + visit signed_in_root_path + + click_on "John Doe" + + expect(page).to have_link("Profile Settings", href: profile_path) + expect(page).to have_link("Sign Out", href: session_path) + end + + scenario "visits profile page" do + visit profile_path + + expect(page).to have_content("First name") + expect(page).to have_content("Last name") + end + + scenario "updates first and last name" do + visit profile_path + + within "form" do + fill_in "First name", with: "John" + fill_in "Last name", with: "Doe" + click_on "Update Profile" + end + + expect(page).to have_content("John Doe") + expect(page).to have_content("JD") + end + + scenario "updates avatar" do + visit profile_path + + within "form" do + attach_file "user[avatar]", Rails.root.join(file_fixture("person_photo.png")), visible: false + fill_in "First name", with: "John" + fill_in "Last name", with: "Doe" + click_on "Update Profile" + end + + expect(page).to have_content("Profile has been updated successfully") + expect(page).to have_content("John Doe") + expect(page).to have_photo("person_photo.png") + end + + context "user is admin" do + let(:user) { create(:user, :with_name, :admin) } + + scenario "admin visits user listing" do + visit admin_users_path + + expect(page).to have_content("First Name") + expect(page).to have_content("Last Name") + expect(page).to have_content("John") + expect(page).to have_content("Doe") + end + + scenario "admin clicks profile dropdown" do + visit admin_users_path + + click_on "John Doe" + + expect(page).to have_link("BIG Admin", href: admin_signed_in_root_path) + expect(page).to have_link("Profile Settings", href: profile_path) + expect(page).to have_link("Account Settings", href: account_auths_path) + expect(page).to have_link("Sign Out", href: session_path) + end + end + + context "user is account manager" do + let(:user) { create(:user, :with_name, :account_manager) } + + scenario "account manager clicks profile dropdown" do + visit signed_in_root_path + + click_on "John Doe" + + expect(page).to have_link("Profile Settings", href: profile_path) + expect(page).to have_link("Account Settings", href: account_auths_path) + expect(page).to have_link("Sign Out", href: session_path) + end + end + + private + + def have_photo(filename) + have_selector("img[src*='#{filename}']") + end +end diff --git a/spec/features/user_manages_project_directories_spec.rb b/spec/features/user_manages_project_directories_spec.rb new file mode 100644 index 0000000..6ad5bc3 --- /dev/null +++ b/spec/features/user_manages_project_directories_spec.rb @@ -0,0 +1,107 @@ +require "rails_helper" + +RSpec.feature "User manages project custom folders", type: :feature do + let(:user) { create(:user, :manager) } + let(:project) { create(:project, members: user, account: user.primary_account) } + + before do + sign_in(user) + end + + scenario "creating a new folder" do + visit new_project_directory_path(project) + + fill_in "Name", with: "Actors" + select "Videos", from: "Category" + + click_on "Create Folder" + + expect(page).to have_content("The folder has been created") + end + + scenario "updating a folder" do + dir = create(:directory) + visit edit_project_directory_path(project, dir) + + fill_in "Name", with: "New Actors" + select "Videos", from: "Category" + + click_on "Update Folder" + + expect(page).to have_content("The folder has been updated") + end + + scenario "deleting an existing folder", js: true do + create(:directory, project: project) + visit project_path(project) + + expect(page).to have_content("Payrolls") + + click_on "button" + accept_alert do + click_on "Delete Folder" + end + + expect(page).to have_content("The folder has been deleted") + end + + scenario "adding files to a folder", js: true do + dir = create(:directory) + visit new_file_project_directory_path(project, dir) + + expect(page).to have_content("UPLOAD NEW FILES") + + drop_file Rails.root.join(file_fixture("person_photo.png")), type: :dropzone + + click_button "Upload Files" + expect(page).to have_content("The folder has been updated") + expect(page).to have_content("person_photo.png") + end + + scenario "folder's show page should show uploaded files", js: true do + dir_with_files = create(:directory, :with_files) + visit project_directory_path(project, dir_with_files) + + expect(page).to have_content("location_photo.png") + end + + scenario "Listed file should have a manage button", js: true do + dir_with_files = create(:directory, :with_files) + visit project_directory_path(project, dir_with_files) + + expect(page).to have_content("location_photo.png") + + click_on "Manage" + expect(page).to have_link("Download File") + expect(page).to have_link("Delete File") + end + + scenario "searching for a file", js: true do + dir_with_many_files = create(:directory, :many_files, project: project) + + visit project_directory_path(project, dir_with_many_files) + + within "form" do + fill_in "Search Files", with: "location" + click_on "button" + end + + expect(page).to have_content("location_photo.png") + expect(page).not_to have_content("material_photo.png") + expect(page).not_to have_content("person_photo.png") + expect(page).to have_field("Search Files", with: "location") + end + + scenario "User can delete a file", js: true do + dir_with_files = create(:directory, :with_files) + visit project_directory_path(project, dir_with_files) + + click_on "Manage" + + accept_alert do + click_on "Delete File" + end + + expect(page).to have_content("File deleted successfully") + end +end diff --git a/spec/features/user_managing_acquired_media_releases_spec.rb b/spec/features/user_managing_acquired_media_releases_spec.rb new file mode 100644 index 0000000..b5832b3 --- /dev/null +++ b/spec/features/user_managing_acquired_media_releases_spec.rb @@ -0,0 +1,294 @@ +require "rails_helper" + +feature "User managing acquired_media releases" do + let(:current_user) { create(:user, :manager) } + let(:project) { create(:project, members: current_user, account: current_user.primary_account) } + + context "when signed out" do + scenario "creating a release", js: true do + contract_template = create(:contract_template, project: project) + + visit new_account_project_contract_template_acquired_media_release_path(project.account, project, contract_template) + + by "filling out the form" do + fill_in acquired_media_name_field, with: "Jane Doe" + acquired_media_category_fields + + draw_signature file_fixture("signature.png"), "acquired_media_release_signature_base64" + end + + click_button "I have read and agree to the above" + + expect(AcquiredMediaRelease.last.categories).to include("Artwork") + expect(AcquiredMediaRelease.last.categories).to include("Still Photograph") + expect(page).to have_content("Your release was successfully submitted. Thank you.") + end + end + + context "when signed in" do + scenario "creating, updating, destroying a release", js: true do + release_data = { + name: "Test Acquired Media Release", + applicable_media: ApplicableMedium.last.label, + territory: Territory.last.label, + term: Term.last.label, + restriction: Restriction.first.label, + restriction_text: "Not available in China", + } + + sign_in current_user + visit new_project_acquired_media_release_path(project) + + by "attaching only a contract" do + attach_file "acquired_media_release[contract]", Rails.root.join(file_fixture("contract.pdf")), visible: false + click_button create_release_button + + expect(page).to have_invalid_field(acquired_media_name_field) + end + + by "attaching files" do + drop_file Rails.root.join(file_fixture("video_file.mp4")), type: "file-info-dropzone" + click_button create_release_button + + expect(page).to have_invalid_field(acquired_media_name_field) + end + + by "filling out the remaining information" do + fill_in_release_fields release_data + click_button create_release_button + + expect(page).to have_content(create_release_notice) + expect(page).to have_content("1") + + click_on "Manage" + expect(page).to have_link("Download") + end + + it_also "updates an existing release" do + click_link "Edit" + + within ".dropzone" do + expect(page).to have_filename("video_file.mp4") + end + + expect(page).to have_filled_in_data(release_data) + + fill_in_release_fields name: "New name" + drop_file Rails.root.join(file_fixture("person_photo.png")), type: "file-info-dropzone" + click_button update_release_button + + expect(page).to have_content(update_release_notice) + expect(page).to have_content("New name") + expect(page).to have_content("2") + end + + it_also "deletes an existing release" do + click_button "Manage" + accept_alert do + click_link "Delete" + end + + expect(page).not_to have_content("New name") + end + end + + scenario "viewing the contract PDF" do + acquired_media_release = create(:acquired_media_release_with_contract_template, + :native, + project: project, + person_name: "Jane Doe", + tag_list: "Woman, Brunette", + notes: [ + build(:note, + content: "Note 1", + user: build(:user, email: "jane.doe@test.com"), + email: "jane.doe@test.com", + created_at: DateTime.new(2020, 2, 21, 12, 0, 0), + ), + build(:note, + content: "Note 2", + user: build(:user, email: "john.doe@test.com"), + email: "john.doe@test.com", + created_at: DateTime.new(2020, 2, 20, 11, 0, 0), + ), + ], + file_infos: + [ + build(:file_info, + filename: "aaa.jpg", + content_type: "image/jpeg"), + build(:file_info, + filename: "bbb.mp4", + content_type: "video/mp4"), + build(:file_info, + filename: "unknown.doc", + content_type: "unknown/file") + ] + + ) + + sign_in(current_user) + visit project_acquired_media_releases_path(project) + click_link *view_release_pdf_link_for(acquired_media_release) + + expect(content_type).to eq("application/pdf") + expect(pdf_body).to have_content("NOTES") + expect(pdf_body).to have_content("Note 1") + expect(pdf_body).to have_content("jane.doe@test.com") + expect(pdf_body).to have_content("2/21/20 12:00 PM") + expect(pdf_body).to have_content("Note 2") + expect(pdf_body).to have_content("john.doe@test.com") + expect(pdf_body).to have_content("2/20/20 11:00 AM") + expect(pdf_body).to have_content("TAGS") + expect(pdf_body).to have_content("Woman") + expect(pdf_body).to have_content("Brunette") + expect(pdf_body).to have_content("FILES") + expect(pdf_body).to have_content("Photos") + expect(pdf_body).to have_content("Videos") + expect(pdf_body).to have_content("aaa.jpg") + expect(pdf_body).to have_content("bbb.mp4") + expect(pdf_body).to have_content("unknown.doc") + expect(pdf_body).to have_content("Other files") + end + + scenario "searching for a release", js: true do + collection1 = create(:acquired_media_release, name: "EDM Music", project: project) + collection2 = create(:acquired_media_release, name: "Classical Music", project: project) + + sign_in current_user + visit project_acquired_media_releases_path(project) + + within "form#search" do + fill_in "Search", with: "EDM" + click_on "button" + end + + expect(page).to have_content("EDM Music") + expect(page).not_to have_content("Classical Music") + expect(page).to have_field("Search", with: "EDM") + end + + scenario "edit is visible for non-native release" do + create(:acquired_media_release_with_contract_template, name: "EDM Music", project: project) + + sign_in current_user + visit project_acquired_media_releases_path(project) + + click_on "Manage" + expect(page).to have_link("Edit", exact: true) + end + + scenario "edit is not visible for native release" do + create(:acquired_media_release_with_contract_template, :native, name: "EDM Music", project: project) + + sign_in current_user + visit project_acquired_media_releases_path(project) + + click_on "Manage" + expect(page).not_to have_link("Edit", exact: true) + end + + context "when the user is associate" do + let(:current_user) { create(:user, :associate) } + + scenario "should not show download" do + collection1 = create(:acquired_media_release_with_contract_template, name: "EDM Music", project: project) + + sign_in current_user + visit project_acquired_media_releases_path(project) + + click_on "Manage" + expect(page).not_to have_link("Download", exact: true) + end + end + end + + private + + def acquired_media_name_field + "acquired_media_release[name]" + end + + def acquired_media_category_fields + find(:css, "#acquired_media_release_categories_artwork").set(true) + find(:css, "#acquired_media_release_categories_still_photograph").set(true) + end + + def have_filename(filename) + have_selector("span[data-dz-name]", text: filename) + end + + def import_acquired_media_release_link(project) + ["Import Release", href: new_project_acquired_media_release_path(project)] + end + + def update_acquired_media_release_link(acquired_media_release) + ["Edit", href: edit_acquired_media_release_path(acquired_media_release)] + end + + def destroy_acquired_media_release_link(acquired_media_release) + ["Delete", href: acquired_media_release_path(acquired_media_release)] + end + + def fill_in_release_fields(data) + fill_in "acquired_media_release[name]", with: data[:name] + + select_exploitable_right "applicable_medium", data[:applicable_medium] + select_exploitable_right "territory", data[:territory] + select_exploitable_right "term", data[:term] + select_exploitable_right "restriction", data[:restriction] + + fill_in_exploitable_right_field("applicable_medium", data[:applicable_medium_text]) + fill_in_exploitable_right_field("territory", data[:territory_text]) + fill_in_exploitable_right_field("term", data[:term_text]) + fill_in_exploitable_right_field("restriction", data[:restriction_text]) + end + + def select_exploitable_right(name, value) + if value.present? + select value, from: "acquired_media_release[#{name}_id]" + end + end + + def fill_in_exploitable_right_field(name, text) + if text.present? + fill_in "acquired_media_release[#{name}_text]", with: text + end + end + + def view_release_pdf_link_for(acquired_media_release) + ["Download", href: acquired_media_release_contracts_path(acquired_media_release, format: "pdf")] + end + + def have_filled_in_data(data) + have_field "acquired_media_release[name]", with: data[:name] + have_field "acquired_media_release[acquired_medium_id]", with: data[:acquired_medium] + have_field "acquired_media_release[acquired_medium_text]", with: data[:acquired_medium_text] + have_field "acquired_media_release[territory_id]", with: data[:territory] + have_field "acquired_media_release[territory_text]", with: data[:territory_text] + have_field "acquired_media_release[term_id]", with: data[:term] + have_field "acquired_media_release[term_text]", with: data[:term_text] + have_field "acquired_media_release[restriction_id]", with: data[:restriction] + have_field "acquired_media_release[restriction_text]", with: data[:restriction_text] + end + + def create_release_button + t "helpers.submit.acquired_media_release.create" + end + + def create_release_notice + t "acquired_media_releases.create.notice" + end + + def update_release_button + t "helpers.submit.acquired_media_release.update" + end + + def update_release_notice + t "acquired_media_releases.update.notice" + end + + def destroy_release_alert + t "acquired_media_releases.destroy.alert" + end +end diff --git a/spec/features/user_managing_appearance_releases_spec.rb b/spec/features/user_managing_appearance_releases_spec.rb new file mode 100644 index 0000000..28eeb2b --- /dev/null +++ b/spec/features/user_managing_appearance_releases_spec.rb @@ -0,0 +1,552 @@ +# frozen_string_literal: true + +require 'rails_helper' + +feature 'User managing appearance releases' do + let(:current_user) { create(:user, :manager) } + let(:project) { create(:project, members: current_user, account: current_user.primary_account) } + + context 'when signed out' do + scenario 'creating a release for an adult', js: true do + allow(BrayniacAI::Validation).to receive(:create).and_return(double(:validation, valid: true)) + + project = create(:project, members: current_user, account: current_user.primary_account) + contract_template = create(:contract_template, project: project) + + visit new_account_project_contract_template_appearance_release_path(project.account, project, contract_template) + + expect(page).to have_photo_button + + fill_in person_first_name_field, with: 'Jane' + fill_in person_last_name_field, with: 'Doe' + fill_in person_address_field, with: '123 Test Lane, New York, NY 10000' + fill_in person_phone_field, with: '555-555-5555' + fill_in person_email_field, with: 'jane.doe@test.com' + fill_in person_date_of_birth, with: '01/01/1999' + attach_file person_photo_field, file_fixture('person_photo.png'), visible: :all + draw_signature file_fixture('signature.png'), 'appearance_release_signature_base64' + click_button 'I have read and agree to the above' + + expect(page).to have_content(successful_submission_message) + end + + scenario 'creating a release for a minor', js: true do + allow(BrayniacAI::Validation).to receive(:create).and_return(double(:validation, valid: true)) + + project = create(:project, members: current_user, account: current_user.primary_account) + contract_template = create(:contract_template, project: project) + + visit new_account_project_contract_template_appearance_release_path(project.account, project, contract_template) + + expect(page).to have_photo_button + expect(page).not_to have_content('GUARDIAN INFORMATION') + expect(page).not_to have_content('GUARDIAN PHOTO') + + page.check person_is_minor_checkbox + expect(page).to have_content('GUARDIAN INFORMATION') + expect(page).to have_content('GUARDIAN PHOTO') + + fill_in guardian_first_name_field, with: 'Guardian' + fill_in guardian_last_name_field, with: 'Name' + fill_in guardian_phone_field, with: '001101' + fill_in person_first_name_field, with: 'Jane' + fill_in person_last_name_field, with: 'Doe' + fill_in person_address_field, with: '123 Test Lane, New York, NY 10000' + fill_in person_phone_field, with: '555-555-5555' + fill_in person_email_field, with: 'jane.doe@test.com' + fill_in person_date_of_birth, with: '01/01/1999' + attach_file person_photo_field, file_fixture('person_photo.png'), visible: :all + attach_file guardian_photo_field, file_fixture('hemsworth.jpeg'), visible: :all + draw_signature file_fixture('signature.png'), 'appearance_release_signature_base64' + click_button 'I have read and agree to the above' + + expect(page).to have_content(successful_submission_message) + end + end + + context 'when signed in' do + before :each do + sign_in current_user + end + + scenario 'editing non native release for adult', js: true do + appearance_release = create(:appearance_release, :non_native, project: project) + + visit edit_appearance_release_path(appearance_release) + + expect(page).not_to have_content('Guardian Photo') + + by 'filling out the form' do + fill_in person_first_name_field, with: 'New John' + fill_in person_last_name_field, with: 'Doe' + fill_in person_email_field, with: 'john.doe@test.com' + select 'Other', from: 'Applicable Media' + fill_in 'Describe other applicable media', with: 'Test' + select 'Other', from: 'Territory' + fill_in 'Describe other territory', with: 'Test' + select 'Other', from: 'Term' + fill_in 'Describe other term', with: 'Test' + select 'Other', from: 'Restriction' + fill_in 'Describe other restrictions', with: 'Test' + end + + click_button submit_update_button + + expect(page).to have_content successful_update_message + expect(page).to have_content 'New John' + end + + scenario 'editing non native release for minor', js: true do + appearance_release = create(:appearance_release, :non_native, :minor, project: project) + + visit edit_appearance_release_path(appearance_release) + + expect(page).to have_content('Guardian Photo') + + by 'filling out the form' do + page.check person_is_minor_checkbox + fill_in guardian_first_name_field, with: 'Guardian' + fill_in guardian_last_name_field, with: 'Name' + fill_in guardian_phone_field, with: '00101' + fill_in person_first_name_field, with: 'New Jane' + fill_in person_last_name_field, with: 'Doe' + fill_in person_email_field, with: 'jane.doe@test.com' + select 'Other', from: 'Applicable Media' + fill_in 'Describe other applicable media', with: 'Test' + select 'Other', from: 'Territory' + fill_in 'Describe other territory', with: 'Test' + select 'Other', from: 'Term' + fill_in 'Describe other term', with: 'Test' + select 'Other', from: 'Restriction' + fill_in 'Describe other restrictions', with: 'Test' + end + + expect(page).to have_content 'Guardian Photo' + + click_button submit_update_button + + expect(page).to have_content successful_update_message + expect(page).to have_content 'New Jane' + end + + scenario 'progress bar shows when user imports a release', js: true do + skip "TODO" + visit project_appearance_releases_path(project) + + attach_file import_appearance_release_field, Rails.root.join(file_fixture('person_photo.png')), visible: false + expect(page).to have_content importing_label + click_button submit_create_button + expect(page).to have_css('.progress') + end + + scenario 'importing a releases works when image is selected', js: true do + visit project_appearance_releases_path(project) + + expect(page).to have_content submit_create_button + expect(page).to have_content no_appearance_releases + attach_file import_appearance_release_field, Rails.root.join(file_fixture('person_photo.png')), visible: false + expect(page).to have_content importing_label + click_button submit_create_button + expect(page).not_to have_content no_appearance_releases + expect(page).to have_content /Imported Headshot\s+\d{7}/ + end + + scenario 'importing a releases works when pdf is selected', js: true do + visit project_appearance_releases_path(project) + + expect(page).to have_content submit_create_button + expect(page).to have_content no_appearance_releases + attach_file import_appearance_release_field, Rails.root.join(file_fixture('contract.pdf')), visible: false + expect(page).to have_content importing_label + click_button submit_create_button + expect(page).not_to have_content no_appearance_releases + expect(page).to have_content /Imported Contract\s+\d{7}/ + end + + scenario 'importing a releases fails when file other than image or pdf is selected', js: true do + visit project_appearance_releases_path(project) + + expect(page).to have_content submit_create_button + expect(page).to have_content no_appearance_releases + attach_file import_appearance_release_field, Rails.root.join(file_fixture('audio.mp3')), visible: false + expect(page).to have_content importing_label + click_button submit_create_button + expect(page).to have_content failed_to_import_notice + expect(page).to have_content no_appearance_releases + end + + scenario 'user leaving the page is presented with the warning if file upload is in progress', js: true do + skip "Test is inconsistently failing in CI" + + visit project_appearance_releases_path(project) + + expect(page).to have_content submit_create_button + expect(page).to have_content no_appearance_releases + + large_pdf_file = build_large_pdf_file + attach_file import_appearance_release_field, Rails.root.join(large_pdf_file.path), visible: false + + page.dismiss_confirm do + first('a', text: 'Files').click + end + + expect(page).to have_content importing_label + # Here capybara automatically waits and submit button changes label from Importing... to Import Release again + expect(page).to have_content submit_create_button + + first('a', text: 'Files').click + expect(page).not_to have_content no_appearance_releases + end + + scenario 'updating a release' do + appearance_release = create(:appearance_release, :non_native, project: project) + + visit edit_appearance_release_path(appearance_release) + + fill_in person_first_name_field, with: 'New' + fill_in person_last_name_field, with: 'Name' + click_button submit_update_button + + expect(page).to have_content(successful_update_message) + expect(page).to have_content('New Name') + end + + scenario 'viewing the contract PDF' do + appearance_release = create(:appearance_release_with_contract_template, + :native, + project: project, + person_first_name: 'Jane', + person_last_name: 'Doe', + tag_list: 'Woman, Brunette', + notes: [ + build(:note, + content: 'Note 1', + user: build(:user, email: 'jane.doe@test.com'), + email: 'jane.doe@test.com', + created_at: DateTime.new(2020, 2, 21, 12, 0, 0)), + build(:note, + content: 'Note 2', + user: build(:user, email: 'john.doe@test.com'), + email: 'john.doe@test.com', + created_at: DateTime.new(2020, 2, 20, 11, 0, 0)) + ]) + + sign_in(current_user) + visit project_appearance_releases_path(project) + click_link *view_release_pdf_link_for(appearance_release) + + expect(content_type).to eq('application/pdf') + expect(content_disposition).to include('inline') + expect(pdf_filename).to include('doe-jane') + expect(pdf_body).to have_content('Jane Doe') + expect(pdf_body).to have_content('NOTES') + expect(pdf_body).to have_content('Note 1') + expect(pdf_body).to have_content('jane.doe@test.com') + expect(pdf_body).to have_content('2/21/20 12:00 PM') + expect(pdf_body).to have_content('Note 2') + expect(pdf_body).to have_content('john.doe@test.com') + expect(pdf_body).to have_content('2/20/20 11:00 AM') + expect(pdf_body).to have_content('TAGS') + expect(pdf_body).to have_content('Woman') + expect(pdf_body).to have_content('Brunette') + end + + scenario 'viewing contract PDF for a minor without guardian photo' do + appearance_release = create(:appearance_release_with_contract_template, :native, :minor, project: project) + + visit project_appearance_releases_path(project) + click_link *view_release_pdf_link_for(appearance_release) + + expect(content_type).to eq('application/pdf') + expect(content_disposition).to include('inline') + expect(pdf_filename).to include(appearance_release.filename_suffix.parameterize) + expect(pdf_body).to have_content(appearance_release.name) + expect(pdf_body).to have_content(appearance_release.guardian_name) + expect(pdf_body).to have_content photos_heading.upcase + expect(pdf_body).to have_content(appearance_release.photo.filename.to_s) + end + + scenario 'viewing contract PDF for a minor with guardian photo' do + appearance_release = create(:appearance_release_with_contract_template, :native, :minor_with_guardian_photo, project: project) + + visit project_appearance_releases_path(project) + click_link *view_release_pdf_link_for(appearance_release) + + expect(content_type).to eq('application/pdf') + expect(content_disposition).to include('inline') + expect(pdf_filename).to include(appearance_release.filename_suffix.parameterize) + expect(pdf_body).to have_content(appearance_release.name) + expect(pdf_body).to have_content(appearance_release.guardian_name) + expect(pdf_body).to have_content photos_heading(2).upcase + expect(pdf_body).to have_content(appearance_release.photo.filename.to_s) + expect(pdf_body).to have_content(appearance_release.guardian_photo.filename.to_s) + end + + scenario 'deleting a release', js: true do + appearance_release = create(:appearance_release, project: project) + + visit project_appearance_releases_path(project) + + click_on 'Manage' + accept_alert do + click_link *destroy_link_for(appearance_release) + end + + expect(page).to have_content(successful_destroy_message) + end + + scenario 'searching for a release', js: true do + chris = create(:appearance_release, person_first_name: 'Chris', person_last_name: 'Evans', project: project) + robert = create(:appearance_release, person_first_name: 'Robert', person_last_name: 'Downey Jr.', project: project) + + visit project_appearance_releases_path(project) + + expect(page).to have_content('Chris Evans') + expect(page).to have_content('Robert Downey Jr.') + + within 'form#search' do + fill_in 'Search', with: 'Robert' + click_on 'button' + end + + expect(page).to have_content('Robert Downey Jr.') + expect(page).not_to have_content('Chris Evans') + expect(page).to have_field('Search', with: 'Robert') + end + + scenario 'filtering for a release by complete/incomplete type', js: true do + create(:appearance_release, :without_person_photo, :non_native, person_first_name: 'Chris', person_last_name: 'Evans Incomplete', project: project) + create(:appearance_release, :non_native, person_first_name: 'Chris', person_last_name: 'Evans Complete', project: project) + create(:appearance_release, :without_person_photo, :non_native, person_first_name: 'Robert', person_last_name: 'Downey Jr. Incomplete', project: project) + create(:appearance_release, :non_native, person_first_name: 'Robert', person_last_name: 'Downey Jr. Complete', project: project) + + visit project_appearance_releases_path(project) + + expect(page).to have_content('Chris Evans Incomplete') + expect(page).to have_content('Chris Evans Complete') + expect(page).to have_content('Robert Downey Jr. Incomplete') + expect(page).to have_content('Robert Downey Jr. Complete') + + click_link filter_type_complete + + expect(page).not_to have_content('Chris Evans Incomplete') + expect(page).to have_content('Chris Evans Complete') + expect(page).not_to have_content('Robert Downey Jr. Incomplete') + expect(page).to have_content('Robert Downey Jr. Complete') + + click_link filter_type_incomplete + + expect(page).to have_content('Chris Evans Incomplete') + expect(page).not_to have_content('Chris Evans Complete') + expect(page).to have_content('Robert Downey Jr. Incomplete') + expect(page).not_to have_content('Robert Downey Jr. Complete') + + click_link filter_type_all + + expect(page).to have_content('Chris Evans Incomplete') + expect(page).to have_content('Chris Evans Complete') + expect(page).to have_content('Robert Downey Jr. Incomplete') + expect(page).to have_content('Robert Downey Jr. Complete') + end + + scenario 'filtering for a release by complete/incomplete type works in parallel with search', js: true do + create(:appearance_release, :without_person_photo, :non_native, person_first_name: 'Chris', person_last_name: 'Evans Incomplete', project: project) + create(:appearance_release, :non_native, person_first_name: 'Chris', person_last_name: 'Evans Complete', project: project) + create(:appearance_release, :without_person_photo, :non_native, person_first_name: 'Robert', person_last_name: 'Downey Jr. Incomplete', project: project) + create(:appearance_release, :non_native, person_first_name: 'Robert', person_last_name: 'Downey Jr. Complete', project: project) + + visit project_appearance_releases_path(project) + + expect(page).to have_content('Chris Evans Incomplete') + expect(page).to have_content('Chris Evans Complete') + expect(page).to have_content('Robert Downey Jr. Incomplete') + expect(page).to have_content('Robert Downey Jr. Complete') + + click_link filter_type_complete + + expect(page).not_to have_content('Chris Evans Incomplete') + expect(page).to have_content('Chris Evans Complete') + expect(page).not_to have_content('Robert Downey Jr. Incomplete') + expect(page).to have_content('Robert Downey Jr. Complete') + + within 'form#search' do + fill_in 'Search', with: 'Robert' + click_on 'button' + end + + expect(page).not_to have_content('Chris Evans Incomplete') + expect(page).not_to have_content('Chris Evans Complete') + expect(page).not_to have_content('Robert Downey Jr. Incomplete') + expect(page).to have_content('Robert Downey Jr. Complete') + + click_link filter_type_incomplete + + expect(page).not_to have_content('Chris Evans Incomplete') + expect(page).not_to have_content('Chris Evans Complete') + expect(page).to have_content('Robert Downey Jr. Incomplete') + expect(page).not_to have_content('Robert Downey Jr. Complete') + + click_link filter_type_all + + expect(page).not_to have_content('Chris Evans Incomplete') + expect(page).not_to have_content('Chris Evans Complete') + expect(page).to have_content('Robert Downey Jr. Incomplete') + expect(page).to have_content('Robert Downey Jr. Complete') + end + end + + context 'when the user is associate' do + let(:current_user) { create(:user, :associate) } + + scenario 'should not show download' do + chris = create(:appearance_release_with_contract_template, person_first_name: 'Chris', person_last_name: 'Evans', project: project) + + sign_in current_user + visit project_appearance_releases_path(project) + + click_on 'Manage' + expect(page).not_to have_link('Download', exact: true) + end + end + + private + + def build_large_pdf_file + # Generates dummy temp file with approx 50MB + tempfile = Tempfile.new(['large_file', '.pdf'], Rails.root.join('tmp')) + tempfile << '%PDF-1.2' + 1_000_000.times do + tempfile << 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + end + tempfile << '%%EOF' + tempfile + end + + def filter_type_all + t 'appearance_releases.type_filter_actions.all_releases' + end + + def filter_type_complete + t 'appearance_releases.type_filter_actions.complete_releases' + end + + def filter_type_incomplete + t 'appearance_releases.type_filter_actions.incomplete_releases' + end + + def failed_to_import_notice + t 'appearance_releases.create.failed_import' + end + + def importing_label + 'Importing...' + end + + def no_appearance_releases + t 'appearance_releases.index.empty' + end + + def photos_heading(photos_count = 1) + t 'contracts.photos.heading', count: photos_count + end + + def have_photo_button + have_selector('.take-photo-button') + end + + def person_is_minor_checkbox + 'appearance_release_minor' + end + + def guardian_first_name_field + 'Guardian first name' + end + + def guardian_last_name_field + 'Guardian last name' + end + + def guardian_phone_field + 'Guardian phone' + end + + def guardian_photo_field + 'appearance_release[guardian_photo]' + end + + def person_name_field + t('helpers.label.appearance_release.person_name') + end + + def person_first_name_field + 'Person first name' + end + + def person_last_name_field + 'Person last name' + end + + def person_address_field + t('helpers.label.appearance_release.person_address') + end + + def person_email_field + t('helpers.label.appearance_release.person_email') + end + + def person_phone_field + t('helpers.label.appearance_release.person_phone') + end + + def import_appearance_release_field + 'attachments[]' + end + + def person_photo_field + 'appearance_release[person_photo]' + end + + def contract_field + 'appearance_release[contract]' + end + + def person_date_of_birth + 'appearance_release[person_date_of_birth]' + end + + def submit_create_button + 'Import Release' + end + + def submit_update_button + 'Save Changes' + end + + def edit_link_for(appearance_release) + ['Edit', href: edit_appearance_release_path(appearance_release)] + end + + def destroy_link_for(appearance_release) + ['Delete', href: appearance_release_path(appearance_release)] + end + + def view_release_pdf_link_for(appearance_release) + ['Download', href: appearance_release_contracts_path(appearance_release, format: 'pdf')] + end + + def successful_submission_message + 'Your release was successfully submitted. Thank you.' + end + + def succesful_create_message + 'The release has been imported.' + end + + def successful_update_message + 'The release has been updated' + end + + def successful_destroy_message + 'The release has been deleted' + end +end diff --git a/spec/features/user_managing_broadcasts_spec.rb b/spec/features/user_managing_broadcasts_spec.rb new file mode 100644 index 0000000..5414351 --- /dev/null +++ b/spec/features/user_managing_broadcasts_spec.rb @@ -0,0 +1,83 @@ +require "rails_helper" + +feature "User managing broadcasts" do + let(:current_user) { create(:user, :manager) } + let(:project) { create(:project, members: current_user, account: current_user.primary_account) } + + context "managing broadcasts" do + before do + sign_in current_user + allow(MuxLiveStream).to receive(:new).and_return(double(id: "id", key: "key", playback_id: "playback_id")) + end + + scenario "creating and deleting a broadcast", js: true do + visit new_project_broadcast_path(project) + + by "filling out the form" do + fill_in broadcast_name_field, with: "My Broadcast" + end + + click_button "Create Live Stream" + expect(page).to have_content("A live stream has been created") + click_on "Manage" + expect(page).to have_link("Copy Stream URL", exact: true) + expect(page).to have_link("Copy Stream Key", exact: true) + expect(page).to have_link("View", exact: true) + expect(page).to have_link("Delete", exact: true) + + it_also "Deletes the broadcast" do + allow_any_instance_of(Broadcast).to receive(:destroy_mux_live_stream).and_return(true) + + accept_alert do + click_link "Delete" + end + + expect(page).to have_content("A live stream has been deleted") + expect(page).not_to have_content("My Broadcast") + end + end + + scenario "visit show page of broadcast", js: true do + broadcast = create(:broadcast, :with_stream, :with_files, project: project) + recording = create(:broadcast_recording, broadcast: broadcast) + + visit project_broadcast_path(project, broadcast) + + expect(page).to have_content("Live stream is waiting to begin.") + expect(page).to have_content("Copy URL") + + click_on "Files" + expect(page).to have_content("contract.pdf") + + click_on "Previous Sessions" + expect(page).to have_content(recording.download_file_name) + end + + scenario "visit multi-view broadcast page", js: true do + broadcasts = create_list(:broadcast, 4, :with_stream, project: project) + + visit project_broadcasts_path(project) + click_checkboxes + + new_window = window_opened_by { click_link "Multi-View" } + within_window new_window do + expect(page).to have_content("Switch View") + + click_on "Switch View" + expect(page).to have_link(broadcasts.first.name) + expect(page).to have_link(broadcasts.second.name) + end + end + end + + private + + def broadcast_name_field + "broadcast[name]" + end + + def click_checkboxes + all('input[type="checkbox"]')[1].click + all('input[type="checkbox"]')[2].click + end +end diff --git a/spec/features/user_managing_downloads_spec.rb b/spec/features/user_managing_downloads_spec.rb new file mode 100644 index 0000000..eed3191 --- /dev/null +++ b/spec/features/user_managing_downloads_spec.rb @@ -0,0 +1,29 @@ +require "rails_helper" + +feature "User managing downloads" do + let(:current_user) { create(:user) } + let(:project) { create(:project, account: current_user.primary_account) } + + before :each do + sign_in current_user + end + + scenario "searching for a downlaod", js: true do + create(:download_release_type_appearance, project: project) + create(:download_release_type_music, project: project) + + visit project_downloads_path(project) + + fill_in "query", with: "Music" + click_on "search-button" + + expect(page).to have_content("Music Release") + expect(page).not_to have_content("Appearance Release") + + fill_in "query", with: "Natgeo" + click_on "search-button" + + expect(page).not_to have_content("Discovery Contract") + expect(page).to have_content("Natgeo Contract") + end +end diff --git a/spec/features/user_managing_location_releases_spec.rb b/spec/features/user_managing_location_releases_spec.rb new file mode 100644 index 0000000..8a2c916 --- /dev/null +++ b/spec/features/user_managing_location_releases_spec.rb @@ -0,0 +1,279 @@ +require "rails_helper" + +feature "User managing location releases" do + let(:current_user) { create(:user, :manager) } + let(:project) { create(:project, members: current_user, account: current_user.primary_account) } + + context "when signed out" do + scenario "creating a release", js: true do + contract_template = create(:contract_template, project: project) + + visit new_account_project_contract_template_location_release_path(project.account, project, contract_template) + + by "filling out the form" do + fill_in location_name_field, with: "Benny's Burritos" + fill_in person_first_name_field, with: "Jane" + fill_in person_last_name_field, with: "Doe" + fill_in person_phone_field, with: "555-555-5555" + fill_in person_email_field, with: "jane.doe@test.com" + fill_in person_address_street1_field, with: "100 Broadway" + draw_signature file_fixture("signature.png"), "location_release_signature_base64" + end + + click_button "I have read and agree to the above" + + expect(page).to have_content("Your release was successfully submitted. Thank you.") + end + end + + context "when signed in" do + before do + set_window_size_permanently_to(1000, 1000) + sign_in current_user + end + + scenario "creating a release", js: true do + visit new_project_location_release_path(project) + + by "attaching only a contract" do + attach_file contract_field, Rails.root.join(file_fixture("contract.pdf")), visible: false + click_button create_release_button + + expect(page).to have_invalid_field(location_name_field) + end + + by "attaching photos" do + drop_file Rails.root.join(file_fixture("location_photo.png")), type: :dropzone + click_button create_release_button + + expect(page).to have_invalid_field(location_name_field) + end + + by "filling out the remaining information" do + fill_in_release_fields name: "Test Location Release" + click_button create_release_button + expect(page).to have_content(create_release_notice) + expect(page).to have_photo("location_photo.png") + + click_on "Manage" + expect(page).to have_link("Download") + end + end + + scenario "updating an existing release", js: true do + location_release = create(:location_release_with_photo, :non_native, project: project) + + visit project_location_releases_path(project) + click_on "Manage" + click_link *update_location_release_link(location_release) + + within ".dropzone" do + expect(page).to have_photo("location_photo.png", attr: "alt") + end + + fill_in_release_fields name: "New release name" + select "Other", from: "Applicable Media" + fill_in "Describe other applicable media", with: "New Test" + click_on update_release_button + + expect(page).to have_content(update_release_notice) + expect(page).to have_content("New release name") + end + + scenario "deleting an existing release", js: true do + location_release = create(:location_release, project: project) + + visit project_location_releases_path(project) + click_on "Manage" + + accept_alert do + click_link *destroy_location_release_link(location_release) + end + + expect(page).to have_content(destroy_release_alert) + expect(page).not_to have_content(location_release.name) + end + + scenario "searching for a release", js: true do + create(:location_release, name: "Cheers", project: project) + create(:location_release, name: "Cipriani", project: project) + + visit project_location_releases_path(project) + + within "form#search" do + fill_in "Search", with: "Cheers" + click_on "button" + end + + expect(page).to have_content("Cheers") + expect(page).not_to have_content("Cipriani") + expect(page).to have_field("Search", with: "Cheers") + end + + scenario "adding photos to an existing release", js: true do + create(:location_release, name: "Apple MacBook Air", project: project) + + visit project_location_releases_path(project) + + expect(page).to have_content("Needs Photo") + + click_on "Manage" + click_on "Photos" + + expect(page).to have_content("Add Photos") + expect(page).to have_content("Apple MacBook Air") + + drop_file Rails.root.join(file_fixture("location_photo.png")), type: :dropzone + click_on "Save Changes" + + expect(page).to have_content("The release has been updated") + expect(page).to have_photo("location_photo.png") + end + end + + scenario "viewing the contract PDF" do + location_release = create(:location_release_with_contract_template_and_photo, + :native, + project: project, + name: "Benny's Burritos", + tag_list: "Restaurant", + notes: [ + build(:note, + content: "Note 1", + user: build(:user, email: "jane.doe@test.com"), + email: "jane.doe@test.com", + created_at: DateTime.new(2020, 2, 21, 12, 0, 0), + ), + build(:note, + content: "Note 2", + user: build(:user, email: "john.doe@test.com"), + email: "john.doe@test.com", + created_at: DateTime.new(2020, 2, 20, 11, 0, 0), + ), + ] + ) + + sign_in(current_user) + visit project_location_releases_path(project) + click_link *view_release_pdf_link_for(location_release) + + expect(content_type).to eq("application/pdf") + expect(content_disposition).to include("inline") + expect(pdf_filename).to include("benny-s-burritos") + expect(pdf_body).to have_content("Benny's Burritos") + expect(pdf_body).to have_content("NOTES") + expect(pdf_body).to have_content("Note 1") + expect(pdf_body).to have_content("jane.doe@test.com") + expect(pdf_body).to have_content("2/21/20 12:00 PM") + expect(pdf_body).to have_content("Note 2") + expect(pdf_body).to have_content("john.doe@test.com") + expect(pdf_body).to have_content("2/20/20 11:00 AM") + expect(pdf_body).to have_content("TAGS") + expect(pdf_body).to have_content("Restaurant") + expect(pdf_body).to have_content photos_heading.upcase + expect(pdf_body).to have_content("location_photo.png") + end + + context "when the user is associate" do + let(:current_user) { create(:user, :associate) } + + scenario "should not show download" do + create(:location_release_with_contract_template, name: "Cheers", project: project) + + sign_in current_user + visit project_location_releases_path(project) + + click_on "Manage" + expect(page).not_to have_link("Download", exact: true) + end + end + + private + + def photos_heading(photos_count = 1) + t 'contracts.photos.heading', count: photos_count + end + + def location_name_field + "location_release[name]" + end + + def contract_field + "location_release[contract]" + end + + def person_first_name_field + "location_release[person_first_name]" + end + + def person_last_name_field + "location_release[person_last_name]" + end + + def person_email_field + "location_release[person_email]" + end + + def person_address_street1_field + "location_release[person_address_street1]" + end + + def person_phone_field + "location_release[person_phone]" + end + + def have_photo(filename, attr: "src") + have_selector("img[#{attr}*='#{filename}']") + end + + def import_location_release_link(project) + ["Import Release", href: new_project_location_release_path(project)] + end + + def update_location_release_link(location_release) + ["Edit", href: edit_location_release_path(location_release)] + end + + def destroy_location_release_link(location_release) + ["Delete", href: location_release_path(location_release)] + end + + def fill_in_release_fields(data) + fill_in "location_release[name]", with: data[:name] + end + + def create_release_button + t "helpers.submit.location_release.create" + end + + def create_release_notice + t "location_releases.create.notice" + end + + def update_release_button + t "helpers.submit.location_release.update" + end + + def update_release_notice + t "location_releases.update.notice" + end + + def destroy_release_alert + t "location_releases.destroy.alert" + end + + def view_release_pdf_link_for(location_release) + ["Download", href: location_release_contracts_path(location_release, format: "pdf")] + end + + def fill_in_exploitable_rights + select "Other", from: "Applicable Media" + fill_in "Describe other applicable media", with: "Test" + select "Other", from: "Territory" + fill_in "Describe other territory", with: "Test" + select "Other", from: "Term" + fill_in "Describe other term", with: "Test" + select "Other", from: "Restriction" + fill_in "Describe other restrictions", with: "Test" + end +end diff --git a/spec/features/user_managing_material_releases_spec.rb b/spec/features/user_managing_material_releases_spec.rb new file mode 100644 index 0000000..6a6e486 --- /dev/null +++ b/spec/features/user_managing_material_releases_spec.rb @@ -0,0 +1,253 @@ +require "rails_helper" + +feature "User managing material releases" do + let(:current_user) { create(:user, :manager) } + let(:project) { create(:project, members: current_user, account: current_user.primary_account) } + + context "when signed out" do + scenario "creating a release", js: true do + contract_template = create(:contract_template, project: project) + + visit new_account_project_contract_template_material_release_path(project.account, project, contract_template) + + by "filling out the form" do + fill_in material_name_field, with: "Pepsi Logo" + fill_in person_first_name_field, with: "Jane" + fill_in person_last_name_field, with: "Doe" + draw_signature file_fixture("signature.png"), "material_release_signature_base64" + end + + click_button "I have read and agree to the above" + + expect(page).to have_content("Your release was successfully submitted. Thank you.") + end + end + + context "when signed in" do + before do + sign_in current_user + end + + scenario "creating a release", js: true do + visit new_project_material_release_path(project) + + by "attaching only a contract" do + attach_file "material_release[contract]", Rails.root.join(file_fixture("contract.pdf")), visible: false + click_button create_release_button + + expect(page).to have_invalid_field(material_name_field) + end + + by "attaching photos" do + drop_file Rails.root.join(file_fixture("material_photo.png")), type: :dropzone + click_button create_release_button + + expect(page).to have_invalid_field(material_name_field) + end + + by "filling out the remaining information" do + fill_in_release_fields name: "Apple Laptop" + click_button create_release_button + + expect(page).to have_content(create_release_notice) + expect(page).to have_photo("material_photo.png") + + click_on "Manage" + expect(page).to have_link("Download") + end + end + + scenario "updating an existing release", js: true do + material_release = create(:material_release, :non_native, project: project) + + visit project_material_releases_path(project) + click_on "Manage" + click_link *update_material_release_link(material_release) + + fill_in_release_fields name: "New release name" + select "Other", from: "Applicable Media" + fill_in "Describe other applicable media", with: "New Test" + click_button update_release_button + + expect(page).to have_content(update_release_notice) + expect(page).to have_content("New release name") + end + + scenario "deleting an existing release", js: true do + material_release = create(:material_release, project: project) + + visit project_material_releases_path(project) + click_on "Manage" + + accept_alert do + click_link *destroy_material_release_link(material_release) + end + + expect(page).to have_content(destroy_release_alert) + expect(page).not_to have_content(material_release.name) + end + + scenario "searching for a release", js: true do + create(:material_release, name: "Apple MacBook Air", project: project) + create(:material_release, name: "Microsoft Surface Pro", project: project) + + visit project_material_releases_path(project) + + within "form#search" do + fill_in "Search", with: "Apple" + click_on "button" + end + + expect(page).to have_content("Apple MacBook Air") + expect(page).not_to have_content("Microsoft Surface Pro") + expect(page).to have_field("Search", with: "Apple") + end + + scenario "adding photos to an existing release", js: true do + create(:material_release, name: "Apple MacBook Air", project: project) + + visit project_material_releases_path(project) + + expect(page).to have_content("Needs Photo") + + click_on "Manage" + click_on "Photos" + + expect(page).to have_content("Add Photos") + expect(page).to have_content("Apple MacBook Air") + + drop_file Rails.root.join(file_fixture("material_photo.png")), type: :dropzone + click_on "Save Changes" + + expect(page).to have_content("The release has been updated") + expect(page).to have_photo("material_photo.png") + end + + scenario "viewing the contract PDF" do + material_release = create(:material_release_with_contract_template_and_photo, + :native, + project: project, + name: "Test Materials", + tag_list: "Soda Can", + notes: [ + build(:note, + content: "Note 1", + user: build(:user, email: "jane.doe@test.com"), + email: "jane.doe@test.com", + created_at: DateTime.new(2020, 2, 21, 12, 0, 0),), + build(:note, + content: "Note 2", + user: build(:user, email: "john.doe@test.com"), + email: "john.doe@test.com", + created_at: DateTime.new(2020, 2, 20, 11, 0, 0),), + ]) + + sign_in(current_user) + visit project_material_releases_path(project) + click_link *view_release_pdf_link_for(material_release) + + expect(content_type).to eq("application/pdf") + expect(content_disposition).to include("inline") + expect(pdf_filename).to include("test-materials") + expect(pdf_body).to have_content("Test Materials") + expect(pdf_body).to have_content("NOTES") + expect(pdf_body).to have_content("Note 1") + expect(pdf_body).to have_content("jane.doe@test.com") + expect(pdf_body).to have_content("2/21/20 12:00 PM") + expect(pdf_body).to have_content("Note 2") + expect(pdf_body).to have_content("john.doe@test.com") + expect(pdf_body).to have_content("2/20/20 11:00 AM") + expect(pdf_body).to have_content("TAGS") + expect(pdf_body).to have_content("Soda Can") + expect(pdf_body).to have_content photos_heading.upcase + expect(pdf_body).to have_content("material_photo.png") + end + end + + context "when the user is associate" do + let(:current_user) { create(:user, :associate) } + + scenario "should not show download" do + create(:material_release_with_contract_template, name: "Apple MacBook Air", project: project) + + sign_in current_user + visit project_material_releases_path(project) + + click_on "Manage" + expect(page).not_to have_link("Download", exact: true) + end + end + + private + + def photos_heading(photos_count = 1) + t 'contracts.photos.heading', count: photos_count + end + + def material_name_field + "material_release[name]" + end + + def person_first_name_field + "material_release[person_first_name]" + end + + def person_last_name_field + "material_release[person_last_name]" + end + + def have_photo(filename) + have_selector("img[src*='#{filename}']") + end + + def import_material_release_link(project) + ["Import Release", href: new_project_material_release_path(project)] + end + + def update_material_release_link(material_release) + ["Edit", href: edit_material_release_path(material_release)] + end + + def destroy_material_release_link(material_release) + ["Delete", href: material_release_path(material_release)] + end + + def view_release_pdf_link_for(material_release) + ["Download", href: material_release_contracts_path(material_release, format: "pdf")] + end + + def fill_in_release_fields(data) + fill_in "material_release[name]", with: data[:name] + end + + def create_release_button + t "helpers.submit.material_release.create" + end + + def create_release_notice + t "material_releases.create.notice" + end + + def update_release_button + t "helpers.submit.material_release.update" + end + + def update_release_notice + t "material_releases.update.notice" + end + + def destroy_release_alert + t "material_releases.destroy.alert" + end + + def fill_in_exploitable_rights + select "Other", from: "Applicable Media" + fill_in "Describe other applicable media", with: "Test" + select "Other", from: "Territory" + fill_in "Describe other territory", with: "Test" + select "Other", from: "Term" + fill_in "Describe other term", with: "Test" + select "Other", from: "Restriction" + fill_in "Describe other restrictions", with: "Test" + end +end diff --git a/spec/features/user_managing_music_releases_spec.rb b/spec/features/user_managing_music_releases_spec.rb new file mode 100644 index 0000000..337d5f1 --- /dev/null +++ b/spec/features/user_managing_music_releases_spec.rb @@ -0,0 +1,166 @@ +require "rails_helper" + +feature "User managing music releases" do + let(:current_user) { create(:user, :manager) } + let(:project) { create(:project, members: current_user, account: current_user.primary_account) } + + scenario "can perform crud actions" do + release_data = attributes_for(:music_release) + + sign_in current_user + visit project_music_releases_path(project) + + by "creating a release" do + click_link *import_music_release_link(project) + expect(page).to have_content "For optimal accuracy, please ensure music file names match the source file name in the editing sequence." + fill_in_release_fields release_data + fill_in_exploitable_rights + click_button create_release_button + expect(page).to have_content(create_release_notice) + expect(page).to have_content(release_data[:name]) + end + + and_by "updating an existing release" do + click_link *update_music_release_link(MusicRelease.last) + + fill_in_release_fields name: "New release name" + select "Other", from: "Applicable Media" + fill_in "Describe other applicable media", with: "New Test" + + click_button update_release_button + + expect(page).to have_content(update_release_notice) + expect(page).to have_content("New release name") + end + + and_by "deleting an existing release" do + click_link *destroy_music_release_link(MusicRelease.last) + + expect(page).to have_content(destroy_release_alert) + expect(page).not_to have_content("New release name") + end + end + + scenario "creating a music release with validation errors", js: true do + sign_in current_user + visit new_project_music_release_path(project) + + by "attaching only a contract" do + attach_file "music_release[contract]", Rails.root.join(file_fixture("contract.pdf")), visible: false + click_button create_release_button + + expect(page).to have_invalid_field(music_release_name_field) + end + + and_by "attaching files" do + drop_file Rails.root.join(file_fixture("audio.mp3")), type: "file-info-dropzone" + click_button create_release_button + + expect(page).to have_invalid_field(music_release_name_field) + end + + and_by "filling out the remaining information" do + fill_in_release_fields name: "Big Media OST" + click_button create_release_button + + expect(page).to have_content(create_release_notice) + click_on "Manage" + expect(page).to have_link("Download") + end + end + + scenario "searching for a release", js: true do + collection1 = create(:music_release, name: "EDM Music", project: project) + collection2 = create(:music_release, name: "Classical Music", project: project) + + sign_in current_user + visit project_music_releases_path(project) + + within "form#search" do + fill_in "Search", with: "EDM" + click_on "button" + end + + expect(page).to have_content("EDM Music") + expect(page).not_to have_content("Classical Music") + expect(page).to have_field("Search", with: "EDM") + end + + context "when the user is associate" do + let(:current_user) { create(:user, :associate) } + + scenario "should not show download" do + collection1 = create(:music_release, name: "EDM Music", project: project) + + sign_in current_user + visit project_music_releases_path(project) + + click_on "Manage" + expect(page).not_to have_link("Download", exact: true) + end + end + + private + + def music_release_name_field + "music_release[name]" + end + + def import_music_release_link(project) + ["Import Release", href: new_project_music_release_path(project)] + end + + def update_music_release_link(music_release) + ["Edit", href: edit_music_release_path(music_release)] + end + + def destroy_music_release_link(music_release) + ["Delete", href: music_release_path(music_release)] + end + + def fill_in_release_fields(data) + fill_in "music_release[name]", with: data[:name] + fill_in "music_release[person_first_name]", with: data[:person_first_name] + fill_in "music_release[person_last_name]", with: data[:person_last_name] + + fill_in "music_release_composers_attributes_0_name", with: "composer name" + fill_in "music_release_composers_attributes_0_affiliation", with: "composer affiliation" + fill_in "music_release_composers_attributes_0_percentage", with: 100 + fill_in "music_release_composers_attributes_0_cae_number", with: "CAE123456789" + + fill_in "music_release_publishers_attributes_0_name", with: "publisher name" + fill_in "music_release_publishers_attributes_0_affiliation", with: "publisher affiliation" + fill_in "music_release_publishers_attributes_0_percentage", with: 100 + end + + def create_release_button + t "helpers.submit.music_release.create" + end + + def create_release_notice + t "music_releases.create.notice" + end + + def update_release_button + t "helpers.submit.music_release.update" + end + + def update_release_notice + t "music_releases.update.notice" + end + + def destroy_release_alert + t "music_releases.destroy.alert" + end + + def fill_in_exploitable_rights + select "Other", from: "Applicable Media" + fill_in "Describe other applicable media", with: "Test" + select "Other", from: "Territory" + fill_in "Describe other territory", with: "Test" + select "Other", from: "Term" + fill_in "Describe other term", with: "Test" + select "Other", from: "Restriction" + fill_in "Describe other restrictions", with: "Test" + end +end diff --git a/spec/features/user_managing_project_memberships_spec.rb b/spec/features/user_managing_project_memberships_spec.rb new file mode 100644 index 0000000..a895c77 --- /dev/null +++ b/spec/features/user_managing_project_memberships_spec.rb @@ -0,0 +1,48 @@ +require "rails_helper" + +feature "User managing project memberships" do + let(:account) { create(:account) } + + scenario "manager invites an existing team member to another project" do + manager = create(:user, :manager, accounts: [account], email: "manager@test.com") + project = create(:project, members: manager, account: account, name: "Project") + another_user = create(:user, accounts: [account], email: "another.user@test.com") + another_project = create(:project, members: [manager, another_user], account: account, name: "Another Project") + + sign_in manager + visit project_project_memberships_path(project) + + expect(page).to have_content "manager@test.com" + expect(page).not_to have_content "another.user@test.com" + + fill_in invite_email_field, with: "another.user@test.com" + click_on "Send Invite" + + expect(page).to have_content "User has been invited to the project." + expect(page).to have_content "another.user@test.com" + end + + scenario "manager removes an existing team member from a project" do + manager = create(:user, :manager, accounts: [account], email: "manager@test.com") + another_user = create(:user, accounts: [account], email: "another.user@test.com") + project = create(:project, members: [manager, another_user], account: account, name: "Project") + + sign_in manager + visit project_project_memberships_path(project) + + expect(page).to have_content "manager@test.com" + expect(page).to have_content "another.user@test.com" + expect(page).to have_link("Remove", href: project_membership_path(another_user.project_memberships.first)) + + click_on "Remove" + + expect(page).to have_content "User has been removed from the project." + expect(page).not_to have_content "another.user@test.com" + end + + private + + def invite_email_field + "project_membership[user_email]" + end +end diff --git a/spec/features/user_managing_projects_spec.rb b/spec/features/user_managing_projects_spec.rb new file mode 100644 index 0000000..e5cf7e3 --- /dev/null +++ b/spec/features/user_managing_projects_spec.rb @@ -0,0 +1,155 @@ +require "rails_helper" + +feature "User managing projects" do + let(:user) { create(:user, :account_manager) } + + before do + sign_in(user) + end + + scenario "creating a new project", js: true do + visit new_project_path + + it_also "requires a project name" do + click_on "Create Project" + + expect(page).to have_content "can't be blank" + end + + fill_in "Name", with: "Avengers" + select "Other", from: "Client" + fill_in "Client name", with: "My Client" + uncheck "Appearance Release" + check "Location Release" + click_on "Create Project" + + expect(page).to have_content "The project has been created" + expect(page).to have_content "Avengers" + expect(page).to have_content /My Client/i + expect(page).not_to have_content "Appearance Releases" + expect(page).to have_content "Location Releases" + end + + scenario "editing a project", js: true do + project = create(:project, members: user, account: user.primary_account, name: "Avengers", client_name: "Marvel") + + visit projects_path + click_on "button" + click_on "Edit" + + it_also "requires a project name" do + fill_in "Name", with: "" + click_on "Update Project" + + expect(page).to have_content "can't be blank" + end + + fill_in "Name", with: "Justice League" + select "Other", from: "Client" + fill_in "Client name", with: "DC" + uncheck "Appearance Release" + check "Location Release" + click_on "Update Project" + + expect(page).to have_content "The project has been updated" + expect(page).to have_content "Justice League" + expect(page).to have_content /DC/i + expect(page).not_to have_content "Appearance Releases" + expect(page).to have_content "Location Releases" + end + + scenario "deleting a project", js: true do + skip "This functionality is no longer available" + + project = create(:project, members: user, name: "Avengers", account: user.primary_account) + create(:appearance_release, project: project) + + visit projects_path + + expect(page).to have_content("Avengers") + + click_on "button" + accept_alert do + click_on "Delete" + end + + expect(page).to have_content "The project has been deleted" + expect(page).not_to have_content "Avengers" + end + + scenario "all features are enabled" do + project = create(:project, members: user, account: user.primary_account) + enable_all_project_features(project) + + visit project_path(project) + + expect(page).to have_content("Talent Releases") + expect(page).to have_content("Appearance Releases") + expect(page).to have_content("Acquired Media Releases") + expect(page).to have_content("Material Releases") + expect(page).to have_content("Music Releases") + end + + scenario "some features are disabled" do + project = create(:project, members: user, account: user.primary_account) + disable_project_features(project, :material_release, :music_release) + + visit project_path(project) + + expect(page).to have_content("Talent Releases") + expect(page).to have_content("Appearance Releases") + expect(page).to have_content("Acquired Media Releases") + expect(page).not_to have_content("Material Releases") + expect(page).not_to have_content("Music Releases") + end + + # TODO: What about the welcome page when there are no existing projects? + + context "for a manager" do + let(:user) { create(:user, :manager) } + let!(:project) { create(:project, members: user, account: user.primary_account) } + + scenario "can manage projects" do + visit projects_path + + expect(page).not_to have_link("Create New Project") + expect(page).to have_link("Edit", href: edit_project_path(project)) + expect(page).not_to have_link("Delete", href: project_path(project)) + # expect(page).to have_link("Archive", href: project_archivals_path(project)) + end + end + + context "for an associate" do + let(:user) { create(:user, :associate) } + let!(:project) { create(:project, members: user, account: user.primary_account) } + scenario "cannot manage projects" do + visit projects_path + + expect(page).not_to have_link("Create New Project") + expect(page).not_to have_link("Edit", href: edit_project_path(project)) + expect(page).not_to have_link("Delete", href: project_path(project)) + # expect(page).not_to have_link("Archive", href: project_archivals_path(project)) + end + end + + private + + def enable_all_project_features(project) + disable_project_features(project) + end + + def disable_project_features(project, *features) + defaults = { + appearance_release: true, + location_release: true, + material_release: true, + acquired_media_release: true, + talent_release: true, + music_release: true, + video_analysis: true, + } + disabled_features = Array.wrap(features).each_with_object({}) { |feature, hash| hash[feature] = false } + + project.settings(:features).update(defaults.merge(disabled_features)) + end +end diff --git a/spec/features/user_managing_reports_spec.rb b/spec/features/user_managing_reports_spec.rb new file mode 100644 index 0000000..14a6d5d --- /dev/null +++ b/spec/features/user_managing_reports_spec.rb @@ -0,0 +1,34 @@ +require "rails_helper" + +feature "User managing reports" do + let(:current_user) { create(:user) } + let(:project) { create(:project, account: current_user.primary_account) } + + before :each do + sign_in current_user + end + + scenario "searching for a report", js: true do + create(:video, :published, project: project, name: "My Video", number: "001") + + visit project_reports_path(project) + + fill_in "query", with: "Production" + click_on "search-button" + + expect(page).to have_content "Production Elements Log" + expect(page).not_to have_content "GFX Cue List" + expect(page).not_to have_content "Music Cue Sheet" + expect(page).not_to have_content "BiG Music Cue Sheet" + expect(page).not_to have_content "Issues and Concerns Report" + + fill_in "query", with: "Cue" + click_on "search-button" + + expect(page).not_to have_content "Production Elements Log" + expect(page).to have_content "GFX Cue List" + expect(page).to have_content "Music Cue Sheet" + expect(page).to have_content "BiG Music Cue Sheet" + expect(page).not_to have_content "Issues and Concerns Report" + end +end diff --git a/spec/features/user_managing_talent_releases_spec.rb b/spec/features/user_managing_talent_releases_spec.rb new file mode 100644 index 0000000..512cc85 --- /dev/null +++ b/spec/features/user_managing_talent_releases_spec.rb @@ -0,0 +1,379 @@ +require "rails_helper" + +feature "User managing talent releases" do + let(:current_user) { create(:user, :manager) } + let(:project) { create(:project, members: current_user, account: current_user.primary_account) } + + context "when signed out" do + scenario "creating a release for an adult", js: true do + contract_template = create(:contract_template, project: project) + + visit new_account_project_contract_template_talent_release_path(project.account, project, contract_template) + + by "filling out the form" do + fill_in person_first_name_field, with: "Jane" + fill_in person_last_name_field, with: "Doe" + fill_in person_address_field, with: "123 Test Lane, New York, NY 10000" + fill_in person_phone_field, with: "555-555-5555" + fill_in person_email_field, with: "jane.doe@test.com" + drop_file Rails.root.join(file_fixture("person_photo.png")), type: :dropzone + draw_signature file_fixture("signature.png"), "talent_release_signature_base64" + end + + click_button "I have read and agree to the above" + + expect(page).to have_content("Your release was successfully submitted. Thank you.") + end + + scenario "creating a release for a minor", js: true do + contract_template = create(:contract_template, project: project) + + visit new_account_project_contract_template_talent_release_path(project.account, project, contract_template) + + expect(page).not_to have_content("GUARDIAN INFORMATION") + expect(page).not_to have_content("GUARDIAN PHOTO") + + page.check person_is_minor_checkbox + expect(page).to have_content("GUARDIAN INFORMATION") + expect(page).to have_content("GUARDIAN PHOTO") + + by "filling out the form" do + fill_in guardian_first_name_field, with: "Guardian" + fill_in guardian_last_name_field, with: "Name" + fill_in guardian_phone_field, with: "001101" + fill_in person_first_name_field, with: "Jane" + fill_in person_last_name_field, with: "Doe" + fill_in person_address_field, with: "123 Test Lane, New York, NY 10000" + fill_in person_phone_field, with: "555-555-5555" + fill_in person_email_field, with: "jane.doe@test.com" + drop_file Rails.root.join(file_fixture("person_photo.png")), type: :dropzone + attach_file guardian_photo_field, file_fixture("hemsworth.jpeg"), visible: :all + draw_signature file_fixture("signature.png"), "talent_release_signature_base64" + end + + click_button "I have read and agree to the above" + + expect(page).to have_content("Your release was successfully submitted. Thank you.") + end + end + + context "when signed in" do + before :each do + sign_in current_user + end + + scenario "creating a release", js: true do + visit new_project_talent_release_path(project) + + by "attaching only a contract" do + attach_file "talent_release[contract]", Rails.root.join(file_fixture("contract.pdf")), visible: false + click_button create_release_button + + expect(page).to have_invalid_field("Person first name") + end + + by "attaching photos" do + drop_file Rails.root.join(file_fixture("person_photo.png")), type: :dropzone + click_button create_release_button + + expect(page).to have_invalid_field("Person first name") + end + + by "filling out the remaining information" do + fill_in person_first_name_field, with: "John" + fill_in person_last_name_field, with: "Doe" + + fill_in_exploitable_rights + click_button create_release_button + + expect(page).to have_content(create_release_notice) + expect(page).to have_photo("person_photo.png") + + click_on "Manage" + expect(page).to have_link("Download") + end + end + + scenario "creating a release for minor", js: true do + visit new_project_talent_release_path(project) + + expect(page).not_to have_content("Guardian Photo") + page.check person_is_minor_checkbox + expect(page).to have_content("Guardian Photo") + fill_in person_first_name_field, with: "John" + fill_in person_last_name_field, with: "Doe" + fill_in guardian_first_name_field, with: "Guardian" + fill_in guardian_last_name_field, with: "Name" + fill_in guardian_phone_field, with: "01010" + + fill_in_exploitable_rights + + attach_file "talent_release[contract]", Rails.root.join(file_fixture("contract.pdf")), visible: false + drop_file Rails.root.join(file_fixture("person_photo.png")), type: :dropzone + attach_file guardian_photo_field, Rails.root.join(file_fixture("hemsworth.jpeg")), visible: false + + click_button create_release_button + + expect(page).to have_content(create_release_notice) + expect(page).to have_photo("person_photo.png") + end + + scenario "updating an existing release" do + talent_release = create(:talent_release, project: project) + + visit project_talent_releases_path(project) + click_link *update_talent_release_link(talent_release) + + fill_in "talent_release[person_first_name]", with: "New" + fill_in "talent_release[person_last_name]", with: "Release Name" + + select "Other", from: "Applicable Media" + fill_in "Describe other applicable media", with: "New Test" + + click_button update_release_button + + expect(page).to have_content(update_release_notice) + expect(page).to have_content("New Release Name") + end + + scenario "deleting an existing release" do + talent_release = create(:talent_release, project: project) + + visit project_talent_releases_path(project) + click_link *destroy_talent_release_link(talent_release) + + expect(page).to have_content(destroy_release_alert) + expect(page).not_to have_content(talent_release.name) + end + + scenario "searching for a release", js: true do + create(:talent_release, person_first_name: "Robert Downey Jr.", person_last_name: "Downey Jr.", project: project) + create(:talent_release, person_first_name: "Chris", person_last_name: "Evans", project: project) + + visit project_talent_releases_path(project) + + within "form#search" do + fill_in "Search", with: "Robert" + click_on "button" + end + + expect(page).to have_content("Robert Downey Jr.") + expect(page).not_to have_content("Chris Evans") + expect(page).to have_field("Search", with: "Robert") + end + + scenario "adding photos to an existing release", js: true do + create(:talent_release, person_first_name: "Robert", person_last_name: "Downey Jr.", project: project) + + visit project_talent_releases_path(project) + + click_on "Manage" + click_on "Photos" + + expect(page).to have_content("Add Photos") + expect(page).to have_content("Robert Downey Jr.") + + drop_file Rails.root.join(file_fixture("location_photo.png")), type: :dropzone + click_on "Save Changes" + + expect(page).to have_content("The release has been updated") + end + + scenario "viewing the contract PDF" do + talent_release = create(:talent_release_with_contract_template_and_photos, + :native, + project: project, + person_first_name: "Jane", + person_last_name: "Doe", + tag_list: "Woman, Brunette", + notes: [ + build(:note, + content: "Note 1", + user: build(:user, email: "jane.doe@test.com"), + email: "jane.doe@test.com", + created_at: DateTime.new(2020, 2, 21, 12, 0, 0),), + build(:note, + content: "Note 2", + user: build(:user, email: "john.doe@test.com"), + email: "john.doe@test.com", + created_at: DateTime.new(2020, 2, 20, 11, 0, 0),), + ]) + + sign_in(current_user) + visit project_talent_releases_path(project) + + click_link *view_release_pdf_link_for(talent_release) + + expect(content_type).to eq("application/pdf") + expect(content_disposition).to include("inline") + expect(pdf_filename).to include("doe-jane") + expect(pdf_body).to have_content("Jane Doe") + expect(pdf_body).to have_content("NOTES") + expect(pdf_body).to have_content("Note 1") + expect(pdf_body).to have_content("jane.doe@test.com") + expect(pdf_body).to have_content("2/21/20 12:00 PM") + expect(pdf_body).to have_content("Note 2") + expect(pdf_body).to have_content("john.doe@test.com") + expect(pdf_body).to have_content("2/20/20 11:00 AM") + expect(pdf_body).to have_content("TAGS") + expect(pdf_body).to have_content("Woman") + expect(pdf_body).to have_content("Brunette") + expect(pdf_body).to have_content photos_heading.upcase + expect(pdf_body).to have_content("person_photo.png") + end + + scenario "viewing the contract PDF for a minor without guardian photo" do + talent_release = create(:talent_release_with_contract_template, :native, :minor, project: project) + + visit project_talent_releases_path(project) + click_link *view_release_pdf_link_for(talent_release) + + expect(content_type).to eq("application/pdf") + expect(content_disposition).to include("inline") + expect(pdf_filename).to include(talent_release.filename_suffix.parameterize) + expect(pdf_body).to have_content(talent_release.name) + expect(pdf_body).to have_content(talent_release.guardian_name) + expect(pdf_body).to have_content photos_heading.upcase + talent_release.photos.each do |photo| + expect(pdf_body).to have_content(photo.filename.to_s) + end + end + + scenario "viewing the contract PDF for a minor with guardian photo" do + talent_release = create(:talent_release_with_contract_template, :native, :minor_with_guardian_photo, project: project) + + visit project_talent_releases_path(project) + click_link *view_release_pdf_link_for(talent_release) + + expect(content_type).to eq("application/pdf") + expect(content_disposition).to include("inline") + expect(pdf_filename).to include(talent_release.filename_suffix.parameterize) + expect(pdf_body).to have_content(talent_release.name) + expect(pdf_body).to have_content(talent_release.guardian_name) + expect(pdf_body).to have_content photos_heading(2).upcase + talent_release.photos.each do |photo| + expect(pdf_body).to have_content(photo.filename.to_s) + end + expect(pdf_body).to have_content(talent_release.guardian_photo.filename.to_s) + end + end + + context "when the user is associate" do + let(:current_user) { create(:user, :associate) } + + scenario "should not show download" do + create(:talent_release_with_contract_template, person_first_name: "Robert", person_last_name: "Downey Jr.", project: project) + + sign_in current_user + visit project_talent_releases_path(project) + + click_on "Manage" + expect(page).not_to have_link("Download", exact: true) + end + end + + private + + def photos_heading(photos_count = 1) + t 'contracts.photos.heading', count: photos_count + end + + def person_is_minor_checkbox + "talent_release_minor" + end + + def guardian_first_name_field + "Guardian first name" + end + + def guardian_last_name_field + "Guardian last name" + end + + def guardian_phone_field + "Guardian phone" + end + + def guardian_photo_field + "talent_release[guardian_photo]" + end + + def have_photo_button + have_selector(".take-photo-button") + end + + def person_first_name_field + "talent_release[person_first_name]" + end + + def person_last_name_field + "talent_release[person_last_name]" + end + + def person_address_field + "talent_release[person_address_street1]" + end + + def person_email_field + "talent_release[person_email]" + end + + def person_phone_field + "talent_release[person_phone]" + end + + def import_talent_release_link(project) + ["Import Release", href: new_project_talent_release_path(project)] + end + + def update_talent_release_link(talent_release) + ["Edit", href: edit_talent_release_path(talent_release)] + end + + def destroy_talent_release_link(talent_release) + ["Delete", href: talent_release_path(talent_release)] + end + + def view_release_pdf_link_for(talent_release) + ["Download", href: talent_release_contracts_path(talent_release, format: "pdf")] + end + + def have_photo(filename) + have_selector("img[src*='#{filename}']", visible: :all) + end + + def create_release_button + t "helpers.submit.talent_release.create" + end + + def create_release_notice + t "talent_releases.create.notice" + end + + def update_release_button + t "helpers.submit.talent_release.update" + end + + def update_release_notice + t "talent_releases.update.notice" + end + + def destroy_release_alert + t "talent_releases.destroy.alert" + end + + def successful_submission_message + "Your release was successfully submitted. Thank you." + end + + def fill_in_exploitable_rights + select "Other", from: "Applicable Media" + fill_in "Describe other applicable media", with: "Test" + select "Other", from: "Territory" + fill_in "Describe other territory", with: "Test" + select "Other", from: "Term" + fill_in "Describe other term", with: "Test" + select "Other", from: "Restriction" + fill_in "Describe other restrictions", with: "Test" + end +end diff --git a/spec/features/user_managing_videos_spec.rb b/spec/features/user_managing_videos_spec.rb new file mode 100644 index 0000000..9dcdfa4 --- /dev/null +++ b/spec/features/user_managing_videos_spec.rb @@ -0,0 +1,118 @@ +require "rails_helper" + +feature "User managing videos" do + let(:current_user) { create(:user) } + let(:project) { create(:project, account: current_user.primary_account) } + let!(:video) do + create(:video, + :with_graphics_only_edl_file, + :with_audio_only_edl_file, + project: project, + name: "Star Wars", + number: "1", + video_editing_system: "adobe_premiere",) + end + + before :each do + sign_in current_user + end + + scenario "creating a video", js: true do + visit project_videos_path(project) + click_link "Upload New Video" + + expect(page).to have_content "Upload Video" + expect(page).to have_link("Avid") + expect(page).to have_link("Adobe Premiere") + + click_link "Adobe Premiere" + + expect(page).to have_content "Upload Video" + expect(page).to have_selector "#video_file", visible: :all + expect(page).to have_selector "#video_edl_file", visible: :all + expect(page).to have_selector "#video_graphics_only_edl_file", visible: :all + expect(page).to have_selector "#video_audio_only_edl_file", visible: :all + expect(page).to have_link "click here", href: /mailto:info@bigmedia\.ai/ + + fill_in_video_fields name: "New name", number: "99" + + # TODO: Figure out how to test video gets created and shows up in project page + end + + scenario "editing a video", js: true do + create(:video, + :with_graphics_only_edl_file, + :with_audio_only_edl_file, + project: project, + name: "Star Wars", + number: "1", + video_editing_system: "adobe_premiere",) + video_data = { + name: "Star Wars", + number: "1", + } + + visit project_videos_path(project) + all('a', :text => /Edit/)[1].click + + within "#current-edl-file" do + expect(page).to have_content("sample-edl.edl") + end + + within "#current-graphics-only-edl-file" do + expect(page).to have_content("sample-edl.edl") + end + + within "#current-audio-only-edl-file" do + expect(page).to have_content("sample-edl.edl") + end + + expect(page).to have_filled_in_data(video_data) + + fill_in_video_fields name: "New name", number: "99" + + accept_alert do + click_button "Save Changes" + end + + expect(page).to have_content(update_video_notice) + end + + scenario "searching for a video", js: true do + create(:video, project: project, name: "First Video") + create(:video, project: project, name: "Second Video") + + visit project_videos_path(project) + + fill_in "query", with: "First" + click_on "search-button" + + expect(page).to have_content("First Video") + expect(page).not_to have_content("Second Video") + + fill_in "query", with: "Second" + click_on "search-button" + expect(page).not_to have_content("First Video") + expect(page).to have_content("Second Video") + end + + private + + def fill_in_video_fields(data) + fill_in "video[name]", with: data[:name] + fill_in "video[number]", with: data[:number] + end + + def have_filled_in_data(data) + have_selector("#video_name[value='#{data[:name]}']") + have_selector("#video_number[value='#{data[:number]}']") + end + + def create_video_notice + t "videos.update.notice" + end + + def update_video_notice + t "videos.update.notice" + end +end diff --git a/spec/features/user_performs_video_analysis_spec.rb b/spec/features/user_performs_video_analysis_spec.rb new file mode 100644 index 0000000..50dbd70 --- /dev/null +++ b/spec/features/user_performs_video_analysis_spec.rb @@ -0,0 +1,1725 @@ +require "rails_helper" + +feature "User performs video analysis" do + let(:current_user) { create(:user, :admin) } # Only admin users can see the VideoAnalysis button now + let(:project) { create(:project, account: current_user.primary_account) } + + let(:video) { create(:video, :with_graphics_only_edl_file, project: project, analysis_status: :pending) } + let(:edl_event) do + BrayniacAI::EdlParseResult.new({ + "channel" => "V", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }) + end + + before :each do + VideoAnalysis.use_overlay_video = false + allow(BrayniacAI::EdlParse).to receive(:create).and_return( + BrayniacAI::EdlParse.new({ results: [edl_event], edl_timecode_start: "00:00:10:00" }) + ) + end + + scenario "does not see Back to Projects" do + sign_in current_user + visit video_video_analyses_path(video) + + expect(page).not_to have_content("Back to Project") + end + + scenario "sees video name with project name" do + sign_in current_user + visit video_video_analyses_path(video) + + expect(page).to have_content("#{video.name} (#{project.name})") + end + + scenario "sees EDLs dropdown's downlod options" do + sign_in current_user + visit video_video_analyses_path(video) + + expect(page).to have_css("a.dropdown-item", text: "Download EDL") + expect(page).to have_css("a.dropdown-item", text: "Download Graphics Only EDL") + expect(page).to have_css("a.dropdown-item", text: "Download Audio Only EDL") + end + + scenario "sees download report button" do + sign_in current_user + visit video_video_analyses_path(video) + click_on "Reports" + + expect(page).to have_link("Production Elements Log") + expect(page).to have_link("GFX Cue List") + expect(page).to have_link("Music Cue Sheet", href: video_audio_reports_path(video, { format: "xlsx", type: "discovery" })) + expect(page).to have_link("BiG Music Cue Sheet", href: video_audio_reports_path(video, { format: "xlsx", type: "big" })) + expect(page).to have_link("Issues and Concerns Report") + end + + scenario "adding a bookmark", js: true do + sign_in current_user + visit video_video_analyses_path(video) + + create_bookmark(video, notes: "Test notes") + + expect(page).to have_bookmark(notes: "Test notes") + end + + scenario "searching for releases", js: true do + robert, chris = [ + create(:appearance_release, project: project, person_first_name: "Robert", person_last_name: "Downey Jr."), + create(:appearance_release, project: project, person_first_name: "Chris", person_last_name: "Evans") + ] + cheers, cipriani = [ + create(:location_release_with_photo, project: project, name: "Cheers"), + create(:location_release_with_photo, project: project, name: "Cipriani") + ] + mac, pc = [ + create(:material_release, name: "Apple MacBook Air", project: project), + create(:material_release, name: "Microsoft Surface Pro", project: project) + ] + acquired_media = create(:acquired_media_release, project: project) + acquired_file_info_1, acquired_file_info_2 = [ + create(:file_info, filename: "Still Image", releasable: acquired_media), + create(:file_info, filename: "Artwork", releasable: acquired_media) + ] + music_release = create(:music_release, project: project) + music_file_info_1, music_file_info_2 = [ + create(:file_info, filename: "I'm not afraid", releasable: music_release), + create(:file_info, filename: "Lose yourself", releasable: music_release) + ] + + sign_in current_user + visit video_video_analyses_path(video) + + within "#appearance_releases_section" do + search_for(:appearance_release, text: "Robert") + expect(page).to have_content("Robert") + expect(page).not_to have_content("Chris") + end + + within "#location_releases_section" do + search_for(:location_release, text: "Cheers") + expect(page).to have_content("Cheers") + expect(page).not_to have_content("Cipriani") + end + + within "#material_releases_section" do + search_for(:material_release, text: "Apple") + expect(page).to have_content("Apple") + expect(page).not_to have_content("Microsoft") + end + + within "#acquired_media_releases_section" do + search_for(:acquired_media_releases, text: "till") + expect(page).to have_content("Still Image") + expect(page).not_to have_content("Artwork") + end + + within "#music_releases_section" do + search_for(:music_releases, text: "not") + expect(page).to have_content("I'm not afraid") + expect(page).not_to have_content("Lose yourself") + end + end + + scenario "viewing EDL information", js: true do + video_event = BrayniacAI::EdlParseResult.new({ + "channel" => "V", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }) + audio_event = BrayniacAI::EdlParseResult.new({ + "channel" => "A1", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name.wav", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }) + + allow(BrayniacAI::EdlParse).to receive(:create).and_return( + BrayniacAI::EdlParse.new({ results: [video_event, audio_event], edl_timecode_start: "00:00:00:00" }) + ) + + sign_in current_user + visit video_video_analyses_path(video) + + click_button "EDL Info" + + expect(page).to have_edl_event_modal + within "#edl_events" do + expect(page).to have_content("V") + expect(page).to have_content("01:00:00:00") + expect(page).to have_content("01:00:02:00") + expect(page).to have_content("00:00:02") + expect(page).to have_content("source_file_name") + expect(page).to have_content("clip_name") + expect(page).to have_content("description") + expect(page).to have_content("A1") + expect(page).to have_content("source_file_name.wav") + end + end + + scenario "seeking to a particular timecode", js: true do + sign_in current_user + visit video_video_analyses_path(video) + + find("#player .container").click + find("#player .container").click + + fill_in "seek", with: "00:00:03:00" + click_button "Seek" + + expect(page).to have_field("seek", with: "00:00:03:00") + expect(page).to have_css(".media-control-indicator", text: "00:03", visible: :all) + + by "putting in a bad timecode" do + fill_in "seek", with: "00:00:03" + click_button "Seek" + + expect(page).to have_field("seek", with: "") + expect(page).to have_css(".media-control-indicator", text: "00:03", visible: :all) + end + end + + scenario "views EDL events", js: true do + video_event = BrayniacAI::EdlParseResult.new({ + "channel" => "V", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }) + audio_event = BrayniacAI::EdlParseResult.new({ + "channel" => "A1", + "start_time" => 0, + "timecode_in" => "01:00:01:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name.wav", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }) + last_audio_event = BrayniacAI::EdlParseResult.new({ + "channel" => "A1", + "start_time" => 0, + "timecode_in" => "01:00:05:00", + "timecode_out" => "01:00:07:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name.wav", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }) + + edl_events = [video_event] + 20.times { |_| edl_events << audio_event } + edl_events.append last_audio_event + + allow(BrayniacAI::EdlParse).to receive(:create).and_return( + BrayniacAI::EdlParse.new({ results: edl_events, edl_timecode_start: "00:00:00:00" }) + ) + + resize_window_to(1_000, 1_000) do + sign_in current_user + visit video_video_analyses_path(video) + + within "#edl_events_list" do + expect(page).to have_content "Channel" + expect(page).to have_content "Source" + expect(page).to have_content "Clip" + expect(page).to have_content "TC In" + expect(page).to have_content "TC Out" + expect(page).to have_content "V" + expect(page).to have_content "source_file_name" + expect(page).to have_content "clip_name" + expect(page).to have_content "01:00:00:00" + expect(page).to have_content "01:00:02:00" + expect(page).to have_content "A1" + expect(page).to have_content "source_file_name.wav" + end + + by "jumping to a specific EDL event using timecode in" do + expect(find("[data-timecode-in='01:00:00:00']")).not_to be_obscured + + fill_in "go_to", with: "01:00:05:00" + click_on "Go" + + expect(find("[data-timecode-in='01:00:00:00']")).to be_obscured + end + + and_by "putting in a bad timecode" do + fill_in "go_to", with: "bob" + click_on "Go" + + expect(page).to have_field("go_to", with: "") + end + + end + end + + context "for Video tab" do + scenario "video analysis is not complete", js: true do + appearance_release = create(:appearance_release, project: project) + bookmark = create(:bookmark, video: video, notes: "Test notes") + unreleased_appearance = create(:unreleased_appearance, video: video, notes: "Test notes") + + sign_in current_user + visit video_video_analyses_path(video) + + expect(page).to have_content("Video is still being analyzed") + expect(page).to have_bookmark(notes: bookmark.notes) + expect(page).to have_unreleased_appearance(notes: unreleased_appearance.notes) + end + + scenario "confirming a talent release" do + talent_release = create(:talent_release, project: project) + + sign_in current_user + visit video_video_analyses_path(video) + + expect(page).to have_unconfirmed_release(talent_release) + + confirm_release(video, talent_release) + + expect(page).to have_confirmed_release(talent_release) + expect(page).not_to have_unconfirmed_release(talent_release) + + unconfirm_release(VideoReleaseConfirmation.last) + + expect(page).not_to have_confirmed_release(talent_release) + expect(page).to have_unconfirmed_release(talent_release) + end + + scenario "confirming an appearance release", js: true do + appearance_release = create(:appearance_release, project: project) + + sign_in current_user + visit video_video_analyses_path(video) + + expect(page).to have_unconfirmed_release(appearance_release) + + confirm_first_release_using_edl_modal(video, appearance_release) + + expect(page).to have_confirmed_release(appearance_release) + expect(page).not_to have_unconfirmed_release(appearance_release) + + unconfirm_release(VideoReleaseConfirmation.last) + + expect(page).not_to have_confirmed_release(appearance_release) + expect(page).to have_unconfirmed_release(appearance_release) + end + + scenario "confirming a location release", js: true do + location_release = create(:location_release_with_photo, project: project) + + sign_in current_user + visit video_video_analyses_path(video) + + expect(page).to have_unconfirmed_release(location_release) + + confirm_first_release_using_edl_modal(video, location_release) + + expect(page).to have_confirmed_release(location_release) + expect(page).not_to have_unconfirmed_release(location_release) + + unconfirm_release(VideoReleaseConfirmation.last) + + expect(page).not_to have_confirmed_release(location_release) + expect(page).to have_unconfirmed_release(location_release) + end + + scenario "confirming an acquired media release", js: true do + acquired_media_release = create(:acquired_media_release_with_file_infos, project: project) + acquired_media_release.file_infos.first.update(filename: "shark jumping") + acquired_media_release.file_infos.second.update(filename: "pig flying") + acquired_media_release.file_infos.third.update(filename: "panda sneezing") + + sign_in current_user + visit video_video_analyses_path(video) + + expect(page).to have_unconfirmed_file_info_release(acquired_media_release, "shark jumping", count: 1) + expect(page).to have_unconfirmed_file_info_release(acquired_media_release, "pig flying", count: 1) + expect(page).to have_unconfirmed_file_info_release(acquired_media_release, "panda sneezing", count: 1) + + confirm_first_release_using_edl_modal(video, acquired_media_release, text: "shark jumping") + + expect(page).to have_confirmed_release(acquired_media_release, text: "shark jumping") + expect(page).to have_unconfirmed_file_info_release(acquired_media_release, "pig flying", count: 1) + expect(page).to have_unconfirmed_file_info_release(acquired_media_release, "panda sneezing", count: 1) + + unconfirm_release(VideoReleaseConfirmation.last) + + expect(page).not_to have_confirmed_release(acquired_media_release, text: "shark jumping") + expect(page).to have_unconfirmed_file_info_release(acquired_media_release, "shark jumping", count: 1) + expect(page).to have_unconfirmed_file_info_release(acquired_media_release, "pig flying", count: 1) + expect(page).to have_unconfirmed_file_info_release(acquired_media_release, "panda sneezing", count: 1) + end + + scenario "confirming a material release", js: true do + material_release = create(:material_release, project: project) + + sign_in current_user + visit video_video_analyses_path(video) + + expect(page).to have_unconfirmed_release(material_release) + + confirm_first_release_using_edl_modal(video, material_release) + + expect(page).to have_confirmed_release(material_release) + expect(page).not_to have_unconfirmed_release(material_release) + + unconfirm_release(VideoReleaseConfirmation.last) + + expect(page).not_to have_confirmed_release(material_release) + expect(page).to have_unconfirmed_release(material_release) + end + + + scenario "confirming an appearance release match", js: true do + video.analysis_success! + appearance_release = create(:appearance_release, project: project) + allow(BrayniacAI::FacialRecognition).to receive(:find).and_return( + BrayniacAI::FacialRecognition.new({ + bucket_name: "", + overlay_video_url: nil, + first_appearances: [ + BrayniacAI::FacialRecognitionResult.new({ + "start_time": 0, + "end_time": 100, + "external_image_id": "appearance_release_#{appearance_release.id}", + }) + ], + }) + ) + + sign_in current_user + visit video_video_analyses_path(video) + + find("#player .container").click + find("#player .container").click + + expect(page).to have_suggested_match(appearance_release, text: "Jane Doe") + + within "#suggested_matches" do + expect(page).not_to have_css(".fa-check-circle") + end + + confirm_suggested_match(video, appearance_release) + + within "#suggested_matches" do + expect(page).to have_css(".fa-check-circle") + end + + expect(page).to have_confirmed_release(appearance_release, text: "") + + unconfirm_release(VideoReleaseConfirmation.last) + + within "#suggested_matches" do + expect(page).not_to have_css(".fa-check-circle") + end + end + + scenario "confirming a talent release match", js: true do + video.analysis_success! + talent_release = create(:talent_release, project: project) + allow(BrayniacAI::FacialRecognition).to receive(:find).and_return( + BrayniacAI::FacialRecognition.new({ + bucket_name: "", + overlay_video_url: nil, + first_appearances: [ + BrayniacAI::FacialRecognitionResult.new({ + "start_time": 0, + "end_time": 100, + "external_image_id": "talent_release_#{talent_release.id}", + }) + ], + }) + ) + + sign_in current_user + visit video_video_analyses_path(video) + + find("#player .container").click + find("#player .container").click + + expect(page).to have_suggested_match(talent_release, text: "Jane Doe") + + within "#suggested_matches" do + expect(page).not_to have_css(".fa-check-circle") + end + + confirm_suggested_talent_match(video, talent_release) + + within "#suggested_matches" do + expect(page).to have_css(".fa-check-circle") + end + + expect(page).to have_confirmed_release(talent_release, text: "") + + unconfirm_release(VideoReleaseConfirmation.last) + + within "#suggested_matches" do + expect(page).not_to have_css(".fa-check-circle") + end + end + + scenario "copying data from multiple EDL events ", js: true do + edl_events = [ + BrayniacAI::EdlParseResult.new({ + "channel" => "V", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name1", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }), + BrayniacAI::EdlParseResult.new({ + "channel" => "V", + "start_time" => 15, + "timecode_in" => "01:00:15:00", + "timecode_out" => "01:00:18:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name2", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }) + ] + allow(BrayniacAI::EdlParse).to receive(:create).and_return( + BrayniacAI::EdlParse.new({ results: edl_events, edl_timecode_start: "00:00:00:00" }) + ) + + release = create(:appearance_release, project: project) + + sign_in current_user + visit video_video_analyses_path(video) + + new_path = polymorphic_path([:new, video, release, :video_release_confirmation]) + first("form[action='#{new_path}']").click_button + + expect(page).to have_content "Timecode in" + expect(page).to have_content "Source file name" + + click_on "Show/Hide EDL Events" + within "#edl_events" do + expect(page).to have_content("V") + expect(page).to have_content("source_file_name2") + end + click_on_edl_event(2) + + expect(page).to have_field("video_release_confirmation[timecode_in]", with: "01:00:15:00") + expect(page).to have_field("video_release_confirmation[source_file_name]", with: "source_file_name2") + end + + scenario "filters out non-video EDL events", js: true do + edl_events = [ + BrayniacAI::EdlParseResult.new({ + "channel" => "V", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }), + BrayniacAI::EdlParseResult.new({ + "channel" => "A1", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name.wav", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }), + ] + allow(BrayniacAI::EdlParse).to receive(:create).and_return( + BrayniacAI::EdlParse.new({ results: edl_events, edl_timecode_start: "00:00:00:00" }) + ) + + release = create(:appearance_release, project: project) + + sign_in current_user + visit video_video_analyses_path(video) + + new_path = polymorphic_path([:new, video, release, :video_release_confirmation]) + first("form[action='#{new_path}']").click_button + + expect(page).to have_content "Timecode in" + expect(page).to have_content "Source file name" + + click_on "Show/Hide EDL Events" + within "#edl_events" do + expect(page).not_to have_content("A1") + expect(page).to have_content("V") + expect(page).to have_content("source_file_name") + end + end + end + + context "for Graphics tab" do + scenario "confirming graphics element match", js: true do + video.analysis_success! + allow(BrayniacAI::FacialRecognition).to receive(:find).and_return( + BrayniacAI::FacialRecognition.new({ + bucket_name: "", + overlay_video_url: nil, + first_appearances: [], + }) + ) + allow(BrayniacAI::EdlParse).to receive(:create).and_return( + BrayniacAI::EdlParse.new({ + results: [ + BrayniacAI::EdlParseResult.new({ + "channel" => "V", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }), + ], + edl_timecode_start: "00:00:00:00", + }) + ) + + sign_in current_user + visit video_video_analyses_path(video) + + click_link "Graphics" + + expect(page).not_to have_graphics_element_match(text: "source_file_name") + + by "verifying a graphics element displays after video starts" do + find("#player .container").click + find("#player .container").click + + expect(page).to have_graphics_element_match(text: "source_file_name") + end + + and_by "adding a graphics match to the report" do + confirm_graphics_element_match + expect(page.find("#matched_file_name").value).to eq "source_file_name" + expect(page).to have_field("Source EDL", with: "Graphics") + + create_graphics_element(video, text: "MTV") + + expect(page).to have_graphics_element(text: "01:00:00:00") + expect(page).to have_confirmed_graphics_match(text: "source_file_name") + end + + and_by "verifying checkmark gets removed if graphics removed from report" do + delete_graphics_element(GraphicsElement.last) + + expect(page).not_to have_confirmed_graphics_match(text: "source_file_name") + expect(page).not_to have_graphics_element(text: "01:00:00:00") + end + end + + scenario "managing a graphics element", js: true do + sign_in current_user + visit video_video_analyses_path(video) + + click_link "Graphics" + + open_graphics_element_modal(video) + + click_on "Show/Hide EDL Events" + + within '#edl_events' do + expect(page).to have_content "V" + expect(page).to have_content "00:00:00" + expect(page).to have_content "00:00:02" + expect(page).to have_content "00:00:02" + expect(page).to have_content "source_file_name" + expect(page).to have_content "clip_name" + expect(page).to have_content "description" + end + + expect(page).to have_field("Source EDL", with: "All Tracks") + expect(page).to have_field("Timecode in", with: "01:00:00:00") + expect(page).to have_field("Timecode out", with: "01:00:02:00") + expect(page).to have_field("Duration", with: "00:00:02") + expect(page).to have_field("Source file name", with: "source_file_name") + expect(page).to have_field("Clip name", with: "clip_name") + expect(page).to have_field("Description", with: "description") + + create_graphics_element(video, text: "MTV") + + expect(page).to have_graphics_element(text: "01:00:00:00") + + open_edit_graphics_modal + + expect(page).to have_field("Source EDL", with: "All Tracks") + + edit_graphics_element(text: "Nickelodeon") + + expect(page).to have_graphics_element(text: "01:00:00:00") + end + + scenario "has new layout" do + sign_in current_user + visit video_video_analyses_path(video) + + click_link "Graphics" + + expect(page).to have_content("EDL #") + expect(page).to have_content("EDL TC") + expect(page).to have_content("VID TC") + expect(page).to have_content("Filename") + end + + + scenario "adding a graphics element requires text", js: true do + sign_in current_user + visit video_video_analyses_path(video) + + open_graphics_element_modal(video) + + create_graphics_element(video, text: "") + + expect(page).to have_graphics_element_modal + end + + scenario "copying data from multiple EDL events ", js: true do + edl_events = [ + BrayniacAI::EdlParseResult.new({ + "channel" => "V", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name1", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }), + BrayniacAI::EdlParseResult.new({ + "channel" => "V", + "start_time" => 15, + "timecode_in" => "01:00:15:00", + "timecode_out" => "01:00:18:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name2", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }) + ] + allow(BrayniacAI::EdlParse).to receive(:create).and_return( + BrayniacAI::EdlParse.new({ results: edl_events, edl_timecode_start: "00:00:00:00" }) + ) + + sign_in current_user + visit video_video_analyses_path(video) + + find("#player .container").click + find("#player .container").click + + open_graphics_element_modal(video) + + expect(page).to have_content "Timecode in" + expect(page).to have_content "Source file name" + + click_on "Show/Hide EDL Events" + within "#edl_events" do + expect(page).to have_content("V") + expect(page).to have_content("source_file_name2") + end + click_on_edl_event(2) + + expect(page).to have_field("graphics_element[timecode_in]", with: "01:00:15:00") + expect(page).to have_field("graphics_element[source_file_name]", with: "source_file_name2") + end + + scenario "filters out non-video EDL events", js: true do + edl_events = [ + BrayniacAI::EdlParseResult.new({ + "channel" => "V", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }), + BrayniacAI::EdlParseResult.new({ + "channel" => "A1", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name.wav", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }), + ] + allow(BrayniacAI::EdlParse).to receive(:create).and_return( + BrayniacAI::EdlParse.new({ results: edl_events, edl_timecode_start: "00:00:00:00" }) + ) + + sign_in current_user + visit video_video_analyses_path(video) + + open_graphics_element_modal(video) + + expect(page).to have_content "Timecode in" + expect(page).to have_content "Source file name" + + click_on "Show/Hide EDL Events" + within "#edl_events" do + expect(page).to have_content("V") + expect(page).to have_content("source_file_name") + expect(page).not_to have_content("A1") + expect(page).not_to have_content("source_file_name.wav") + end + end + end + + context "for Audio tab" do + + before :each do + video.analysis_success! + allow(BrayniacAI::FacialRecognition).to receive(:find).and_return( + BrayniacAI::FacialRecognition.new({ + bucket_name: "", + overlay_video_url: nil, + first_appearances: [], + }) + ) + end + + scenario "confirming an acquired media match", js: true do + skip "TODO: Finish implementing this functionality" + + video.audio_analysis_success! + acquired_media_release = create(:acquired_media_release, + project: project, + file_infos: create_list(:file_info, 1, filename: "acquired_file_name")) + allow(BrayniacAI::AudioRecognition).to receive(:find).and_return( + OpenStruct.new({ + results: [ + OpenStruct.new( + uid: acquired_media_release.file_infos.first.id, + requested_filename: acquired_media_release.file_infos.first.filename, + type: "acquired_media", + audio_data: OpenStruct.new( + filename: acquired_media_release.file_infos.first.filename, + composers: [], + publishers: [], + catalog: "", + title: "", + ), + edl: [ + OpenStruct.new( + start_time: 0, + timecode_in: "01:00:00:00", + ), + ] + ) + ] + }) + ) + allow(BrayniacAI::EdlParse).to receive(:create).and_return( + BrayniacAI::EdlParse.new({ + results: [ + BrayniacAI::EdlParseResult.new({ + "channel" => "A1", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => acquired_media_release.file_infos.first.filename, + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }), + ], + edl_timecode_start: "00:00:00:00", + fps: 29.97, + edl_offset_seconds: 0, + }) + ) + + sign_in current_user + visit video_video_analyses_path(video) + + click_link "Audio" + + expect(page).not_to have_audio_match(text: "(A) acquired_file_name") + + by "verifying a music match displays after video starts" do + find("#player .container").click + find("#player .container").click + + expect(page).to have_audio_match(text: "(A) acquired_file_name") + end + + within "#audio_matches" do + expect(page).not_to have_css(".fa-check-circle") + end + + confirm_acquired_media_match(video) + + sleep(1) + + within "#audio_matches" do + expect(page).to have_css(".fa-check-circle") + match_text = page.find(".fa-check-circle").find(:xpath, "../..").text + expect(match_text).to include("(A) acquired_file_name") + end + + expect(page).to have_confirmed_audio("acquired_file_name") + + and_by "verifying checkmark gets removed if confirmation removed from report" do + unconfirm_audio(AudioConfirmation.last) + + expect(page).not_to have_confirmed_audio("acquired_file_name") + + within "#audio_matches" do + expect(page).not_to have_css(".fa-check-circle") + end + end + end + + scenario "confirming an original music match", js: true do + video.audio_analysis_success! + music_release = create(:music_release, + project: project, + file_infos: build_list(:file_info, 1, filename: "original_music_file_name.wav")) + allow(BrayniacAI::AudioRecognition).to receive(:find).and_return( + OpenStruct.new( + results: [ + OpenStruct.new( + uid: music_release.file_infos.first.id, + requested_filename: music_release.file_infos.first.filename, + type: "original_music", + audio_data: OpenStruct.new( + filename: music_release.file_infos.first.filename, + composers: [], + publishers: [], + catalog: "", + title: "", + ), + edl: [ + OpenStruct.new( + start_time: 0, + timecode_in: "01:00:00:00", + ), + ] + ), + ] + ) + ) + allow(BrayniacAI::EdlParse).to receive(:create).and_return( + BrayniacAI::EdlParse.new({ + results: [ + BrayniacAI::EdlParseResult.new({ + "channel" => "A1", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => music_release.file_infos.first.filename, + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }), + ], + edl_timecode_start: "00:00:00:00", + fps: 29.97, + edl_offset_seconds: 0, + }) + ) + + sign_in current_user + visit video_video_analyses_path(video) + + click_link "Audio" + + expect(page).not_to have_audio_match(text: "(O) original_music_file_name") + + by "verifying a music match displays after video starts" do + find("#player .container").click + find("#player .container").click + + expect(page).to have_audio_match(text: "(O) original_music_file_name") + end + + within "#audio_matches" do + expect(page).not_to have_css(".fa-check-circle") + end + + confirm_original_music_match(video, "original_music_file_name") + + sleep(1) + + within "#audio_matches" do + expect(page).to have_css(".fa-check-circle") + match_text = page.find(".fa-check-circle").find(:xpath, "../..").text + expect(match_text).to include("(O) original_music_file_name") + end + + expect(page).to have_confirmed_audio("original_music_file_name") + + and_by "verifying checkmark gets removed if confirmation removed from report" do + unconfirm_audio(AudioConfirmation.last) + + expect(page).not_to have_confirmed_audio("original_music_file_name.wav") + + within "#audio_matches" do + expect(page).not_to have_css(".fa-check-circle") + end + end + end + + scenario "confirming a library music match", js: true do + video.audio_analysis_success! + allow(BrayniacAI::AudioRecognition).to receive(:find).and_return( + OpenStruct.new( + results: [ + OpenStruct.new( + uid: "abc123", + requested_filename: "library_file_name.wav", + type: "library_music", + audio_data: OpenStruct.new( + filename: "library_file_name.wav", + composers: [], + publishers: [], + catalog: "", + title: "", + ), + edl: [ + OpenStruct.new( + start_time: 0, + timecode_in: "01:00:00:00", + ), + ] + ), + ] + ) + ) + allow(BrayniacAI::EdlParse).to receive(:create).and_return( + BrayniacAI::EdlParse.new({ + results: [ + BrayniacAI::EdlParseResult.new({ + "channel" => "A1", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "library_file_name.wav", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }), + ], + edl_timecode_start: "00:00:00:00", + fps: 29.97, + edl_offset_seconds: 0, + }) + ) + + sign_in current_user + visit video_video_analyses_path(video) + + click_link "Audio" + + expect(page).not_to have_audio_match(text: "(L) library_file_name.wav") + + by "verifying a music match displays after video starts" do + find("#player .container").click + find("#player .container").click + + expect(page).to have_audio_match(text: "(L) library_file_name.wav") + end + + within "#audio_matches" do + expect(page).not_to have_css(".fa-check-circle") + end + + confirm_library_music_match(video) + + sleep(1) + + within "#audio_matches" do + expect(page).to have_css(".fa-check-circle") + match_text = page.find(".fa-check-circle").find(:xpath, "../..").text + expect(match_text).to include("(L) library_file_name.wav") + end + + expect(page).to have_confirmed_audio("library_file_name.wav") + + and_by "verifying checkmark gets removed if confirmation removed from report" do + unconfirm_audio(AudioConfirmation.last) + + expect(page).not_to have_confirmed_audio("library_file_name.wav") + + within "#audio_matches" do + expect(page).not_to have_css(".fa-check-circle") + end + end + end + + scenario "confirming a music release", js: true do + music_release = create(:music_release, + project: project, + file_infos: [ + build(:file_info, filename: "shark jumping"), + build(:file_info, filename: "pig flying"), + build(:file_info, filename: "panda sneezing"), + ] + ) + allow(BrayniacAI::EdlParse).to receive(:create).and_return( + BrayniacAI::EdlParse.new({ + results: [ + BrayniacAI::EdlParseResult.new({ + "channel" => "A1", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "shark jumping.wav", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }), + ], + edl_timecode_start: "00:00:00:00", + }) + ) + + sign_in current_user + visit video_video_analyses_path(video) + + click_link "Audio" + + find("#player .container").click + find("#player .container").click + + expect(page).to have_unconfirmed_file_info_release(music_release, "shark jumping", count: 1) + expect(page).to have_unconfirmed_file_info_release(music_release, "pig flying", count: 1) + expect(page).to have_unconfirmed_file_info_release(music_release, "panda sneezing", count: 1) + + confirm_first_music_release_using_edl_modal(video, text: "shark jumping") + + expect(page).not_to have_unconfirmed_file_info_release(music_release, "shark jumping", count: 1) + expect(page).to have_unconfirmed_file_info_release(music_release, "pig flying", count: 1) + expect(page).to have_unconfirmed_file_info_release(music_release, "panda sneezing", count: 1) + + expect(page).to have_confirmed_audio("shark jumping") + + unconfirm_audio(AudioConfirmation.last) + + expect(page).not_to have_confirmed_audio("shark jumping") + expect(page).to have_unconfirmed_file_info_release(music_release, "shark jumping", count: 1) + expect(page).to have_unconfirmed_file_info_release(music_release, "pig flying", count: 1) + expect(page).to have_unconfirmed_file_info_release(music_release, "panda sneezing", count: 1) + end + + scenario "filters out non-audio EDL events", js: true do + skip "TODO: Fix this test" + + edl_events = [ + BrayniacAI::EdlParseResult.new({ + "channel" => "V", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }), + BrayniacAI::EdlParseResult.new({ + "channel" => "A1", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name.wav", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }), + ] + allow(BrayniacAI::EdlParse).to receive(:create).and_return( + BrayniacAI::EdlParse.new({ results: edl_events, edl_timecode_start: "00:00:00:00" }) + ) + video = create(:video, :with_audio_only_edl_file, project: project, analysis_status: :success) + + sign_in current_user + visit video_video_analyses_path(video) + + open_audio_confirmation_modal + + expect(page).to have_field("Origin", with: "Original Music") + expect(page).to have_content "Timecode in" + expect(page).to have_content "Source file name" + expect(page).to have_field("Source EDL", with: "Audio") + + click_on "Show/Hide EDL Events" + within "#edl_events" do + expect(page).not_to have_content("V") + expect(page).to have_content("A1") + expect(page).to have_content("source_file_name.wav") + end + end + + scenario "manually creating an audio confirmation", js: true do + skip "TODO: Fix this test" + + sign_in current_user + visit video_video_analyses_path(video) + + click_link "Audio" + + click_button "Add to Music Cue Sheet" + + expect(page).to have_content "Confirm Audio" + + within "#new_audio_confirmation_modal" do + fill_in "Title", with: "Two Waves" + select "Vocal", from: "Music type" + select "Feature", from: "Music category" + expect(page).to have_field("Source EDL", with: "All Tracks") + expect(page).to have_field("Channel", with: "A1") + expect(page).to have_field("Origin", with: "Library Music") + expect(page).to have_field("Timecode in", with: "01:00:00:00") + expect(page).to have_field("Timecode out", with: "01:00:02:00") + expect(page).to have_field("Duration", with: "00:00:02") + expect(page).to have_field("Source file name", with: "source_file_name.wav") + expect(page).to have_field("Clip name", with: "clip_name") + expect(page).to have_field("Description", with: "description") + + click_on "Confirm Audio" + end + + expect(page).to have_confirmed_audio("source_file_name.wav") + end + + scenario "has new layout" do + sign_in current_user + visit video_video_analyses_path(video) + + click_link "Audio" + + expect(page).to have_content("EDL #") + expect(page).to have_content("EDL TC") + expect(page).to have_content("VID TC") + expect(page).to have_content("Filename") + end + end + + context "for Issues and Concerns" do + scenario "manages Unreleased appearances", js: true do + sign_in current_user + visit video_video_analyses_path(video) + + click_link "Issues & Concerns" + + by "adding an issues and concerns item" do + open_unreleased_modal(video) + + expect(page).to have_selector("#unreleased_appearance_note_category") + select_option("#unreleased_appearance_note_category","Missing talent release") + expect(page).not_to have_selector("#unreleased_appearance_notes") + select_option("#unreleased_appearance_note_category","Other") + expect(page).to have_selector("#unreleased_appearance_notes") + + create_unreleased_appearance(video, notes: "Many issues present") + + expect(page).to have_selector("#issues_and_concerns_list", text: "Many issues present") + expect(page).to have_selector("#issues_and_concerns_list", text: "Channel") + expect(page).to have_selector("#issues_and_concerns_list", text: "EDL TC") + expect(page).to have_selector("#issues_and_concerns_list", text: "Video TC") + end + + and_by "editing an issues and concerns item" do + edit_issues_and_concerns_item(text: "Many more issues present") + + expect(page).to have_selector("#issues_and_concerns_list", text: "Many more issues present") + end + + by "adding an issues and concerns item with predefined note" do + open_unreleased_modal(video) + select_option("#unreleased_appearance_note_category","Missing talent release") + click_button "Create Issue/Concern" + expect(page).to have_selector("#issues_and_concerns_list", text: "Missing talent release") + end + + and_by "removing an issues and concerns item" do + delete_issues_and_concerns(UnreleasedAppearance.last) + + expect(page).not_to have_selector("#issues_and_concerns_list", text: "Missing talent release") + end + end + + scenario "copying data from multiple EDL events ", js: true do + edl_events = [ + BrayniacAI::EdlParseResult.new({ + "channel" => "V", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name1", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }), + BrayniacAI::EdlParseResult.new({ + "channel" => "V", + "start_time" => 15, + "timecode_in" => "01:00:15:00", + "timecode_out" => "01:00:18:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name2", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }) + ] + allow(BrayniacAI::EdlParse).to receive(:create).and_return( + BrayniacAI::EdlParse.new({ results: edl_events, edl_timecode_start: "00:00:00:00" }) + ) + + sign_in current_user + visit video_video_analyses_path(video) + + open_unreleased_modal(video) + + expect(page).to have_content "Timecode in" + expect(page).to have_content "Source file name" + + click_on "Show/Hide EDL Events" + within "#edl_events" do + expect(page).to have_content("V") + expect(page).to have_content("source_file_name2") + end + click_on_edl_event(2) + + expect(page).to have_field("unreleased_appearance[timecode_in]", with: "01:00:15:00") + expect(page).to have_field("unreleased_appearance[source_file_name]", with: "source_file_name2") + end + + scenario "does not filter EDL events", js: true do + edl_events = [ + BrayniacAI::EdlParseResult.new({ + "channel" => "V", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }), + BrayniacAI::EdlParseResult.new({ + "channel" => "A1", + "start_time" => 0, + "timecode_in" => "01:00:00:00", + "timecode_out" => "01:00:02:00", + "duration" => "00:00:02", + "source_file_name" => "source_file_name.wav", + "clip_name" => "clip_name", + "description" => "description", + "matches" => [], + }), + ] + allow(BrayniacAI::EdlParse).to receive(:create).and_return( + BrayniacAI::EdlParse.new({ results: edl_events, edl_timecode_start: "00:00:00:00" }) + ) + + sign_in current_user + visit video_video_analyses_path(video) + + open_unreleased_modal(video) + + expect(page).to have_content "Timecode in" + expect(page).to have_content "Source file name" + + click_on "Show/Hide EDL Events" + within "#edl_events" do + expect(page).to have_content("V") + expect(page).to have_content("source_file_name") + expect(page).to have_content("A1") + expect(page).to have_content("source_file_name.wav") + end + end + + scenario "clicking video TC in issues & concerns tab seeks video to TC", js: true do + create(:unreleased_appearance, video: video, notes: "Test issue", time_elapsed: 2) + + sign_in current_user + visit video_video_analyses_path(video) + + find("#player .container").click + find("#player .container").click + + click_link "Issues & Concerns" + + TC_time_selector = "[data-behavior=seekable-timecode]" + + expect(page).to have_content "Test issue" + expect(page).to have_css(TC_time_selector, text: "00:00:02:00") + + find(TC_time_selector).click + + expect(page).to have_css(".media-control-indicator", text: "00:02", visible: :all) + end + end + + context "for Notes tab" do + scenario "clicking video TC seeks video to TC", js: true do + bookmark = create(:bookmark, video: video, notes: "Test notes", time_elapsed: 5) + + sign_in current_user + visit video_video_analyses_path(video) + + find("#player .container").click + find("#player .container").click + + + click_link "Notes" + + expect(page).to have_content "Test notes" + expect(page).to have_css("#bookmark_#{bookmark.id} > td:nth-child(3)", text: "00:00:05:00") + + find("#bookmark_#{bookmark.id} > td:nth-child(3)").click + + expect(page).to have_css(".media-control-indicator", text: "00:05", visible: :all) + end + end + + private + + def confirm_release(video, release) + action = polymorphic_path([video, release, :video_release_confirmations]) + form = find("form[action='#{action}']") + confirm_button = "Add to Report" + + within form do + click_button confirm_button + end + end + + def confirm_suggested_match(video, release) + new_path = polymorphic_path([:new, video, release, :video_release_confirmation]) + create_path = polymorphic_path([video, release, :video_release_confirmations]) + + first("#suggested_matches form[action='#{new_path}']").click_button + + expect(page).to have_content "Timecode in" + + within "form[action='#{create_path}']" do + click_button "Confirm Release" + end + end + + def confirm_original_music_match(video, filename) + open_audio_confirmation_modal + create_audio_confirmation(video, filename: "original_music_file_name") + end + + def open_audio_confirmation_modal + page.execute_script <<-JS + $("#audio_matches .releasable-match button")[0].click() + JS + + expect(page).to have_content "Timecode in" + end + + def select_option(select_id, text) + page.find(select_id).find(:option, text).select_option + end + + def create_audio_confirmation(video, filename: "source_file_name") + create_path = video_video_analyses_audio_confirmations_path(video) + + within "form[action='#{create_path}']" do + expect(page).to have_selector("#matched_file_name", text: filename) + click_button "Confirm Audio" + end + end + + def confirm_library_music_match(video) + create_path = video_video_analyses_audio_confirmations_path(video) + + page.execute_script <<-JS + $("#audio_matches .releasable-match button")[0].click() + JS + + expect(page).to have_field("Origin", with: "Library Music") + expect(page).to have_content "Timecode in" + + within "form[action='#{create_path}']" do + expect(page).to have_selector("#matched_file_name", text: "library_file_name") + click_button "Confirm Audio" + end + end + + def confirm_suggested_talent_match(video, release) + create_path = polymorphic_path([video, release, :video_release_confirmations]) + + first("#suggested_matches form[action='#{create_path}']").click_button + end + + def unconfirm_release(confirmation) + url = polymorphic_path([confirmation.video, confirmation.releasable, confirmation]) + + click_link "", href: url, visible: false + end + + def unconfirm_audio(confirmation) + path = audio_confirmation_path(confirmation) + + find("#audio_confirmations a[href='#{path}']").click + end + + def confirm_first_release_using_edl_modal(video, release, text: "") + new_path = polymorphic_path([:new, video, release, :video_release_confirmation]) + create_path = polymorphic_path([video, release, :video_release_confirmations]) + + first("form[action='#{new_path}']", text: text).click_button + + expect(page).to have_content "Timecode in" + + within "form[action='#{create_path}']" do + click_button "Confirm Release" + end + end + + def confirm_first_music_release_using_edl_modal(video, text: "") + new_path = new_video_video_analyses_audio_confirmation_path(video) + create_path = video_video_analyses_audio_confirmations_path(video) + + page.execute_script <<-JS + $("[data-ujs-target=audio-confirmation-form] input[name='audio_confirmation[time_elapsed]']").attr("value", "0") + JS + + first("#music_releases form[action='#{new_path}']", text: text).click_button + + expect(page).to have_field("Origin", with: "Original Music") + expect(page).to have_content "Timecode in" + + within "form[action='#{create_path}']" do + select "Vocal", from: "Music type" + select "Feature", from: "Music category" + fill_in "Source file name", with: text + click_button "Confirm Audio" + end + end + + def click_on_edl_event(index) + all("[data-behavior=fillable-fields]")[index - 1].click + end + + def create_bookmark(video, notes:) + new_path = new_video_bookmark_path(video) + create_path = video_bookmarks_path(video) + + # Opens the new bookmark form + within "form[action='#{new_path}']" do + click_button + end + + # Fills out and submits the bookmark form + within "form[action='#{create_path}']" do + fill_in "Notes", with: notes + click_button "Create" + end + end + + def open_graphics_element_modal(video) + new_path = new_video_video_analyses_graphics_element_path(video) + + page.execute_script("document.querySelector(\"input[name='graphics_element[time_elapsed]']\").setAttribute(\"value\", 0)") + + within "form[action='#{new_path}']" do + click_button + end + end + + def confirm_graphics_element_match + page.execute_script <<-JS + $("#graphics_elements_matches .graphics-match button")[0].click() + JS + end + + def create_graphics_element(video, text:) + create_path = video_video_analyses_graphics_elements_path(video) + + within "form[action='#{create_path}']" do + select "Subtitle", from: "Graphic type" + fill_in "Text", with: text + click_button "Create GFX Element" + end + end + + def open_edit_graphics_modal + within "#graphics_elements_list" do + find(".fa-pencil").click + end + end + + def edit_graphics_element(text:) + within "#edit_graphics_element_modal" do + select "Logo", from: "Graphic type" + fill_in "Text", with: text + click_button "Update GFX Element" + end + end + + def delete_graphics_element(graphics_element) + path = graphics_element_path(graphics_element) + + find("#graphics_elements_list a[href='#{path}']").click + end + + def open_unreleased_modal(video) + new_path = new_video_video_analyses_unreleased_appearance_path(video) + + within "form[action='#{new_path}']" do + click_button + end + end + + def create_unreleased_appearance(video, notes:) + create_path = video_video_analyses_unreleased_appearances_path(video) + + # Fills out and submits the unreleased appearance form + within "form[action='#{create_path}']" do + if notes + select "Other", from: "Note" + fill_in "Notes", with: notes + end + click_button "Create" + end + end + + def edit_issues_and_concerns_item(text:) + within "#issues_and_concerns_list" do + click_link "Edit" + end + + within "#edit_unreleased_appearance_modal" do + fill_in "Notes", with: text + click_button "Update Issue/Concern" + end + end + + def delete_issues_and_concerns(unreleased_appearance) + path = unreleased_appearance_path(unreleased_appearance) + + find("#issues_and_concerns_list a[href='#{path}']").click + end + + def have_unconfirmed_file_info_release(release, text, count: 1) + scope = "#" + release.class.to_s.tableize + have_selector("#{scope} [data-confirmed='false'][data-hidden='false']", text: text, count: count) + end + + def have_acquired_media_release_suggested_match(release, file_info_id, text:) + have_selector("#suggested_matches [data-ujs-target=#{dom_id(release, "file_info_#{file_info_id}")}]", text: text) + end + + def have_suggested_match(release, text:) + have_selector("#suggested_matches ##{dom_id(release)}", text: text) + end + + def have_graphics_element_match(text:) + have_selector("#graphics_elements_matches", text: text) + end + + def have_audio_match(text:) + have_selector("#audio_matches", text: text) + end + + def have_unconfirmed_release(release, count: 1) + scope = "#" + release.class.to_s.tableize + have_selector("#{scope} ##{dom_id(release)}[data-confirmed='false'][data-hidden='false']", count: count) + end + + def have_confirmed_release(release, text: "00:00:00:00", scope: "#video_release_confirmations") + have_selector("#{scope} [data-releasable-id='#{release.id}']", text: text) + end + + def have_confirmed_audio(text) + have_selector("#audio_confirmations", text: text) + end + + def have_bookmark(notes:) + have_selector(".standard-marker.bookmark-marker", visible: false) && + have_selector(".standard-tooltip", text: notes, visible: false) + end + + def have_graphics_element(text:) + have_selector(".graphics-elements .graphics-element", text: text) + end + + def have_confirmed_graphics_match(text:) + have_selector("#graphics_elements_matches [data-confirmed='true']", text: text) + end + + def have_graphics_element_modal + have_selector("#new_graphics_element_modal") + end + + def have_edl_event_modal + have_selector("#edl_event_modal") + end + + def have_unreleased_appearance(notes:) + have_selector(".standard-marker.unreleased-appearance-marker", visible: false) && + have_selector(".standard-tooltip", text: notes, visible: false) + end + + def search_for(type, text: "") + action = "video_analyses/#{type.to_s}" + + within "form[action*='#{action}']" do + fill_in "query", with: text + click_button "Search" + end + end + + def dom_id(record, prefix = nil) + ActionController::Base.helpers.dom_id(record, prefix) + end +end diff --git a/spec/features/user_sign_in_spec.rb b/spec/features/user_sign_in_spec.rb new file mode 100644 index 0000000..5742a8c --- /dev/null +++ b/spec/features/user_sign_in_spec.rb @@ -0,0 +1,75 @@ +require "rails_helper" + +feature "User" do + let(:user) { create(:user, :with_name, email: "test@example.com") } + let!(:project) { create(:project, account: user.primary_account, name: "My Video Project") } + + context "when signing in" do + scenario "is redirected on success" do + visit "/" + + fill_in "Email", with: "test@example.com" + fill_in "Password", with: "password" + check "Remember me" + + click_button "Sign In" + + expect(page).to have_content "My Video Project" + end + + scenario "signing out" do + sign_in user + + visit signed_in_root_path + click_on user.full_name + + click_link "Sign Out" + + expect(page).to have_content "Sign In" + end + end + + context "when signed in and accountless" do + before :each do + sign_in(create(:user, :accountless)) + end + + scenario "goes to accountless information page" do + visit signed_in_root_path + expect(page).to have_content /You are signed in but not added to an account/ + end + end + + context "when signed in" do + before :each do + sign_in(user) + end + + scenario "sees 404 page when navigating to unknown url" do + expect { visit "/foo" }.to raise_exception(ActionController::RoutingError) + end + + scenario "gets redirected to project page when navigating to sessions url" do + visit new_session_path + expect(page).to have_content user.primary_account.name + end + + scenario "sees project page when navigating to known url" do + visit project_path(project) + + expect(page).to have_content "My Video Project" + end + end + + context "when not signed in" do + scenario "gets redirected to sign in path when navigating to unknown url" do + expect { visit "/en/foo" }.to raise_exception(ActionController::RoutingError) + end + + scenario "gets redirected to sign in path when navigating to known url" do + visit project_path(project) + + expect(page.current_path).to eq "/en/session/new" + end + end +end diff --git a/spec/features/user_tags_multiple_releases.rb b/spec/features/user_tags_multiple_releases.rb new file mode 100644 index 0000000..56cfa4d --- /dev/null +++ b/spec/features/user_tags_multiple_releases.rb @@ -0,0 +1,75 @@ +require "rails_helper" + +feature "User creates tags" do + let(:current_user) { create(:user) } + let(:project) { create(:project, account: current_user.primary_account) } + + shared_examples "a taggable collection UI", js: true do + specify do + sign_in current_user + visit polymorphic_path [subject.first.project, subject.first.model_name.plural] + + by "adding a tag to multiple releases" do + select_multiple_releases(subject) + submit_tags_modal(subject, tags: "Test tag!") + expect(page).to have_content("Test tag!", count: 5) + end + end + + def select_multiple_releases(release) + all('input[type=checkbox]').each do |checkbox| + if !checkbox.checked? then + checkbox.click + end + end + + click_link "Tag All" + end + + def submit_tags_modal(releases, tags:) + action = url_for([:tag_multiple, releases.first.model_name.plural, only_path: true]) + tags_form = "form[action='#{action}']" + + within tags_form do + fill_in "Name", with: tags + click_button "Add" + end + end + end + + context "for appearance releases" do + subject! { create_list(:appearance_release, 5, project: project, tags: []) } + + it_behaves_like "a taggable collection UI" + end + + context "for talent releases" do + subject! { create_list(:talent_release, 5, project: project, tags: []) } + + it_behaves_like "a taggable collection UI" + end + + context "for location releases" do + subject! { create_list(:location_release, 5, project: project, tags: []) } + + it_behaves_like "a taggable collection UI" + end + + context "for material releases" do + subject! { create_list(:material_release, 5, project: project, tags: []) } + + it_behaves_like "a taggable collection UI" + end + + context "for acquired media releases" do + subject! { create_list(:acquired_media_release, 5, project: project, tags: []) } + + it_behaves_like "a taggable collection UI" + end + + context "for music releases" do + subject! { create_list(:music_release, 5, project: project, tags: []) } + + it_behaves_like "a taggable collection UI" + end +end diff --git a/spec/fixtures/admin/new_video b/spec/fixtures/admin/new_video new file mode 100644 index 0000000..ffcbae3 --- /dev/null +++ b/spec/fixtures/admin/new_video @@ -0,0 +1,3 @@ +Admin#new_video + +Hi, find me in app/views/admin/new_video diff --git a/spec/fixtures/files/AppearanceRelease.pdf b/spec/fixtures/files/AppearanceRelease.pdf new file mode 100644 index 0000000..9896fb4 Binary files /dev/null and b/spec/fixtures/files/AppearanceRelease.pdf differ diff --git a/spec/fixtures/files/audio.mp3 b/spec/fixtures/files/audio.mp3 new file mode 100644 index 0000000..084a7d1 Binary files /dev/null and b/spec/fixtures/files/audio.mp3 differ diff --git a/spec/fixtures/files/bray_edl_100319-mp4_big-cue-sheet.xlsx b/spec/fixtures/files/bray_edl_100319-mp4_big-cue-sheet.xlsx new file mode 100644 index 0000000..80dbe69 Binary files /dev/null and b/spec/fixtures/files/bray_edl_100319-mp4_big-cue-sheet.xlsx differ diff --git a/spec/fixtures/files/bray_edl_100319-mp4_discovery-cue-sheet.xlsx b/spec/fixtures/files/bray_edl_100319-mp4_discovery-cue-sheet.xlsx new file mode 100644 index 0000000..fa0b6ec Binary files /dev/null and b/spec/fixtures/files/bray_edl_100319-mp4_discovery-cue-sheet.xlsx differ diff --git a/spec/fixtures/files/bray_edl_100319-mp4_gfx-cue-list.xlsx b/spec/fixtures/files/bray_edl_100319-mp4_gfx-cue-list.xlsx new file mode 100644 index 0000000..0551edf Binary files /dev/null and b/spec/fixtures/files/bray_edl_100319-mp4_gfx-cue-list.xlsx differ diff --git a/spec/fixtures/files/bray_edl_100319-mp4_issues-and-concerns.xlsx b/spec/fixtures/files/bray_edl_100319-mp4_issues-and-concerns.xlsx new file mode 100644 index 0000000..9a4fbf8 Binary files /dev/null and b/spec/fixtures/files/bray_edl_100319-mp4_issues-and-concerns.xlsx differ diff --git a/spec/fixtures/files/bray_edl_100319-mp4_production-elements-log.xlsx b/spec/fixtures/files/bray_edl_100319-mp4_production-elements-log.xlsx new file mode 100644 index 0000000..2fd34f7 Binary files /dev/null and b/spec/fixtures/files/bray_edl_100319-mp4_production-elements-log.xlsx differ diff --git a/spec/fixtures/files/contract.pdf b/spec/fixtures/files/contract.pdf new file mode 100644 index 0000000..802992c --- /dev/null +++ b/spec/fixtures/files/contract.pdf @@ -0,0 +1 @@ +Hello world diff --git a/spec/fixtures/files/contracts.zip b/spec/fixtures/files/contracts.zip new file mode 100644 index 0000000..0a3e8df Binary files /dev/null and b/spec/fixtures/files/contracts.zip differ diff --git a/spec/fixtures/files/fake_1mb_pdf.pdf b/spec/fixtures/files/fake_1mb_pdf.pdf new file mode 100644 index 0000000..a3d9331 Binary files /dev/null and b/spec/fixtures/files/fake_1mb_pdf.pdf differ diff --git a/spec/fixtures/files/hemsworth.jpeg b/spec/fixtures/files/hemsworth.jpeg new file mode 100644 index 0000000..5dbfc9e Binary files /dev/null and b/spec/fixtures/files/hemsworth.jpeg differ diff --git a/spec/fixtures/files/location_photo.png b/spec/fixtures/files/location_photo.png new file mode 100644 index 0000000..98e3fff Binary files /dev/null and b/spec/fixtures/files/location_photo.png differ diff --git a/spec/fixtures/files/material_photo.png b/spec/fixtures/files/material_photo.png new file mode 100644 index 0000000..a783527 Binary files /dev/null and b/spec/fixtures/files/material_photo.png differ diff --git a/spec/fixtures/files/person_photo.png b/spec/fixtures/files/person_photo.png new file mode 100644 index 0000000..7a47277 Binary files /dev/null and b/spec/fixtures/files/person_photo.png differ diff --git a/spec/fixtures/files/pratt.jpg b/spec/fixtures/files/pratt.jpg new file mode 100644 index 0000000..cc76809 Binary files /dev/null and b/spec/fixtures/files/pratt.jpg differ diff --git a/spec/fixtures/files/sample-adobe-edl.edl b/spec/fixtures/files/sample-adobe-edl.edl new file mode 100644 index 0000000..f37cc15 --- /dev/null +++ b/spec/fixtures/files/sample-adobe-edl.edl @@ -0,0 +1,23 @@ +TITLE: Test Adobe EDL +FCM: NON-DROP FRAME + +001 AX V C 00:00:00:00 00:00:19:04 00:00:00:00 00:00:19:04 +* FROM CLIP NAME: Qualcomm.mp4 + +002 AX A C 00:00:00:00 00:00:19:04 00:00:00:00 00:00:19:04 +* FROM CLIP NAME: MIBE cover two.wav + +003 AX V C 00:00:00:09 00:00:16:12 00:00:19:04 00:00:35:07 +* FROM CLIP NAME: KENTUCKY DERBY 140 Film Open.mp4 + +004 AX A2 C 00:00:00:00 00:00:16:03 00:00:19:04 00:00:35:07 +* FROM CLIP NAME: MIBE first impressions 30.wav + +005 AX V C 01:00:00:00 01:00:15:01 00:00:35:07 00:00:50:08 +* FROM CLIP NAME: American Diner Revival.mov + +006 AX A C 00:00:00:00 00:00:15:01 00:00:35:07 00:00:50:08 +* FROM CLIP NAME: MIBE gonna play to win vox.wav + +007 AX V C 00:00:00:00 00:00:14:09 00:00:50:08 00:01:04:17 +* FROM CLIP NAME: Shut Up And Run.mp4 \ No newline at end of file diff --git a/spec/fixtures/files/sample-edl.edl b/spec/fixtures/files/sample-edl.edl new file mode 100644 index 0000000..5f52257 --- /dev/null +++ b/spec/fixtures/files/sample-edl.edl @@ -0,0 +1,8 @@ +0001 KASS1 A1234V C 00:00:00:00 00:00:12:24 10:00:00:00 10:00:12:24 +* REEL RC100009 IS CLIP 28661.mov +0002 KASS1 A1234V C 00:00:00:00 00:00:13:03 10:00:12:24 10:00:26:02 +* REEL RC100003 IS CLIP 28655.mov +0003 KASS1 A1234V C 00:00:00:00 00:00:15:06 10:00:26:02 10:00:41:08 +* REEL RC100005 IS CLIP 28657.mov +0004 KASS1 A1234V C 00:00:00:00 00:00:16:06 10:00:41:08 10:00:57:14 +* REEL RC100010 IS CLIP 28662.mov diff --git a/spec/fixtures/files/signature.png b/spec/fixtures/files/signature.png new file mode 100644 index 0000000..a9a698f Binary files /dev/null and b/spec/fixtures/files/signature.png differ diff --git a/spec/fixtures/files/video_file.mp4 b/spec/fixtures/files/video_file.mp4 new file mode 100644 index 0000000..ed139d6 Binary files /dev/null and b/spec/fixtures/files/video_file.mp4 differ diff --git a/spec/fixtures/files/video_file_1m.mp4 b/spec/fixtures/files/video_file_1m.mp4 new file mode 100644 index 0000000..a3d9331 Binary files /dev/null and b/spec/fixtures/files/video_file_1m.mp4 differ diff --git a/spec/fixtures/responses/responses.json b/spec/fixtures/responses/responses.json new file mode 100644 index 0000000..054f7b2 --- /dev/null +++ b/spec/fixtures/responses/responses.json @@ -0,0 +1,53 @@ +{ + "Zoom": { + "user_list": { + "status_code": 200, + "body": { + "page_count":1, + "page_number":1, + "page_size":30, + "total_records":1, + "users":[{ + "id":"CS6E_Xu0Q2Kl00nLtEGTLw", + "first_name":"Blazej", + "last_name":"Kotowski", + "email":"blaziko@gmail.com", + "type":1, + "pmi":9287196776, + "timezone":"Europe/Madrid", + "verified":1, + "created_at":"2020-03-25T14:21:01Z", + "last_login_time":"2020-03-31T12:20:02Z", + "language":"en-US", + "phone_number":"", + "status":"active" + }] + }, + "headers": {"content-type": "application/json"} + }, + + "failed_authentication": { + "status_code": 401, + "body": {"code": 124,"message": "Invalid access token."}, + "headers": {"content-type": "application/json"} + }, + + "meetings_404": { + "status_code": 404, + "body": {"code": 1001, "message": "User xxx not exist or not belong to this account."}, + "headers": {"content-type": "application/json"} + }, + + "meeting_create": { + "status_code": 200, + "body": {"uuid":"yOgxLu1US7GA3HBEnC5zDw==","id":776586152,"host_id":"CS6E_Xu0Q2Kl00nLtEGTLw","topic":"Zoom Meeting","type":1,"status":"waiting","timezone":"Europe/Madrid","created_at":"2020-04-03T14:37:19Z","start_url":"https://us04web.zoom.us/s/776586152?zak=eyJ6bV9za20iOiJ6bV9vMm0iLCJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjbGllbnQiLCJ1aWQiOiJDUzZFX1h1MFEyS2wwMG5MdEVHVEx3IiwiaXNzIjoid2ViIiwic3R5IjoxMDAsIndjZCI6InVzMDQiLCJjbHQiOjAsInN0ayI6InJMNU5tRGt2QTAxOWVEdWt6VV81LWcydlB2RTZYdVc2ekItUGpTb3A4ZkEuQmdVZ1VHaDRUMVZUSzA1Uk1scDNTMlZ0ZUZSb1MwRndaRUozUmtwU0wycE9TelpBT0RCbU1qazVZMlF4T1dJM09XUTBORFkyTlRFNVltWmlOR0UwWkdRd01qZ3pNemhtWkdSa1pHRmtNRE5qWmprMk5URXlNREEyT0RZME0yVmlNRFV4TWdBTU0wTkNRWFZ2YVZsVE0zTTlBQVIxY3pBMCIsImV4cCI6MTU4NTkzMTgzOSwiaWF0IjoxNTg1OTI0NjM5LCJhaWQiOiI2SDYzX3RaaVF6S0hJMUhPVlU5WVpnIiwiY2lkIjoiIn0.N5-dfqS7xF4wu15y4TJuGJSB-09yypKWUyZBej5fBL0","join_url":"https://us04web.zoom.us/j/776586152?pwd=ZkJWbmlmR1kyNmNrQkU4N2ozSkpjQT09","password":"529434","h323_password":"529434","pstn_password":"529434","encrypted_password":"ZkJWbmlmR1kyNmNrQkU4N2ozSkpjQT09","settings":{"host_video":false,"participant_video":false,"cn_meeting":false,"in_meeting":false,"join_before_host":false,"mute_upon_entry":false,"watermark":false,"use_pmi":false,"approval_type":2,"audio":"both","auto_recording":"none","enforce_login":false,"enforce_login_domains":"","alternative_hosts":"","close_registration":false,"registrants_confirmation_email":true,"waiting_room":false,"registrants_email_notification":true,"meeting_authentication":false}}, + "headers": {"content-type": "application/json"} + }, + + "meeting_get": { + "status_code": 200, + "body": {"uuid":"yOgxLu1US7GA3HBEnC5zDw==","id":776586152,"host_id":"CS6E_Xu0Q2Kl00nLtEGTLw","topic":"Zoom Meeting","type":1,"status":"waiting","timezone":"Europe/Madrid","agenda":"","created_at":"2020-04-03T14:37:19Z","start_url":"https://us04web.zoom.us/s/776586152?zak=eyJ6bV9za20iOiJ6bV9vMm0iLCJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjbGllbnQiLCJ1aWQiOiJDUzZFX1h1MFEyS2wwMG5MdEVHVEx3IiwiaXNzIjoid2ViIiwic3R5IjoxMDAsIndjZCI6InVzMDQiLCJjbHQiOjAsInN0ayI6InJMNU5tRGt2QTAxOWVEdWt6VV81LWcydlB2RTZYdVc2ekItUGpTb3A4ZkEuQmdVZ1VHaDRUMVZUSzA1Uk1scDNTMlZ0ZUZSb1MwRndaRUozUmtwU0wycE9TelpBT0RCbU1qazVZMlF4T1dJM09XUTBORFkyTlRFNVltWmlOR0UwWkdRd01qZ3pNemhtWkdSa1pHRmtNRE5qWmprMk5URXlNREEyT0RZME0yVmlNRFV4TWdBTU0wTkNRWFZ2YVZsVE0zTTlBQVIxY3pBMCIsImV4cCI6MTU4NTkzMTk4NCwiaWF0IjoxNTg1OTI0Nzg0LCJhaWQiOiI2SDYzX3RaaVF6S0hJMUhPVlU5WVpnIiwiY2lkIjoiIn0.GBHlYgjFe8t65JN4L9zTSlWJIHh1H8Lol4Kgy8oEewM","join_url":"https://us04web.zoom.us/j/776586152?pwd=ZkJWbmlmR1kyNmNrQkU4N2ozSkpjQT09","password":"529434","h323_password":"529434","pstn_password":"529434","encrypted_password":"ZkJWbmlmR1kyNmNrQkU4N2ozSkpjQT09","settings":{"host_video":false,"participant_video":false,"cn_meeting":false,"in_meeting":false,"join_before_host":false,"mute_upon_entry":false,"watermark":false,"use_pmi":false,"approval_type":2,"audio":"both","auto_recording":"none","enforce_login":false,"enforce_login_domains":"","alternative_hosts":"","close_registration":false,"registrants_confirmation_email":true,"waiting_room":false,"registrants_email_notification":true,"meeting_authentication":false}}, + "headers": {"content-type": "application/json"} + } + } +} \ No newline at end of file diff --git a/spec/fixtures/user/password_reset b/spec/fixtures/user/password_reset new file mode 100644 index 0000000..fb7954f --- /dev/null +++ b/spec/fixtures/user/password_reset @@ -0,0 +1,3 @@ +User#password_reset + +Hi, find me in app/views/user/password_reset diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb new file mode 100644 index 0000000..2cf6b29 --- /dev/null +++ b/spec/helpers/application_helper_spec.rb @@ -0,0 +1,4 @@ +require "rails_helper" + +RSpec.describe ApplicationHelper, type: :helper do +end diff --git a/spec/helpers/buttons_helper_spec.rb b/spec/helpers/buttons_helper_spec.rb new file mode 100644 index 0000000..dcf460d --- /dev/null +++ b/spec/helpers/buttons_helper_spec.rb @@ -0,0 +1,171 @@ +require "rails_helper" + +RSpec.describe ButtonsHelper do + describe ".link_to_edl_file_download" do + before :each do + helper.instance_variable_set(:@virtual_path, "video_analyses.show") + end + + context "when video has edl_file" do + let(:edl_file) { double(:edl_file, signed_id: "signed_id", filename: "filename", attached?: true) } + let(:video) { instance_double(Video, edl_file: edl_file) } + + it "returns HTML anchor tag with link to EDL file download" do + expect(helper.link_to_edl_file_download(video)).to eq <<-HTML.gsub(/\n/, '').gsub(/\s+ + Download EDL + HTML + end + end + + context "when video does not have edl_file" do + let(:edl_file) { double(:edl_file, attached?: false) } + let(:video) { instance_double(Video, edl_file: edl_file) } + + it "returns disabled HTML anchor tag" do + expect(helper.link_to_edl_file_download(video)).to eq <<-HTML.gsub(/\n/, '').gsub(/\s+ + Download EDL + HTML + end + end + end + + describe ".link_to_graphics_only_edl_file_download" do + before :each do + helper.instance_variable_set(:@virtual_path, "video_analyses.show") + end + + context "when video has graphics_only_edl_file" do + let(:graphics_only_edl_file) { double(:graphics_only_edl_file, signed_id: "signed_id", filename: "filename", attached?: true) } + let(:video) { instance_double(Video, graphics_only_edl_file: graphics_only_edl_file) } + + it "returns HTML anchor tag with link to EDL file download" do + expect(helper.link_to_graphics_only_edl_file_download(video)).to eq <<-HTML.gsub(/\n/, '').gsub(/\s+ + Download Graphics Only EDL + HTML + end + end + + context "when video does not have graphics_only_edl_file" do + let(:graphics_only_edl_file) { double(:graphics_only_edl_file, attached?: false) } + let(:video) { instance_double(Video, graphics_only_edl_file: graphics_only_edl_file) } + + it "returns disabled HTML anchor tag" do + expect(helper.link_to_graphics_only_edl_file_download(video)).to eq <<-HTML.gsub(/\n/, '').gsub(/\s+ + Download Graphics Only EDL + HTML + end + end + end + + describe ".link_to_audio_only_edl_file_download" do + before :each do + helper.instance_variable_set(:@virtual_path, "video_analyses.show") + end + + context "when video has audio_only_edl_file" do + let(:audio_only_edl_file) { double(:audio_only_edl_file, signed_id: "signed_id", filename: "filename", attached?: true) } + let(:video) { instance_double(Video, audio_only_edl_file: audio_only_edl_file) } + + it "returns HTML anchor tag with link to EDL file download" do + expect(helper.link_to_audio_only_edl_file_download(video)).to eq <<-HTML.gsub(/\n/, '').gsub(/\s+ + Download Audio Only EDL + HTML + end + end + + context "when video does not have audio_only_edl_file" do + let(:audio_only_edl_file) { double(:audio_only_edl_file, attached?: false) } + let(:video) { instance_double(Video, audio_only_edl_file: audio_only_edl_file) } + + it "returns disabled HTML anchor tag" do + expect(helper.link_to_audio_only_edl_file_download(video)).to eq <<-HTML.gsub(/\n/, '').gsub(/\s+ + Download Audio Only EDL + HTML + end + end + end + + describe "#button_to_release_report" do + context "when reports have not been published" do + let(:video) { build(:video) } + + before :each do + helper.instance_variable_set(:@virtual_path, "videos.video") + end + + it "returns html for disabled release report button" do + html = helper.button_to_release_report(video, content: "button content", disabled: true) + + expect(html).to match "Generating..." + end + end + + context "for a Discovery client" do + let(:video) { build(:video, id: 45, project: build(:project, :discovery_client)) } + + it "returns html for Discovery reports" do + html = helper.button_to_release_report(video, content: "button content") + + expect(html).to match "Download Discovery Documents" + expect(html).to match "Production Elements Log" + expect(html).to match "en/videos/45/video_reports.xlsx\\?type=discovery" + expect(html).to match "GFX Cue List" + expect(html).to match "en/videos/45/graphic_reports.xlsx\\?type=discovery" + expect(html).to match "BiG Music Cue Sheet" + expect(html).to match "en/videos/45/audio_reports.xlsx\\?type=big" + expect(html).to match "Music Cue Sheet" + expect(html).to match "en/videos/45/audio_reports.xlsx\\?type=discovery" + expect(html).to match "Issues and Concerns Report" + expect(html).to match "en/videos/45/issues_and_concerns_reports.xlsx" + end + end + + context "for a Nat Geo client" do + let(:video) { build(:video, id: 45, project: build(:project, :nat_geo_client)) } + + it "returns html for Net Geo reports" do + html = helper.button_to_release_report(video, content: "button content") + + expect(html).to match "Download Nat Geo Documents" + expect(html).to match "Legal Binder Log" + expect(html).to match "en/videos/45/video_reports.xlsx\\?type=nat_geo" + expect(html).to match "Text Graphics Log" + expect(html).to match "en/videos/45/graphic_reports.xlsx\\?type=nat_geo" + expect(html).to match "BiG Music Cue Sheet" + expect(html).to match "en/videos/45/audio_reports.xlsx\\?type=big" + expect(html).to match "Music Cue Sheet" + expect(html).to match "en/videos/45/audio_reports.xlsx\\?type=nat_geo" + expect(html).to match "Original Music Log" + expect(html).to match "en/videos/45/audio_reports.xlsx\\?type=nat_geo-original" + expect(html).to match "Issues and Concerns Report" + expect(html).to match "en/videos/45/issues_and_concerns_reports.xlsx" + end + end + + context "for any other client" do + let(:video) { build(:video, id: 45, project: build(:project, client_name: "Other")) } + + it "returns html for Discovery reports" do + html = helper.button_to_release_report(video, content: "button content") + + expect(html).to match "Download Discovery Documents" + expect(html).to match "Production Elements Log" + expect(html).to match "en/videos/45/video_reports.xlsx\\?type=discovery" + expect(html).to match "GFX Cue List" + expect(html).to match "en/videos/45/graphic_reports.xlsx\\?type=discovery" + expect(html).to match "BiG Music Cue Sheet" + expect(html).to match "en/videos/45/audio_reports.xlsx\\?type=big" + expect(html).to match "Music Cue Sheet" + expect(html).to match "en/videos/45/audio_reports.xlsx\\?type=discovery" + expect(html).to match "Issues and Concerns Report" + expect(html).to match "en/videos/45/issues_and_concerns_reports.xlsx" + end + end + end +end diff --git a/spec/helpers/cards_helper_spec.rb b/spec/helpers/cards_helper_spec.rb new file mode 100644 index 0000000..67cbd61 --- /dev/null +++ b/spec/helpers/cards_helper_spec.rb @@ -0,0 +1,23 @@ +require "rails_helper" + +RSpec.describe CardsHelper, type: :helper do + describe "#card_header" do + it "includes a heading element" do + header = helper.card_header(text: "Foo") + + expect(header).to have_css "h1", text: "Foo" + end + + it "includes subtext when present" do + header = helper.card_header(subtext: "Bar") + + expect(header).to have_css "small", text: "Bar" + end + + it "includes close button when present" do + header = helper.card_header(close_action_path: "/foo") + + expect(header).to have_css "a[href='/foo']", text: "Close" + end + end +end diff --git a/spec/helpers/dropzone_helper_spec.rb b/spec/helpers/dropzone_helper_spec.rb new file mode 100644 index 0000000..19b9d8a --- /dev/null +++ b/spec/helpers/dropzone_helper_spec.rb @@ -0,0 +1,35 @@ +require "rails_helper" + +RSpec.describe DropzoneHelper, type: :helper do + describe "#dropzone_accepted_files_for" do + it "accepts image files for appearance releases" do + releasable = build(:appearance_release) + expect(helper.dropzone_accepted_files_for(releasable)).to eq("image/*") + end + + it "accepts image files for talent releases" do + releasable = build(:talent_release) + expect(helper.dropzone_accepted_files_for(releasable)).to eq("image/*") + end + + it "accepts image files for location releases" do + releasable = build(:location_release) + expect(helper.dropzone_accepted_files_for(releasable)).to eq("image/*") + end + + it "accepts image files for material releases" do + releasable = build(:material_release) + expect(helper.dropzone_accepted_files_for(releasable)).to eq("image/*") + end + + it "accepts audio files for music releases" do + releasable = build(:music_release) + expect(helper.dropzone_accepted_files_for(releasable)).to eq("audio/*") + end + + it "accepts any file for acquired media releases" do + releasable = build(:acquired_media_release) + expect(helper.dropzone_accepted_files_for(releasable)).to be_nil + end + end +end diff --git a/spec/helpers/duration_helper_spec.rb b/spec/helpers/duration_helper_spec.rb new file mode 100644 index 0000000..ba5980a --- /dev/null +++ b/spec/helpers/duration_helper_spec.rb @@ -0,0 +1,47 @@ +require 'rails_helper' + +describe DurationHelper do + describe '#convert_duration' do + it 'returns the same value if the from and to are the same unit' do + result = helper.convert_duration(60, from: :seconds, to: :seconds) + + expect(result).to eq(60) + end + + it 'converts seconds into minutes' do + result = helper.convert_duration(60, from: :seconds, to: :minutes) + + expect(result).to eq(1) + end + + it 'converts seconds into hours' do + result = helper.convert_duration(3600, from: :seconds, to: :hours) + + expect(result).to eq(1) + end + + it 'converts minutes into seconds' do + result = helper.convert_duration(1, from: :minutes, to: :seconds) + + expect(result).to eq(60) + end + + it 'converts minutes into hours' do + result = helper.convert_duration(60, from: :minutes, to: :hours) + + expect(result).to eq(1) + end + + it 'converts hours into seconds' do + result = helper.convert_duration(1, from: :hours, to: :seconds) + + expect(result).to eq(3600) + end + + it 'converts hours into minutes' do + result = helper.convert_duration(1, from: :hours, to: :minutes) + + expect(result).to eq(60) + end + end +end diff --git a/spec/helpers/images_helper_spec.rb b/spec/helpers/images_helper_spec.rb new file mode 100644 index 0000000..daa9323 --- /dev/null +++ b/spec/helpers/images_helper_spec.rb @@ -0,0 +1,51 @@ +require "rails_helper" + +RSpec.describe ImagesHelper do + describe "#thumbnail_variant" do + it "calls variant with orient and size options" do + attachment = double(:attachment) + allow(attachment).to receive(:variant) + + helper.thumbnail_variant(attachment) + + expect(attachment).to have_received(:variant).with( + gravity: "center", + resize: "75x75>", + extent: "75x75", + background: "#fff", + ) + end + end + + describe "#medium_variant" do + it "calls variant with orient and size options" do + attachment = double(:attachment) + allow(attachment).to receive(:variant) + + helper.medium_variant(attachment) + + expect(attachment).to have_received(:variant).with( + gravity: "center", + resize: "100x100>", + extent: "100x100", + background: "#fff", + ) + end + end + + describe "#large_variant" do + it "calls variant with orient and size options" do + attachment = double(:attachment) + allow(attachment).to receive(:variant) + + helper.large_variant(attachment) + + expect(attachment).to have_received(:variant).with( + gravity: "center", + resize: "150x150>", + extent: "150x150", + background: "#fff", + ) + end + end +end diff --git a/spec/helpers/mail_helper_spec.rb b/spec/helpers/mail_helper_spec.rb new file mode 100644 index 0000000..6ac54d1 --- /dev/null +++ b/spec/helpers/mail_helper_spec.rb @@ -0,0 +1,16 @@ +require "rails_helper" + +RSpec.describe MailHelper, type: :helper do + describe "#mail_to_for_multiple_edls" do + it "generates a mailto link with the project info" do + project = build(:project, name: "Test Project", account: build(:account, name: "Test Account")) + + link = CGI.unescape helper.mail_to_for_multiple_edls("test content", project) + + expect(link).to match "test content" + expect(link).to match "info@bigmedia.ai" + expect(link).to match /Multiple Adobe Premiere EDLs needed for Test Account's Test Project project/ + expect(link).to match "INSTRUCTIONS" + end + end +end diff --git a/spec/helpers/note_categories_helper_spec.rb b/spec/helpers/note_categories_helper_spec.rb new file mode 100644 index 0000000..fb6cb2a --- /dev/null +++ b/spec/helpers/note_categories_helper_spec.rb @@ -0,0 +1,21 @@ +require "rails_helper" + +RSpec.describe NoteCategoriesHelper do + describe ".options_for_note_category_select" do + it "returns human readable options for note categories" do + + unreleased_appearance = instance_double("UnreleasedAppearance", note_category: "missing_location_release") + + results = helper.options_for_note_category_select(unreleased_appearance) + + expect(results).to match /