eivid 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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