cm-admin 0.7.7 → 0.8.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 (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