eivid 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +215 -0
  4. data/Rakefile +18 -0
  5. data/app/controllers/eivid/application_controller.rb +20 -0
  6. data/app/controllers/eivid/concerns/video_validations.rb +42 -0
  7. data/app/controllers/eivid/videos_controller.rb +48 -0
  8. data/app/dump/eivid/video_mime_dump.rb +9 -0
  9. data/app/errors/eivid/incorrect_video_mime_type_error.rb +4 -0
  10. data/app/errors/eivid/main_app_record_not_found_error.rb +4 -0
  11. data/app/errors/eivid/maximum_vimeo_poll_reached_error.rb +4 -0
  12. data/app/errors/eivid/video_file_not_present_error.rb +4 -0
  13. data/app/errors/eivid/video_file_size_too_big_error.rb +4 -0
  14. data/app/errors/eivid/video_unavailable_error.rb +4 -0
  15. data/app/errors/eivid/video_urls_unavailable_error.rb +4 -0
  16. data/app/jobs/eivid/application_job.rb +4 -0
  17. data/app/jobs/eivid/check_vimeo_status_job.rb +46 -0
  18. data/app/jobs/eivid/get_vimeo_urls_job.rb +62 -0
  19. data/app/jobs/eivid/upload_vimeo_job.rb +55 -0
  20. data/app/mailers/eivid/application_mailer.rb +6 -0
  21. data/app/models/eivid/application_record.rb +5 -0
  22. data/app/models/eivid/concerns/main_app/owner.rb +17 -0
  23. data/app/models/eivid/concerns/main_app/user.rb +11 -0
  24. data/app/models/eivid/concerns/main_app/video_concerns.rb +16 -0
  25. data/app/models/eivid/concerns/main_app/video_resource.rb +11 -0
  26. data/app/models/eivid/concerns/request_service/delete_video.rb +17 -0
  27. data/app/models/eivid/concerns/request_service/get_video.rb +9 -0
  28. data/app/models/eivid/concerns/request_service/manage_folder.rb +29 -0
  29. data/app/models/eivid/concerns/request_service/poll_status.rb +9 -0
  30. data/app/models/eivid/concerns/request_service/testing_methods.rb +38 -0
  31. data/app/models/eivid/concerns/request_service/upload_video.rb +31 -0
  32. data/app/models/eivid/owner.rb +21 -0
  33. data/app/models/eivid/video.rb +21 -0
  34. data/app/models/eivid/video_resource.rb +8 -0
  35. data/app/services/eivid/notify_front_service.rb +14 -0
  36. data/app/services/eivid/request_service.rb +42 -0
  37. data/app/services/eivid/upload_service.rb +30 -0
  38. data/config/initializers/macro_initializer.rb +22 -0
  39. data/config/routes.rb +8 -0
  40. data/db/migrate/20210325114212_create_eivid_videos.rb +11 -0
  41. data/db/migrate/20210325140804_add_uploaded_bool_to_videos.rb +5 -0
  42. data/db/migrate/20210330092643_create_eivid_owners.rb +9 -0
  43. data/db/migrate/20210330094604_add_external_id_to_owner.rb +5 -0
  44. data/db/migrate/20210330100147_add_folder_id_to_owner.rb +5 -0
  45. data/db/migrate/20210330103729_add_vimeo_id_to_videos.rb +5 -0
  46. data/db/migrate/20210331082454_add_status_poll_cnt_to_videos.rb +5 -0
  47. data/db/migrate/20210401091448_create_eivid_video_resources.rb +12 -0
  48. data/db/migrate/20210401123512_remove_status_poll_cnt_from_eivid_videos.rb +5 -0
  49. data/db/migrate/20210401132759_create_eivid_dev_envs.rb +11 -0
  50. data/db/migrate/20210401134700_drop.rb +5 -0
  51. data/db/migrate/20210419122001_change_url_to_embedded_url.rb +5 -0
  52. data/db/migrate/20210419122752_add_video_urls_to_videos.rb +7 -0
  53. data/db/migrate/20210420120017_add_user_id_to_eivid_videos.rb +5 -0
  54. data/lib/eivid.rb +6 -0
  55. data/lib/eivid/engine.rb +33 -0
  56. data/lib/eivid/version.rb +3 -0
  57. data/lib/tasks/eivid_tasks.rake +4 -0
  58. metadata +144 -0
@@ -0,0 +1,62 @@
1
+ module Eivid
2
+ class GetVimeoUrlsJob < ApplicationJob
3
+
4
+ retry_on VideoUrlsUnavailableError, wait: 10.seconds, attempts: 50
5
+
6
+ def perform(video_record:, vimeo_id:)
7
+ @video_record = video_record
8
+ @vimeo_id = vimeo_id
9
+
10
+ set_response
11
+ set_url_sd
12
+ set_url_hd
13
+ set_url_thumbnail
14
+
15
+ validate_url_presence
16
+ validate_url_thumbnail
17
+
18
+ update_record
19
+ notify_front
20
+ end
21
+
22
+ private
23
+
24
+ def set_response
25
+ @response = Eivid::RequestService.get_video @vimeo_id
26
+ end
27
+
28
+ def set_url_sd
29
+ @url_sd = dig_video_url :sd
30
+ end
31
+
32
+ def set_url_hd
33
+ @url_hd = dig_video_url :hd
34
+ end
35
+
36
+ def dig_video_url(definition)
37
+ @response[:files]&.find { |video| video[:quality] == definition.to_s }&.dig(:link)
38
+ end
39
+
40
+ def set_url_thumbnail
41
+ @url_thumbnail = @response&.dig(:pictures, :sizes)&.find { |url| url[:width] == 640 && url[:height] == 360 }&.dig(:link_with_play_button)
42
+ end
43
+
44
+ def validate_url_presence
45
+ raise Eivid::VideoUrlsUnavailableError unless (@url_sd && @url_hd && @url_thumbnail)
46
+ end
47
+
48
+ def validate_url_thumbnail
49
+ raise Eivid::VideoUrlsUnavailableError if @url_thumbnail.include? 'video%2Fdefault'
50
+ end
51
+
52
+ def update_record
53
+ @video_record.update(uploaded: true, url_sd: @url_sd, url_hd: @url_hd, url_thumbnail: @url_thumbnail)
54
+ end
55
+
56
+ def notify_front
57
+ data = { video: @video_record.slice(:id, :user_id), progress: { percentage: 100, step: "All video versions are available on Vimeo." } }
58
+ NotifyFrontService.progress('notify_method_on_versions_available', data)
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,55 @@
1
+ module Eivid
2
+ class UploadVimeoJob < ApplicationJob
3
+
4
+ def perform(video_record:, video_path:)
5
+ @video_record = video_record
6
+ @video_path = video_path
7
+ @video_file = File.open(video_path).read
8
+
9
+ upload_to_vimeo
10
+ set_attributes
11
+ update_record
12
+ add_to_folder
13
+
14
+ notify_front
15
+ check_status
16
+ end
17
+
18
+ private
19
+
20
+ def upload_to_vimeo
21
+ @response = Eivid::RequestService.upload_video(video_path: @video_path)
22
+ end
23
+
24
+ def set_attributes
25
+ set_vimeo_url
26
+ set_vimeo_id
27
+ end
28
+
29
+ def set_vimeo_url
30
+ @vimeo_url = @response.dig(:link)
31
+ end
32
+
33
+ def set_vimeo_id
34
+ @vimeo_id = @vimeo_url.split('/').last
35
+ end
36
+
37
+ def update_record
38
+ @video_record.update(url_embedded: @vimeo_url, vimeo_id: @vimeo_id)
39
+ end
40
+
41
+ def add_to_folder
42
+ Eivid::RequestService.add_video_to_folder(video_record: @video_record)
43
+ end
44
+
45
+ def check_status
46
+ CheckVimeoStatusJob.perform_later(video_record: @video_record)
47
+ end
48
+
49
+ def notify_front
50
+ data = { video: @video_record.slice(:id, :user_id), progress: { percentage: 33, step: "The video has been uploaded to Vimeo." } }
51
+ NotifyFrontService.progress('notify_method_on_upload', data)
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,6 @@
1
+ module Eivid
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Eivid
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,17 @@
1
+ module Eivid::Concerns::MainApp::Owner
2
+ extend ActiveSupport::Concern
3
+ included do
4
+
5
+ has_one :video_owner, class_name: 'Eivid::Owner', foreign_key: 'external_id'
6
+ has_many :videos, class_name: 'Eivid::Video', source: :video_owner, foreign_key: 'owner_id'
7
+
8
+ include Eivid::Concerns::MainApp::VideoConcerns
9
+
10
+ after_create :create_eivid_owner
11
+
12
+ def create_eivid_owner
13
+ Eivid::Owner.create(external_id: self.id)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ module Eivid::Concerns::MainApp::User
2
+ extend ActiveSupport::Concern
3
+ included do
4
+
5
+ has_many :videos, class_name: 'Eivid::Video'
6
+ has_many :video_resources, class_name: 'Eivid::VideoResource', through: :videos
7
+
8
+ include Eivid::Concerns::MainApp::VideoConcerns
9
+
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ module Eivid::Concerns::MainApp::VideoConcerns
2
+ extend ActiveSupport::Concern
3
+ included do
4
+
5
+ scope :has_video, -> { joins(:videos).distinct }
6
+ scope :has_not_video, -> { joins(:videos).where('eivid_videos.id' => nil) }
7
+
8
+ scope :has_pending_video, -> { joins(:videos).where('eivid_videos.uploaded' => false) }
9
+ scope :has_not_pending_video, -> { where(id: [ids - has_pending_video.ids]) }
10
+
11
+ # caution: due to the has_many with .distinct, the condition only has to be satisfied once
12
+ scope :has_processed_video, -> { joins(:videos).where('eivid_videos.uploaded': true).distinct }
13
+ scope :has_unprocessed_video, -> { joins(:videos).where('eivid_videos.uploaded' => false).distinct }
14
+
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ module Eivid::Concerns::MainApp::VideoResource
2
+ extend ActiveSupport::Concern
3
+ included do
4
+
5
+ has_many :video_resources, class_name: 'Eivid::VideoResource', as: :resource
6
+ has_many :videos, class_name: 'Eivid::Video', through: :video_resources, source: :video
7
+
8
+ include Eivid::Concerns::MainApp::VideoConcerns
9
+
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ module Eivid::Concerns::RequestService::DeleteVideo
2
+ extend ActiveSupport::Concern
3
+
4
+ def destroy(video:)
5
+ @video = video
6
+ delete_from_vimeo
7
+ video.destroy
8
+ end
9
+
10
+ private
11
+
12
+ def delete_from_vimeo
13
+ return unless @video.uploaded
14
+ HTTParty.delete "#{Eivid::RequestService::PLAIN_VIDEO_URL}/#{@video.vimeo_id}", headers: default_headers
15
+ end
16
+
17
+ end
@@ -0,0 +1,9 @@
1
+ module Eivid::Concerns::RequestService::GetVideo
2
+ extend ActiveSupport::Concern
3
+
4
+ def get_video(vimeo_id)
5
+ response = HTTParty.get "#{Eivid::RequestService::PLAIN_VIDEO_URL}/#{vimeo_id}", headers: default_headers
6
+ JSON.parse(response.body).deep_symbolize_keys
7
+ end
8
+
9
+ end
@@ -0,0 +1,29 @@
1
+ module Eivid::Concerns::RequestService::ManageFolder
2
+ extend ActiveSupport::Concern
3
+
4
+ def get_all_folders
5
+ HTTParty.get Eivid::RequestService::FOLDER_URL, headers: default_headers
6
+ end
7
+
8
+ def create_folder(namespace:, id:)
9
+ body = { "name" => "#{Rails.env}_#{namespace}_#{id}" }
10
+
11
+ @response = HTTParty.post Eivid::RequestService::FOLDER_URL, body: body.to_json, headers: default_headers
12
+ get_folder_id
13
+ end
14
+
15
+ def add_video_to_folder(video_record:)
16
+ video_id = video_record.vimeo_id
17
+ folder_id = video_record.owner.folder_id
18
+ endpoint = Eivid::RequestService::ADD_TO_FOLDER_URL.call(folder_id, video_id)
19
+
20
+ HTTParty.put endpoint, headers: default_headers
21
+ end
22
+
23
+ private
24
+
25
+ def get_folder_id
26
+ @response["uri"].split('/').last
27
+ end
28
+
29
+ end
@@ -0,0 +1,9 @@
1
+ module Eivid::Concerns::RequestService::PollStatus
2
+ extend ActiveSupport::Concern
3
+
4
+ def get_status(vimeo_id:)
5
+ response = HTTParty.get "#{Eivid::RequestService::BASE_URL}/videos/#{vimeo_id}?fields=uri,status", headers: default_headers
6
+ JSON.parse(response)
7
+ end
8
+
9
+ end
@@ -0,0 +1,38 @@
1
+ module Eivid::Concerns::RequestService::TestingMethods
2
+ extend ActiveSupport::Concern
3
+
4
+ def delete_all_videos
5
+ puts "are you sure you want to delete ALL videos from Vimeo? Confirm by typing: 'yup brah'"
6
+ confirmation = gets
7
+ return "operation cancelled" unless confirmation.chomp == "yup brah"
8
+ delete_all_videos_from_vimeo
9
+ end
10
+
11
+ private
12
+
13
+ def get_all_video_ids
14
+ get_all_pages.map { |page| get_single_page_video_ids(page) }.flatten
15
+ end
16
+
17
+ def delete_all_videos_from_vimeo
18
+ get_all_video_ids.map { |id| delete_single_video_from_vimeo(id) }
19
+ end
20
+
21
+ def delete_single_video_from_vimeo(vimeo_id)
22
+ HTTParty.delete "#{Eivid::RequestService::PLAIN_VIDEO_URL}/#{vimeo_id}", headers: default_headers
23
+ puts "deleted #{vimeo_id}"
24
+ end
25
+
26
+ def get_single_page_video_ids(url)
27
+ response = HTTParty.get url, headers: default_headers
28
+ JSON.parse(response)["data"].map { |record| record["link"].split('/').last }
29
+ end
30
+
31
+ def get_all_pages
32
+ response = HTTParty.get Eivid::RequestService::VIDEOS_URL, headers: default_headers
33
+ page_count = (JSON.parse(response)["total"] / 25.to_f).ceil
34
+
35
+ (1..page_count).map { |cnt| "#{Eivid::RequestService::VIDEOS_URL}?page=#{cnt}" }
36
+ end
37
+
38
+ end
@@ -0,0 +1,31 @@
1
+ module Eivid::Concerns::RequestService::UploadVideo
2
+ extend ActiveSupport::Concern
3
+
4
+ def upload_video(video_path:)
5
+ @file = File.open(video_path)
6
+ @request = HTTParty.post Eivid::RequestService::VIDEOS_URL, **upload_params
7
+ @response = JSON.parse(@request).deep_symbolize_keys
8
+ @upload_link = @response.dig(:upload, :upload_link)
9
+
10
+ tus_upload_to_vimeo
11
+ @response
12
+ end
13
+
14
+ private
15
+
16
+ def tus_upload_to_vimeo
17
+ HTTParty.patch @upload_link, body: @file.read, headers: tus_headers
18
+ end
19
+
20
+ def upload_params
21
+ body = {
22
+ "upload" => {
23
+ "approach" => "tus",
24
+ "size" => @file.size.to_s,
25
+ }
26
+ }
27
+
28
+ { body: body.to_json, headers: default_headers }
29
+ end
30
+
31
+ end
@@ -0,0 +1,21 @@
1
+ module Eivid
2
+ class Owner < ApplicationRecord
3
+
4
+ has_many :videos
5
+
6
+ validates :external_id, presence: true #, uniqueness: true # comment out for dev only
7
+
8
+ before_create :get_folder_id
9
+
10
+ belongs_to :"#{Eivid.owner_model}", foreign_key: "external_id"
11
+
12
+ def external_owner
13
+ send Eivid.owner_model
14
+ end
15
+
16
+ def get_folder_id
17
+ self.folder_id = RequestService.create_folder(namespace: Eivid.owner_model, id: self.external_id)
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Eivid
2
+ class Video < ApplicationRecord
3
+
4
+ belongs_to :owner
5
+ belongs_to :user, optional: true
6
+ has_many :video_resources
7
+
8
+ def owner_id # required for aliassing :owner_id
9
+ super
10
+ end
11
+
12
+ def external_owner
13
+ owner.send Eivid.owner_model
14
+ end
15
+
16
+ alias_method :"#{Eivid.owner_model}_id", :owner_id
17
+ alias_method :"#{Eivid.owner_model}", :external_owner
18
+ scope :"of_#{Eivid.owner_model}", -> (owner_id) { where(owner_id: owner_id) }
19
+
20
+ end
21
+ end
@@ -0,0 +1,8 @@
1
+ module Eivid
2
+ class VideoResource < ApplicationRecord
3
+
4
+ belongs_to :video
5
+ belongs_to :resource, polymorphic: true
6
+
7
+ end
8
+ end
@@ -0,0 +1,14 @@
1
+ module Eivid::NotifyFrontService
2
+ class << self
3
+
4
+ def progress(progress_method, progress_hash)
5
+ return unless Eivid.notify_front_enabled
6
+
7
+ main_app_proc = Eivid.send progress_method
8
+ main_app_meth = main_app_proc.call progress_hash.deep_symbolize_keys
9
+
10
+ eval main_app_meth
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,42 @@
1
+ # vimeo docs: https://developer.vimeo.com/api/guides/start
2
+ # wrapper docs: https://github.com/bo-oz/vimeo_me2
3
+
4
+ module Eivid::RequestService
5
+
6
+ VIMEO_ID = "136419353"
7
+ BASE_URL = "https://api.vimeo.com"
8
+ VIDEOS_URL = "#{BASE_URL}/me/videos"
9
+ FOLDER_URL = "#{BASE_URL}/me/projects"
10
+ PLAIN_VIDEO_URL = "#{BASE_URL}/videos"
11
+ ADD_TO_FOLDER_URL = -> (folder_id, video_id) { "#{FOLDER_URL}/#{folder_id}/videos/#{video_id}" }
12
+
13
+ class << self
14
+
15
+ include Eivid::Concerns::RequestService::ManageFolder
16
+ include Eivid::Concerns::RequestService::PollStatus
17
+ include Eivid::Concerns::RequestService::GetVideo
18
+ include Eivid::Concerns::RequestService::UploadVideo
19
+ include Eivid::Concerns::RequestService::DeleteVideo
20
+ include Eivid::Concerns::RequestService::TestingMethods
21
+
22
+ private
23
+
24
+ def default_headers
25
+ {
26
+ "Authorization" => "bearer #{Figaro.env.VIMEO_ACCESS_TOKEN}",
27
+ "Content-Type" => "application/json",
28
+ "Accept" => "application/vnd.vimeo.*+json;version=3.4"
29
+ }
30
+ end
31
+
32
+ def tus_headers
33
+ {
34
+ "Tus-Resumable" => "1.0.0",
35
+ "Upload-Offset" => "0",
36
+ "Content-Type" => "application/offset+octet-stream",
37
+ "Accept" => "application/vnd.vimeo.*+json;version=3.4"
38
+ }
39
+ end
40
+
41
+ end
42
+ end