cm-admin 0.7.7 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +6 -5
  3. data/Gemfile.lock +49 -9
  4. data/app/assets/images/image_not_available.png +0 -0
  5. data/app/assets/stylesheets/cm_admin/base/table.scss +1 -1
  6. data/app/assets/stylesheets/cm_admin/cm_admin.css.scss +1 -0
  7. data/app/assets/stylesheets/cm_admin/pages/import_page.scss +125 -0
  8. data/app/controllers/cm_admin/resource_controller.rb +26 -4
  9. data/app/helpers/cm_admin/application_helper.rb +41 -0
  10. data/app/javascript/packs/cm_admin/exports.js +0 -1
  11. data/app/jobs/file_import_processor_job.rb +38 -0
  12. data/app/models/concerns/cm_admin/file_import.rb +40 -0
  13. data/app/models/file_import.rb +27 -0
  14. data/app/policies/cm_admin/file_import_policy.rb +11 -0
  15. data/app/views/cm_admin/main/_actions_dropdown.html.slim +3 -15
  16. data/app/views/cm_admin/main/_associated_table.html.slim +5 -0
  17. data/app/views/cm_admin/main/_member_custom_action_modal.html.slim +13 -0
  18. data/app/views/cm_admin/main/_table.html.slim +3 -1
  19. data/app/views/cm_admin/main/_tabs.html.slim +1 -1
  20. data/app/views/cm_admin/main/_top_navbar.html.slim +2 -0
  21. data/app/views/cm_admin/main/import_form.html.slim +35 -0
  22. data/cm_admin.gemspec +2 -1
  23. data/config/routes.rb +6 -0
  24. data/lib/cm_admin/model.rb +7 -1
  25. data/lib/cm_admin/models/column.rb +3 -1
  26. data/lib/cm_admin/models/field.rb +4 -2
  27. data/lib/cm_admin/models/form_field.rb +3 -2
  28. data/lib/cm_admin/models/importer.rb +17 -0
  29. data/lib/cm_admin/version.rb +1 -1
  30. data/lib/cm_admin/view_helpers/field_display_helper.rb +8 -0
  31. data/lib/cm_admin/view_helpers/form_field_helper.rb +8 -6
  32. data/lib/cm_admin/view_helpers.rb +18 -18
  33. data/lib/cm_admin.rb +2 -1
  34. data/lib/generators/cm_admin/add_graphql_generator.rb +25 -0
  35. data/lib/generators/cm_admin/install_generator.rb +2 -0
  36. data/lib/generators/cm_admin/templates/cm_admin_initializer.rb +1 -1
  37. data/lib/generators/cm_admin/templates/concerns/attachable.rb +63 -0
  38. data/lib/generators/cm_admin/templates/concerns/filtered_list.rb +22 -0
  39. data/lib/generators/cm_admin/templates/concerns/paginator.rb +12 -0
  40. data/lib/generators/cm_admin/templates/constants.rb +3 -0
  41. data/lib/generators/cm_admin/templates/exceptions/base_exception.rb +9 -0
  42. data/lib/generators/cm_admin/templates/graphql/enums/base/sort_column.rb +7 -0
  43. data/lib/generators/cm_admin/templates/graphql/enums/base/sort_direction.rb +8 -0
  44. data/lib/generators/cm_admin/templates/graphql/graphql_schema.rb +55 -0
  45. data/lib/generators/cm_admin/templates/graphql/inputs/base/attachment.rb +15 -0
  46. data/lib/generators/cm_admin/templates/graphql/inputs/base/filter.rb +15 -0
  47. data/lib/generators/cm_admin/templates/graphql/inputs/base/paging.rb +15 -0
  48. data/lib/generators/cm_admin/templates/graphql/inputs/base/sort.rb +15 -0
  49. data/lib/generators/cm_admin/templates/graphql/mutations/base_mutation.rb +8 -0
  50. data/lib/generators/cm_admin/templates/graphql/objects/base/attachment_type.rb +31 -0
  51. data/lib/generators/cm_admin/templates/graphql/objects/base/paging_type.rb +9 -0
  52. data/lib/generators/cm_admin/templates/graphql/queries/base_query.rb +4 -0
  53. data/package-lock.json +131 -35
  54. data/tmp/cache/webpacker/last-compilation-digest-development +1 -1
  55. data/yarn.lock +5145 -6202
  56. metadata +47 -7
@@ -0,0 +1,35 @@
1
+ .import-page
2
+ .import-page__header
3
+ h1.header-title #{@model.name} Import
4
+ p.header-description To import #{@model.name.underscore} data using the form below.
5
+ .import-page__body
6
+ h6.body-title Import data
7
+ - if flash[:notice]
8
+ .success-card
9
+ h6.success-title
10
+ i.fa-solid.fa-circle-check
11
+ | SUCCESS
12
+ p.success-msg Your file has been uploaded and it will be processed soon.
13
+ / p.success-msg-info An email will be sent once the process is completed. If there are any problems with the import, we'll let you know through an email.
14
+ .actions-wrapper
15
+ a.secondary-btn href="#{cm_admin.send(:"#{@model.name.underscore}_import_path")}" Import new data
16
+ / button.cta-btn.ml-2 Back to Page_Name
17
+ - else
18
+ = simple_form_for(FileImport.new, url: "/admin/#{@model.ar_model.table_name}/import", method: :post, html: { class: "csv-import-form" }) do |f|
19
+ .form-card
20
+ = f.input :associated_model_name, as: :hidden, placeholder: 'Enter product title', label: false, input_html: { value: @model.name }
21
+ = f.input :import_file, as: :file, placeholder: 'Enter product title', label: false
22
+ .note-card
23
+ h6.note-title
24
+ i.fa.fa-info-circle
25
+ | Note
26
+ .steps-wrapper
27
+ h6.steps-title Follow these steps to import your data
28
+ ul.steps-list
29
+ li
30
+ | Download this
31
+ a href="#" template file
32
+ li Add your data on the file without changing the format
33
+ li Save the file as a csv, xls or xlsx
34
+ li Upload the file in the field above
35
+ = f.button :submit, class: "cta-btn import-btn", value: 'Import data'
data/cm_admin.gemspec CHANGED
@@ -26,11 +26,12 @@ Gem::Specification.new do |spec|
26
26
  spec.bindir = "exe"
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ["lib"]
29
- spec.add_runtime_dependency 'axlsx_rails', '~> 0.6.1'
29
+ spec.add_runtime_dependency 'caxlsx_rails'
30
30
  spec.add_runtime_dependency 'cocoon', '~> 1.2.15'
31
31
  spec.add_runtime_dependency 'local_time', '~> 2.1.0'
32
32
  spec.add_runtime_dependency 'pagy', '~> 4.11.0'
33
33
  spec.add_runtime_dependency 'pundit', '~> 2.2.0'
34
34
  spec.add_runtime_dependency 'slim', '~> 4.1.0'
35
35
  spec.add_runtime_dependency 'webpacker', '~> 5.4.3'
36
+ spec.add_runtime_dependency 'csv-importer', '~> 0.8.2'
36
37
  end
data/config/routes.rb CHANGED
@@ -10,6 +10,12 @@ CmAdmin::Engine.routes.draw do
10
10
 
11
11
  # Defining action routes for each model
12
12
  CmAdmin.config.cm_admin_models.each do |model|
13
+ if model.importer
14
+ scope model.name.tableize do
15
+ send(:get, 'import', to: "#{model.name.underscore}#import_form", as: "#{model.name.underscore}_import_form")
16
+ send(:post, 'import', to: "#{model.name.underscore}#import", as: "#{model.name.underscore}_import")
17
+ end
18
+ end
13
19
  model.available_actions.sort_by {|act| act.name}.each do |act|
14
20
  scope model.name.tableize do
15
21
  send(act.verb, act.path.present? ? act.path : act.name, to: "#{model.name.underscore}##{act.name}", as: "#{model.name.underscore}_#{act.name}")
@@ -1,5 +1,6 @@
1
1
  require_relative 'constants'
2
2
  require_relative 'models/action'
3
+ require_relative 'models/importer'
3
4
  require_relative 'models/custom_action'
4
5
  require_relative 'models/field'
5
6
  require_relative 'models/form_field'
@@ -15,6 +16,7 @@ require 'axlsx'
15
16
  require 'cocoon'
16
17
  require 'pundit'
17
18
  require 'local_time'
19
+ require 'csv_importer'
18
20
 
19
21
  module CmAdmin
20
22
  class Model
@@ -23,7 +25,7 @@ module CmAdmin
23
25
  include Models::DslMethod
24
26
  attr_accessor :available_actions, :actions_set, :available_fields, :permitted_fields,
25
27
  :current_action, :params, :filters, :available_tabs, :icon_name
26
- attr_reader :name, :ar_model, :is_visible_on_sidebar
28
+ attr_reader :name, :ar_model, :is_visible_on_sidebar, :importer
27
29
 
28
30
  def initialize(entity, &block)
29
31
  @name = entity.name
@@ -80,6 +82,10 @@ module CmAdmin
80
82
  @actions_set = true
81
83
  end
82
84
 
85
+ def importable(class_name:, importer_type:)
86
+ @importer = CmAdmin::Models::Importer.new(class_name, importer_type)
87
+ end
88
+
83
89
  def visible_on_sidebar(visible_option)
84
90
  @is_visible_on_sidebar = visible_option
85
91
  end
@@ -1,7 +1,7 @@
1
1
  module CmAdmin
2
2
  module Models
3
3
  class Column
4
- attr_accessor :field_name, :field_type, :header, :format, :prefix, :suffix, :exportable, :round,
4
+ attr_accessor :field_name, :field_type, :header, :format, :prefix, :suffix, :exportable, :round, :height, :width,
5
5
  :cm_css_class, :link, :url, :custom_method, :helper_method, :managable, :lockable, :drawer_partial, :tag_class
6
6
 
7
7
  def initialize(field_name, attributes = {})
@@ -13,6 +13,8 @@ module CmAdmin
13
13
 
14
14
  #formatting header (either field_name or value present in header attribute)
15
15
  self.send("header=", format_header)
16
+ self.height = 50 if self.field_type == :image && self.height.nil?
17
+ self.width = 50 if self.field_type == :image && self.width.nil?
16
18
  end
17
19
 
18
20
  #returns a string value as a header (either field_name or value present in header attribute)
@@ -2,8 +2,8 @@ module CmAdmin
2
2
  module Models
3
3
  class Field
4
4
 
5
- attr_accessor :field_name, :label, :header, :field_type, :format, :precision,
6
- :helper_method, :preview, :custom_link, :precision, :prefix, :suffix, :tag_class
5
+ attr_accessor :field_name, :label, :header, :field_type, :format, :precision, :height,
6
+ :width, :helper_method, :preview, :custom_link, :precision, :prefix, :suffix, :tag_class
7
7
 
8
8
  def initialize(field_name, attributes = {})
9
9
  @field_name = field_name
@@ -11,6 +11,8 @@ module CmAdmin
11
11
  attributes.each do |key, value|
12
12
  self.send("#{key.to_s}=", value)
13
13
  end
14
+ self.height = 50 if self.field_type == :image && self.height.nil?
15
+ self.width = 50 if self.field_type == :image && self.width.nil?
14
16
  end
15
17
 
16
18
  def set_default_values
@@ -1,21 +1,22 @@
1
1
  module CmAdmin
2
2
  module Models
3
3
  class FormField
4
- attr_accessor :field_name, :label, :header, :input_type, :collection, :custom_value, :disabled, :collection_method
4
+ attr_accessor :field_name, :label, :header, :input_type, :collection, :disabled, :helper_method
5
5
  VALID_INPUT_TYPES = [:integer, :decimal, :string, :single_select, :multi_select, :date, :date_time, :text, :single_file_upload, :multi_file_upload, :hidden, :rich_text].freeze
6
6
 
7
7
  def initialize(field_name, input_type, attributes = {})
8
- raise ArgumentError, "Kindly select a valid filter type like #{VALID_INPUT_TYPES.sort.to_sentence(last_word_connector: ', or ')} instead of #{input_type} for column #{field_name}" unless VALID_INPUT_TYPES.include?(input_type.to_sym)
9
8
  @field_name = field_name
10
9
  set_default_values
11
10
  attributes.each do |key, value|
12
11
  self.send("#{key.to_s}=", value)
13
12
  end
13
+ raise ArgumentError, "Kindly select a valid input type like #{VALID_INPUT_TYPES.sort.to_sentence(last_word_connector: ', or ')} instead of #{self.input_type} for form field #{field_name}" unless VALID_INPUT_TYPES.include?(self.input_type.to_sym)
14
14
  end
15
15
 
16
16
  def set_default_values
17
17
  self.disabled = false
18
18
  self.label = self.field_name.to_s.titleize
19
+ self.input_type = :string
19
20
  end
20
21
  end
21
22
  end
@@ -0,0 +1,17 @@
1
+ module CmAdmin
2
+ module Models
3
+ class Importer
4
+
5
+ attr_accessor :class_name, :importer_type
6
+
7
+ VALID_IMPORTER_TYPES = [:csv_importer, :custom_importer]
8
+
9
+ def initialize(class_name, importer_type=:csv_importer)
10
+ raise ArgumentError, "Kindly select a valid importer type like #{VALID_IMPORTER_TYPES.sort.to_sentence(last_word_connector: ', or ')} instead of #{importer_type}" unless VALID_IMPORTER_TYPES.include?(importer_type.to_sym)
11
+ @class_name = class_name
12
+ @importer_type = importer_type
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -1,3 +1,3 @@
1
1
  module CmAdmin
2
- VERSION = "0.7.7"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -61,6 +61,14 @@ module CmAdmin
61
61
  concat content_tag(:div, ar_object.send(field.field_name).to_s, class: 'text-ellipsis')
62
62
  concat content_tag(:div, 'View', class: 'drawer-btn')
63
63
  end
64
+ when :image
65
+ content_tag(:div, class: 'd-flex') do
66
+ if ar_object.send(field.field_name).attached?
67
+ image_tag(ar_object.send(field.field_name).url, height: field.height, width: field.height)
68
+ else
69
+ image_tag('/assets/image_not_available', height: 50, width: 50)
70
+ end
71
+ end
64
72
  end
65
73
  end
66
74
 
@@ -2,7 +2,7 @@ module CmAdmin
2
2
  module ViewHelpers
3
3
  module FormFieldHelper
4
4
  def input_field_for_column(f, field)
5
- value = field.custom_value || f.object.send(field.field_name)
5
+ value = field.helper_method ? send(field.helper_method) : f.object.send(field.field_name)
6
6
  is_required = f.object._validators[field.field_name].map(&:kind).include?(:presence)
7
7
  required_class = is_required ? 'required' : ''
8
8
  case field.input_type
@@ -13,9 +13,9 @@ module CmAdmin
13
13
  when :string
14
14
  return f.text_field field.field_name, class: "normal-input #{required_class}", disabled: field.disabled, value: value, placeholder: "Enter #{field.field_name.to_s.downcase.gsub('_', ' ')}"
15
15
  when :single_select
16
- return f.select field.field_name, options_for_select(select_collection_value(field), value), {include_blank: "Select #{field.field_name.to_s.downcase.gsub('_', ' ')}"}, class: "normal-input #{required_class} select-2", disabled: field.disabled
16
+ return f.select field.field_name, options_for_select(select_collection_value(field), f.object.send(field.field_name)), {include_blank: "Select #{field.field_name.to_s.downcase.gsub('_', ' ')}"}, class: "normal-input #{required_class} select-2", disabled: field.disabled
17
17
  when :multi_select
18
- return f.select field.field_name, options_for_select(select_collection_value(field), value), {include_blank: "Select #{field.field_name.to_s.downcase.gsub('_', ' ')}"}, class: "normal-input #{required_class} select-2", disabled: field.disabled, multiple: true
18
+ return f.select field.field_name, options_for_select(select_collection_value(field), f.object.send(field.field_name)), {include_blank: "Select #{field.field_name.to_s.downcase.gsub('_', ' ')}"}, class: "normal-input #{required_class} select-2", disabled: field.disabled, multiple: true
19
19
  when :date
20
20
  return f.text_field field.field_name, class: "normal-input #{required_class}", disabled: field.disabled, value: value&.strftime('%d-%m-%Y'), placeholder: "Enter #{field.field_name.to_s.downcase.gsub('_', ' ')}", data: { behaviour: 'date-only' }
21
21
  when :date_time
@@ -29,13 +29,15 @@ module CmAdmin
29
29
  when :multi_file_upload
30
30
  return f.file_field field.field_name, multiple: true, class: "normal-input #{required_class}"
31
31
  when :hidden
32
- return f.hidden_field field.field_name, value: field.custom_value
32
+ return f.hidden_field field.field_name, value: value
33
33
  end
34
34
  end
35
35
 
36
+ # Refactor: Collection argument can be removed.
37
+ # helper_method argument will accept a method where value can be passed.
36
38
  def select_collection_value(field)
37
- if field.collection_method
38
- collection = send(field.collection_method)
39
+ if field.helper_method
40
+ collection = send(field.helper_method)
39
41
  elsif field.collection
40
42
  collection = field.collection
41
43
  else
@@ -1,6 +1,6 @@
1
1
  module CmAdmin
2
2
  module ViewHelpers
3
- Dir[File.expand_path("view_helpers", __dir__) + "/*.rb"].each { |f| require f }
3
+ Dir[File.expand_path('view_helpers', __dir__) + '/*.rb'].each { |f| require f }
4
4
 
5
5
  include ActionDropdownHelper
6
6
  include FieldDisplayHelper
@@ -14,17 +14,17 @@ module CmAdmin
14
14
  include ActionView::Helpers::FormTagHelper
15
15
  include ActionView::Helpers::TagHelper
16
16
 
17
- def exportable(klass, html_class: [])
18
- tag.a "Export as excel", class: html_class.append("filter-btn modal-btn mr-2"), data: {toggle: "modal", target: "#exportmodal"} do
17
+ def exportable(_klass, html_class: [])
18
+ tag.a 'Export as excel', class: html_class.append('filter-btn modal-btn mr-2'), data: { toggle: 'modal', target: '#exportmodal' } do
19
19
  concat tag.i class: 'fa fa-download'
20
- concat tag.span " Export"
20
+ concat tag.span ' Export'
21
21
  end
22
22
  end
23
23
 
24
24
  def column_pop_up(klass, required_filters = nil)
25
- tag.div class: "modal fade form-modal", id: "exportmodal", role: "dialog", aria: {labelledby: "exportModal"} do
26
- tag.div class: "modal-dialog modal-lg", role: "document" do
27
- tag.div class: "modal-content" do
25
+ tag.div class: 'modal fade form-modal', id: 'exportmodal', role: 'dialog', aria: { labelledby: 'exportModal' } do
26
+ tag.div class: 'modal-dialog modal-lg', role: 'document' do
27
+ tag.div class: 'modal-content' do
28
28
  concat pop_ups(klass, required_filters)
29
29
  end
30
30
  end
@@ -39,17 +39,17 @@ module CmAdmin
39
39
  end
40
40
 
41
41
  def pop_up_header
42
- tag.div class: "modal-header" do
43
- tag.button type: "button", class: "close", data: {dismiss: "modal"}, aria: {label: "Close"} do
44
- tag.span "X", aria: {hidden: "true"}
42
+ tag.div class: 'modal-header' do
43
+ tag.button type: 'button', class: 'close', data: { dismiss: 'modal' }, aria: { label: 'Close' } do
44
+ tag.span 'X', aria: { hidden: 'true' }
45
45
  end
46
- tag.h4 "Select columns to export", class: "modal-title", id: "exportModal"
46
+ tag.h4 'Select columns to export', class: 'modal-title', id: 'exportModal'
47
47
  end
48
48
  end
49
49
 
50
- def pop_up_body(klass, required_filters)
51
- tag.div class: "modal-body" do
52
- form_tag '/cm_admin/export_to_file.js', id: 'export-to-file-form', style: "width: 100%;", class:"cm-admin-csv-export-form" do
50
+ def pop_up_body(klass, _required_filters)
51
+ tag.div class: 'modal-body' do
52
+ form_tag cm_admin.send('export_to_file_path'), id: 'export-to-file-form', style: 'width: 100%;', class: 'cm-admin-csv-export-form' do
53
53
  concat hidden_field_tag 'class_name', klass.name.to_s, id: 'export-to-file-klass'
54
54
  concat checkbox_row(klass)
55
55
  concat tag.hr
@@ -59,7 +59,7 @@ module CmAdmin
59
59
  end
60
60
 
61
61
  def checkbox_row(klass)
62
- tag.div class: "row" do
62
+ tag.div class: 'row' do
63
63
  CmAdmin::Models::Export.exportable_columns(klass).each do |column_path|
64
64
  concat create_checkbox(column_path)
65
65
  end
@@ -67,9 +67,9 @@ module CmAdmin
67
67
  end
68
68
 
69
69
  def create_checkbox(column_path)
70
- tag.div class: "col-md-4" do
71
- concat check_box_tag "columns[]", column_path, id: column_path.to_s.gsub('/', '-')
72
- concat " " + column_path.to_s.gsub('/', '_').humanize
70
+ tag.div class: 'col-md-4' do
71
+ concat check_box_tag 'columns[]', column_path, id: column_path.to_s.gsub('/', '-')
72
+ concat " #{column_path.to_s.gsub('/', '_').humanize}"
73
73
  end
74
74
  end
75
75
  end
data/lib/cm_admin.rb CHANGED
@@ -35,7 +35,8 @@ module CmAdmin
35
35
  def initialize_model(entity, &block)
36
36
  if entity.is_a?(Class)
37
37
  return if CmAdmin::Model.find_by({name: entity.name})
38
- config.cm_admin_models << CmAdmin::Model.new(entity, &block)
38
+ cm_model = CmAdmin::Model.new(entity, &block)
39
+ config.cm_admin_models << cm_model
39
40
  end
40
41
  end
41
42
  end
@@ -0,0 +1,25 @@
1
+ require 'rails/generators'
2
+
3
+ module CmAdmin
4
+ module Generators
5
+ class AddGraphqlGenerator < Rails::Generators::Base
6
+ source_root File.expand_path('templates', __dir__)
7
+
8
+ def add_graphql
9
+ gem 'graphql'
10
+ gem 'graphql-errors'
11
+ gem 'graphql-rails_logger'
12
+ generate 'graphql:install'
13
+ template 'graphql/graphql_schema.rb', "app/graphql/#{Rails.application.class.module_parent_name.underscore}_schema.rb"
14
+ directory 'graphql/inputs/base', 'app/graphql/types/inputs/base'
15
+ directory 'graphql/enums/base', 'app/graphql/types/enums/base'
16
+ directory 'graphql/objects/base', 'app/graphql/types/objects/base'
17
+ directory 'concerns', 'app/models/concerns'
18
+ copy_file 'graphql/mutations/base_mutation.rb', 'app/graphql/mutations/base_mutation.rb'
19
+ copy_file 'graphql/queries/base_query.rb', 'app/graphql/queries/base_query.rb'
20
+ copy_file 'exceptions/base_exception.rb', 'app/exceptions/base_exception.rb'
21
+ copy_file 'constants.rb', 'config/initializers/constants.rb'
22
+ end
23
+ end
24
+ end
25
+ end
@@ -14,6 +14,8 @@ module CmAdmin
14
14
  remove_file 'app/assets/stylesheets/actiontext.scss'
15
15
  copy_file 'application_policy.rb', 'app/policies/application_policy.rb'
16
16
  route 'mount CmAdmin::Engine => "/admin"'
17
+ generate 'migration', 'CreateFileImport associated_model_name:string added_by:references{polymorphic} error_report:jsonb completed_at:datetime status:integer'
18
+ rake 'db:migrate'
17
19
  end
18
20
  end
19
21
  end
@@ -2,5 +2,5 @@ CmAdmin.configure do |config|
2
2
  # Sets the default layout to be used for admin
3
3
  config.layout = 'admin'
4
4
  # config.authorized_roles = [:super_admin?]
5
- # config.included_models = [Admin]
5
+ config.included_models = [FileImport]
6
6
  end
@@ -0,0 +1,63 @@
1
+ module Attachable
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ end
6
+
7
+ def save_with_attachments
8
+ save!
9
+ save_attachment
10
+ end
11
+
12
+ def update!(args)
13
+ add_accessors_for_attachment_types
14
+ super
15
+ save_attachment
16
+ end
17
+
18
+ def initialize(args)
19
+ add_accessors_for_attachment_types
20
+ super
21
+ end
22
+
23
+ def save_attachment
24
+ self.class.attachment_types.each do |attachment_type|
25
+ next if send("#{attachment_type}_file").blank?
26
+
27
+ arr = []
28
+ if send("#{attachment_type}_file").class.eql?(Array)
29
+ arr = send("#{attachment_type}_file")
30
+ else
31
+ arr << send("#{attachment_type}_file")
32
+ end
33
+ arr.each do |x|
34
+ regexp = %r{\Adata:([-\w]+\/[-\w\+\.]+)?;base64,(.*)}m
35
+ data_uri_parts = x[:content].match(regexp) || []
36
+ decoded_data = Base64.decode64(data_uri_parts[2])
37
+ filename = x[:filename]
38
+ filepath = "#{Rails.root}/tmp/#{filename}"
39
+ File.open(filepath, 'wb') do |f|
40
+ f.write(decoded_data)
41
+ end
42
+ send(attachment_type.to_s).attach(io: File.open(filepath), filename: filename, content_type: data_uri_parts[1])
43
+ File.delete(filepath)
44
+ end
45
+ end
46
+ end
47
+
48
+ def attached_url(attachment_type)
49
+ if send(attachment_type.to_s).attached? && send(attachment_type.to_s).class == ActiveStorage::Attached::One
50
+ Rails.application.routes.url_helpers.rails_blob_url(send(attachment_type.to_s))
51
+ elsif send(attachment_type.to_s).attached? && send(attachment_type.to_s).class == ActiveStorage::Attached::Many
52
+ send(attachment_type.to_s)
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def add_accessors_for_attachment_types
59
+ self.class.attachment_types.each do |attachment_type|
60
+ singleton_class.class_eval { attr_accessor "#{attachment_type}_file" }
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,22 @@
1
+ class FilteredList
2
+ attr_reader :data, :facets, :paging
3
+
4
+ def initialize(paginated_list)
5
+ self.data = paginated_list[:list]
6
+ self.paging = paginated_list
7
+ end
8
+
9
+ def data=(data)
10
+ @data = []
11
+ @data = data if data.present?
12
+ end
13
+
14
+ def paging=(paginated_list)
15
+ @paging = {
16
+ total_items: paginated_list[:list].total_count,
17
+ current_page: paginated_list[:list].current_page,
18
+ total_pages: paginated_list[:list].total_pages,
19
+ total_count: paginated_list[:total_count]
20
+ }
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ module Paginator
2
+ extend ActiveSupport::Concern
3
+ module ClassMethods
4
+ def list(per_page = DEFAULT_PER_PAGE, page = nil, _filter_params = nil, total_count = nil)
5
+ paginated_list = {}
6
+ per_page = DEFAULT_PER_PAGE if per_page == 0
7
+ paginated_list[:list] = self.page(page || 1).per(per_page)
8
+ paginated_list[:total_count] = total_count
9
+ FilteredList.new(paginated_list)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ DEFAULT_PER_PAGE = 20
2
+ DEFAULT_SORT_COLUMN = 'created_at'.freeze
3
+ DEFAULT_SORT_DIRECTION = 'desc'.freeze
@@ -0,0 +1,9 @@
1
+ class BaseException < StandardError
2
+ def initialize(message = nil)
3
+ @message = message
4
+ end
5
+
6
+ def message
7
+ @message || 'Hello!'
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module Types
2
+ class Enums::Base::SortColumn < Types::BaseEnum
3
+ description 'Possible values for sort column'
4
+
5
+ value :created_at, 'Sort by created_at'
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ module Types
2
+ class Enums::Base::SortDirection < Types::BaseEnum
3
+ description 'Possible values for sort direction'
4
+
5
+ value :asc, 'Sort by ascending'
6
+ value :desc, 'Sort by descending'
7
+ end
8
+ end
@@ -0,0 +1,55 @@
1
+ class <%= Rails.application.class.module_parent_name %>Schema < GraphQL::Schema
2
+ mutation(Types::MutationType)
3
+ query(Types::QueryType)
4
+
5
+ # Union and Interface Resolution
6
+ def self.resolve_type(abstract_type, obj, ctx)
7
+ # TODO: Implement this function
8
+ # to return the correct object type for `obj`
9
+ raise(GraphQL::RequiredImplementationMissingError)
10
+ end
11
+
12
+ # Relay-style Object Identification:
13
+
14
+ # Return a string UUID for `object`
15
+ def self.id_from_object(object, type_definition, query_ctx)
16
+ # Here's a simple implementation which:
17
+ # - joins the type name & object.id
18
+ # - encodes it with base64:
19
+ # GraphQL::Schema::UniqueWithinType.encode(type_definition.name, object.id)
20
+ end
21
+
22
+ # Given a string UUID, find the object
23
+ def self.object_from_id(id, query_ctx)
24
+ # For example, to decode the UUIDs generated above:
25
+ # type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id)
26
+ #
27
+ # Then, based on `type_name` and `id`
28
+ # find an object in your application
29
+ # ...
30
+ end
31
+
32
+ rescue_from ActiveRecord::RecordNotFound do |err, obj, args, ctx, field|
33
+ GraphQL::ExecutionError.new("#{field.type.unwrap.graphql_name} not found", extensions: {code: :unprocessable_entity, sub_code: :record_invalid, message: err.message})
34
+ end
35
+
36
+ rescue_from ActiveRecord::RecordInvalid do |err, obj, args, ctx, field|
37
+ GraphQL::ExecutionError.new(err.message, extensions: {code: :unprocessable_entity, sub_code: :record_invalid, message: err.message})
38
+ end
39
+
40
+ rescue_from BaseException do |err, obj, args, ctx, field|
41
+ GraphQL::ExecutionError.new(err.message, extensions: {code: err.code, sub_code: err.sub_code, message: err.message})
42
+ end
43
+
44
+ unless Rails.env.development?
45
+ rescue_from StandardError do |err, obj, args, ctx, field|
46
+ rollbar_error = Rollbar.error(err)
47
+ GraphQL::ExecutionError.new("Internal Server Error", extensions: {code: :internal_server_error, uuid: rollbar_error[:uuid]})
48
+ end
49
+ end
50
+
51
+ def self.unauthorized_object(error)
52
+ raise Unauthorized, I18n.t("graphql.unauthorized", error_type: error.type.graphql_name)
53
+ end
54
+
55
+ end
@@ -0,0 +1,15 @@
1
+ module Types
2
+ module Inputs
3
+ module Base
4
+ class Attachment < Types::BaseInputObject
5
+ graphql_name 'AttachmentInput'
6
+
7
+ description 'Attributes needed to attach a file'
8
+
9
+ argument :filename, String, nil, required: true
10
+ argument :content, String, nil, required: true
11
+ end
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,15 @@
1
+ module Types
2
+ module Inputs
3
+ module Base
4
+ class Filter < Types::BaseInputObject
5
+ graphql_name 'BaseFilterInput'
6
+
7
+ description 'Attributes needed for filtering items'
8
+
9
+ argument :ids, [Integer], nil, required: false
10
+ argument :q, String, nil, required: false
11
+ end
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,15 @@
1
+ module Types
2
+ module Inputs
3
+ module Base
4
+ class Paging < Types::BaseInputObject
5
+ graphql_name 'PagingInput'
6
+
7
+ description 'Attributes needed for paginating list of items'
8
+
9
+ argument :page_no, Integer, nil, required: true
10
+ argument :per_page, Integer, nil, required: false
11
+ end
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,15 @@
1
+ module Types
2
+ module Inputs
3
+ module Base
4
+ class Sort < Types::BaseInputObject
5
+ graphql_name 'SortInput'
6
+
7
+ description 'Attributes needed for sorting the list of items'
8
+
9
+ argument :column, Types::Enums::Base::SortColumn, nil, required: true
10
+ argument :direction, Types::Enums::Base::SortDirection, nil, required: true
11
+ end
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,8 @@
1
+ module Mutations
2
+ class BaseMutation < GraphQL::Schema::RelayClassicMutation
3
+ argument_class Types::BaseArgument
4
+ field_class Types::BaseField
5
+ input_object_class Types::BaseInputObject
6
+ object_class Types::BaseObject
7
+ end
8
+ end