lato 0.1.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 (86) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +95 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/lato_manifest.js +5 -0
  6. data/app/assets/images/lato/user-150x150.jpg +0 -0
  7. data/app/assets/images/lato/user-300x300.jpg +0 -0
  8. data/app/assets/images/lato/user-600x600.jpg +0 -0
  9. data/app/assets/images/lato/user-900x900.jpg +0 -0
  10. data/app/assets/javascripts/lato/application.js +6 -0
  11. data/app/assets/javascripts/lato/controllers/application.js +9 -0
  12. data/app/assets/javascripts/lato/controllers/index.js +11 -0
  13. data/app/assets/javascripts/lato/controllers/lato_action_controller.js +92 -0
  14. data/app/assets/javascripts/lato/controllers/lato_aside_opener_controller.js +21 -0
  15. data/app/assets/javascripts/lato/controllers/lato_feedback_controller.js +13 -0
  16. data/app/assets/javascripts/lato/controllers/lato_form_controller.js +60 -0
  17. data/app/assets/javascripts/lato/controllers/lato_hello_controller.js +12 -0
  18. data/app/assets/javascripts/lato/controllers/lato_operation_controller.js +24 -0
  19. data/app/assets/stylesheets/lato/application.scss +73 -0
  20. data/app/controllers/concerns/lato/componentable.rb +52 -0
  21. data/app/controllers/concerns/lato/layoutable.rb +27 -0
  22. data/app/controllers/concerns/lato/sessionable.rb +45 -0
  23. data/app/controllers/lato/account_controller.rb +82 -0
  24. data/app/controllers/lato/application_controller.rb +21 -0
  25. data/app/controllers/lato/authentication_controller.rb +121 -0
  26. data/app/controllers/lato/operations_controller.rb +23 -0
  27. data/app/helpers/lato/application_helper.rb +5 -0
  28. data/app/helpers/lato/components_helper.rb +183 -0
  29. data/app/jobs/lato/application_job.rb +59 -0
  30. data/app/mailers/lato/application_mailer.rb +6 -0
  31. data/app/mailers/lato/user_mailer.rb +23 -0
  32. data/app/models/lato/application_record.rb +5 -0
  33. data/app/models/lato/operation.rb +96 -0
  34. data/app/models/lato/session.rb +36 -0
  35. data/app/models/lato/user.rb +164 -0
  36. data/app/views/lato/account/_alert-accepted-privacy-policy-version.html.erb +20 -0
  37. data/app/views/lato/account/_alert-accepted-terms-and-conditions-version.html.erb +20 -0
  38. data/app/views/lato/account/_form-destroy.html.erb +21 -0
  39. data/app/views/lato/account/_form-password.html.erb +28 -0
  40. data/app/views/lato/account/_form-user.html.erb +40 -0
  41. data/app/views/lato/account/index.html.erb +65 -0
  42. data/app/views/lato/authentication/_form-recover-password.html.erb +20 -0
  43. data/app/views/lato/authentication/_form-signin.html.erb +31 -0
  44. data/app/views/lato/authentication/_form-signup.html.erb +47 -0
  45. data/app/views/lato/authentication/_form-update-password.html.erb +30 -0
  46. data/app/views/lato/authentication/_form-verify-email.html.erb +22 -0
  47. data/app/views/lato/authentication/recover_password.html.erb +13 -0
  48. data/app/views/lato/authentication/signin.html.erb +13 -0
  49. data/app/views/lato/authentication/signout.html.erb +11 -0
  50. data/app/views/lato/authentication/signup.html.erb +13 -0
  51. data/app/views/lato/authentication/update_password.html.erb +13 -0
  52. data/app/views/lato/authentication/verify_email.html.erb +7 -0
  53. data/app/views/lato/components/_index.html.erb +89 -0
  54. data/app/views/lato/components/_navbar_nav_item.html.erb +5 -0
  55. data/app/views/lato/components/_operation.html.erb +68 -0
  56. data/app/views/lato/components/_page_head.html.erb +15 -0
  57. data/app/views/lato/components/_sidebar_nav_item.html.erb +5 -0
  58. data/app/views/lato/mailer/user/email_verification_mail.html.erb +9 -0
  59. data/app/views/lato/mailer/user/password_update_mail.html.erb +9 -0
  60. data/app/views/lato/operations/show.html.erb +3 -0
  61. data/app/views/layouts/lato/_action.html.erb +17 -0
  62. data/app/views/layouts/lato/_aside-opener.html.erb +7 -0
  63. data/app/views/layouts/lato/_content.html.erb +5 -0
  64. data/app/views/layouts/lato/_feedbacks.html.erb +16 -0
  65. data/app/views/layouts/lato/_footer.html.erb +11 -0
  66. data/app/views/layouts/lato/_mailer-foot_content.html.erb +2 -0
  67. data/app/views/layouts/lato/_mailer-head_content.html.erb +2 -0
  68. data/app/views/layouts/lato/_navbar-brand_content.html.erb +1 -0
  69. data/app/views/layouts/lato/_navbar-nav_content.html.erb +15 -0
  70. data/app/views/layouts/lato/_navbar.html.erb +16 -0
  71. data/app/views/layouts/lato/_sidebar-nav_content.html.erb +3 -0
  72. data/app/views/layouts/lato/_sidebar.html.erb +5 -0
  73. data/app/views/layouts/lato/application.html.erb +44 -0
  74. data/app/views/layouts/lato/mailer.html.erb +3 -0
  75. data/config/importmap.rb +8 -0
  76. data/config/locales/it.yml +235 -0
  77. data/config/routes.rb +35 -0
  78. data/db/migrate/20221022205744_create_lato_users.rb +14 -0
  79. data/db/migrate/20221118072130_create_lato_operations.rb +14 -0
  80. data/lib/lato/btstrap.rb +36 -0
  81. data/lib/lato/config.rb +39 -0
  82. data/lib/lato/engine.rb +13 -0
  83. data/lib/lato/version.rb +3 -0
  84. data/lib/lato.rb +27 -0
  85. data/lib/tasks/lato_tasks.rake +28 -0
  86. metadata +199 -0
@@ -0,0 +1,21 @@
1
+ module Lato
2
+ class ApplicationController < ActionController::Base
3
+ include Lato::Sessionable
4
+ include Lato::Layoutable
5
+ include Lato::Componentable
6
+
7
+ def index
8
+ session_root_path = Lato.config.session_root_path ? main_app.send(Lato.config.session_root_path) : lato.account_path
9
+ redirect_to @session.valid? ? session_root_path : lato.authentication_signin_path
10
+ end
11
+
12
+ protected
13
+
14
+ def respond_to_with_404
15
+ respond_to do |format|
16
+ format.html { render plain: '', status: :not_found }
17
+ format.json { render json: {}, status: :not_found }
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,121 @@
1
+ module Lato
2
+ class AuthenticationController < ApplicationController
3
+ before_action :not_authenticate_session, only: %i[signin signin_action signup signup_action]
4
+ before_action :authenticate_session, only: %i[signout signout_action]
5
+ before_action :find_user, only: %i[verify_email verify_email_action update_password update_password_action]
6
+ before_action :hide_sidebar
7
+ before_action :lock_signup_if_disabled, only: %i[signup signup_action]
8
+
9
+ def signin
10
+ @user = Lato::User.new
11
+ end
12
+
13
+ def signin_action
14
+ @user = Lato::User.new
15
+
16
+ respond_to do |format|
17
+ if @user.signin(params.require(:user).permit(:email, :password))
18
+ session_create(@user.id)
19
+
20
+ format.html { redirect_to lato.root_path }
21
+ format.json { render json: @user }
22
+ else
23
+ format.html { render :signin, status: :unprocessable_entity }
24
+ format.json { render json: @user.errors, status: :unprocessable_entity }
25
+ end
26
+ end
27
+ end
28
+
29
+ def signup
30
+ @user = Lato::User.new
31
+ end
32
+
33
+ def signup_action
34
+ @user = Lato::User.new(params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation, :accepted_privacy_policy_version, :accepted_terms_and_conditions_version))
35
+
36
+ respond_to do |format|
37
+ if @user.save
38
+ session_create(@user.id)
39
+
40
+ format.html { redirect_to lato.root_path }
41
+ format.json { render json: @user }
42
+ else
43
+ format.html { render :signup, status: :unprocessable_entity }
44
+ format.json { render json: @user.errors, status: :unprocessable_entity }
45
+ end
46
+ end
47
+ end
48
+
49
+ def signout; end
50
+
51
+ def signout_action
52
+ session_destroy
53
+
54
+ respond_to do |format|
55
+ format.html { redirect_to lato.root_path }
56
+ format.json { render plain: '' }
57
+ end
58
+ end
59
+
60
+ def verify_email
61
+ @code = params[:code]
62
+ end
63
+
64
+ def verify_email_action
65
+ respond_to do |format|
66
+ if @user.verify_email(params.require(:user).permit(:code))
67
+ format.html { redirect_to lato.root_path, notice: 'Indirizzo email verificato correttamente' }
68
+ format.json { render json: @user }
69
+ else
70
+ format.html { render :verify_email, status: :unprocessable_entity }
71
+ format.json { render json: @user.errors, status: :unprocessable_entity }
72
+ end
73
+ end
74
+ end
75
+
76
+ def recover_password
77
+ @user = Lato::User.new
78
+ end
79
+
80
+ def recover_password_action
81
+ @user = Lato::User.new
82
+
83
+ respond_to do |format|
84
+ if @user.request_recover_password(params.require(:user).permit(:email))
85
+ format.html { redirect_to lato.authentication_update_password_path(id: @user.id) }
86
+ format.json { render json: @user }
87
+ else
88
+ format.html { render :recover_password, status: :unprocessable_entity }
89
+ format.json { render json: @user.errors, status: :unprocessable_entity }
90
+ end
91
+ end
92
+ end
93
+
94
+ def update_password; end
95
+
96
+ def update_password_action
97
+ respond_to do |format|
98
+ if @user.update_password(params.require(:user).permit(:code, :password, :password_confirmation))
99
+ format.html { redirect_to lato.authentication_signin_path, notice: 'La tua password è stata aggiornata correttamente' }
100
+ format.json { render json: @user }
101
+ else
102
+ format.html { render :update_password, status: :unprocessable_entity }
103
+ format.json { render json: @user.errors, status: :unprocessable_entity }
104
+ end
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ def find_user
111
+ @user = User.find_by(id: params[:id])
112
+ respond_to_with_404 unless @user
113
+ end
114
+
115
+ def lock_signup_if_disabled
116
+ return unless Lato.config.auth_disable_signup
117
+
118
+ respond_to_with_404
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,23 @@
1
+ module Lato
2
+ class OperationsController < ApplicationController
3
+ before_action :authenticate_session
4
+
5
+ def show
6
+ @operation = Lato::Operation.find(params[:id])
7
+ return unless validate_user_access_to_operation
8
+ end
9
+
10
+ private
11
+
12
+ def validate_user_access_to_operation
13
+ return true if @operation.lato_user_id == @session.user_id
14
+
15
+ respond_to do |format|
16
+ format.html { redirect_to lato.root_path }
17
+ format.json { render plain: '', status: :unauthorized }
18
+ end
19
+
20
+ false
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ module Lato
2
+ module ApplicationHelper
3
+ include Lato::ComponentsHelper
4
+ end
5
+ end
@@ -0,0 +1,183 @@
1
+ module Lato
2
+ module ComponentsHelper
3
+ # Navbar
4
+ ##
5
+
6
+ def lato_navbar_nav_item(key, path, &block)
7
+ active = request.path == path
8
+ active = @navbar_key == key if @navbar_key
9
+
10
+ render 'lato/components/navbar_nav_item', active: active, path: path do
11
+ yield if block
12
+ end
13
+ end
14
+
15
+ # Sidebar
16
+ ##
17
+
18
+ def lato_sidebar_nav_item(key, path, &block)
19
+ active = request.path == path
20
+ active = @sidebar_key == key if @sidebar_key
21
+
22
+ render 'lato/components/sidebar_nav_item', active: active, path: path do
23
+ yield if block
24
+ end
25
+ end
26
+
27
+ # Page head
28
+ ##
29
+
30
+ def lato_page_head(title, breadcrumbs = [], &block)
31
+ render 'lato/components/page_head', title: title, breadcrumbs: breadcrumbs do
32
+ yield if block
33
+ end
34
+ end
35
+
36
+ # Index
37
+ ##
38
+
39
+ def lato_index(collection, options = {})
40
+ key = options[:key] || 'default'
41
+
42
+ @_lato_index ||= {}
43
+ @_lato_index[key] ||= {}
44
+ columns = options[:columns] || @_lato_index[key][:columns] || collection.column_names || []
45
+ sortable_columns = @_lato_index[key][:sortable_columns] || []
46
+ searchable_columns = @_lato_index[key][:searchable_columns] || []
47
+ model_name_underscore = collection.model.name.underscore
48
+
49
+ render(
50
+ 'lato/components/index',
51
+ key: key,
52
+ collection: collection,
53
+ columns: columns,
54
+ sortable_columns: sortable_columns,
55
+ searchable_columns: searchable_columns,
56
+ model_name_underscore: model_name_underscore,
57
+ custom_actions: options[:custom_actions] || {}
58
+ )
59
+ end
60
+
61
+ # Operation
62
+ ##
63
+
64
+ def lato_operation(operation)
65
+ render(
66
+ 'lato/components/operation',
67
+ operation: operation
68
+ )
69
+ end
70
+
71
+ # Forms
72
+ ##
73
+
74
+ def _lato_form_input_options(form, key, options, action_change_event, classes = '')
75
+ # setup classes
76
+ options[:class] ||= []
77
+ options[:class].push(classes)
78
+ options[:class].push('is-invalid') unless form.object.errors[key].blank?
79
+
80
+ # setup stimulus
81
+ options[:data] ||= {}
82
+ options[:data][:action] ||= ''
83
+ options[:data][:action] += " #{action_change_event}->lato-form#onInputChange"
84
+ options[:data][:lato_form_target] = 'input'
85
+ end
86
+
87
+ def lato_form_notices(options = {})
88
+ return unless notice
89
+
90
+ options[:class] ||= []
91
+ options[:class] += %w[alert alert-success]
92
+ options[:class] += %w[alert-dismissible fade show] unless options[:fixed]
93
+
94
+ content_tag :div, options do
95
+ concat notice
96
+ concat button_tag('', type: 'button', class: 'btn-close', data: { bs_dismiss: 'alert' }) unless options[:fixed]
97
+ end
98
+ end
99
+
100
+ def lato_form_errors(instance, options = {})
101
+ return unless instance.errors.any?
102
+
103
+ options[:class] ||= []
104
+ options[:class] += %w[alert alert-danger]
105
+ options[:class] += %w[alert-dismissible fade show] unless options[:fixed]
106
+
107
+ errors_list = content_tag(:ul, class: %w[mb-0 ps-3]) do
108
+ instance.errors.collect do |error|
109
+ content_tag :li, error.full_message
110
+ end.join.html_safe
111
+ end
112
+
113
+ content_tag :div, options do
114
+ concat content_tag(:span, 'Si sono verificati i seguenti errori:')
115
+ concat errors_list
116
+ concat button_tag('', type: 'button', class: 'btn-close', data: { bs_dismiss: 'alert' }) unless options[:fixed]
117
+ end
118
+ end
119
+
120
+ def lato_form_item_label(form, key, label = nil, options = {})
121
+ options[:class] ||= []
122
+ options[:class].push('form-label')
123
+
124
+ form.label key, label, options
125
+ end
126
+
127
+ def lato_form_item_input_text(form, key, options = {})
128
+ _lato_form_input_options(form, key, options, :keyup, 'form-control')
129
+
130
+ form.text_field key, options
131
+ end
132
+
133
+ def lato_form_item_input_email(form, key, options = {})
134
+ _lato_form_input_options(form, key, options, :keyup, 'form-control')
135
+
136
+ form.email_field key, options
137
+ end
138
+
139
+ def lato_form_item_input_password(form, key, options = {})
140
+ _lato_form_input_options(form, key, options, :keyup, 'form-control')
141
+
142
+ form.password_field key, options
143
+ end
144
+
145
+ def lato_form_item_input_check(form, key, label, options = {})
146
+ _lato_form_input_options(form, key, options, :change, 'form-check-input')
147
+
148
+ # TO-DO: Trovare il modo di calcolare l'id dato da rails a check_input_tag e metterlo nell'attributo :for di check_label_tag
149
+
150
+ content_tag :div, class: 'form-check' do
151
+ concat form.check_box(key, options)
152
+ concat label_tag(key, raw(label), class: 'form-check-label')
153
+ end
154
+ end
155
+
156
+ def lato_form_submit(form, label, options = {})
157
+ options[:class] ||= []
158
+ options[:class].push('btn')
159
+ options[:class].push('btn-primary')
160
+
161
+ options[:data] ||= {}
162
+ options[:data][:lato_form_target] = 'submit'
163
+
164
+ form.submit label, options
165
+ end
166
+
167
+ # Data
168
+ ##
169
+
170
+ def lato_data_badge(label, color = 'primary')
171
+ content_tag :span, label, class: "badge rounded-pill bg-#{color}"
172
+ end
173
+
174
+ def lato_data_user(label, image_url = nil)
175
+ image_url ||= image_path('lato/user-150x150')
176
+
177
+ content_tag :div, class: 'd-flex align-items-center' do
178
+ concat content_tag :div, '', class: 'border border-2 rounded-circle me-2', style: "background-position: center; background-size: cover; background-repeat: no-repeat; background-image: url(#{image_url}); width: 30px; height: 30px;"
179
+ concat content_tag :small, label, class: 'text-black'
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,59 @@
1
+ module Lato
2
+ # ApplicationJob.
3
+ class ApplicationJob < ActiveJob::Base
4
+ around_perform :manage_operation
5
+
6
+ protected
7
+
8
+ def operation?
9
+ !!@operation
10
+ end
11
+
12
+ def update_operation_percentage(percentage)
13
+ return false unless operation?
14
+
15
+ @operation.update(
16
+ percentage: percentage
17
+ )
18
+
19
+ true
20
+ end
21
+
22
+ def save_operation_output_file(file_path)
23
+ return false unless operation?
24
+
25
+ file = File.open(file_path)
26
+ file_name = File.basename(file_path)
27
+ @operation.output_file.attach(
28
+ io: file,
29
+ filename: file_name
30
+ )
31
+
32
+ true
33
+ end
34
+
35
+ def save_operation_output_message(message)
36
+ return false unless operation?
37
+
38
+ @operation.update(
39
+ active_job_output: @operation.active_job_output.merge(_message: message)
40
+ )
41
+
42
+ true
43
+ end
44
+
45
+ private
46
+
47
+ def manage_operation
48
+ @operation = Lato::Operation.find(arguments.first[:_operation_id]) if arguments.first && arguments.first.is_a?(Hash) && !arguments.first[:_operation_id].blank?
49
+ @operation&.running
50
+
51
+ begin
52
+ yield
53
+ @operation&.completed
54
+ rescue StandardError => e
55
+ @operation&.failed(e.message)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,6 @@
1
+ module Lato
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: Lato.config.email_from
4
+ layout 'lato/mailer'
5
+ end
6
+ end
@@ -0,0 +1,23 @@
1
+ module Lato
2
+ class UserMailer < ApplicationMailer
3
+ def email_verification_mail(user_id, code)
4
+ @user = Lato::User.find(user_id)
5
+ @code = code
6
+ mail(
7
+ to: @user.email,
8
+ subject: 'Conferma il tuo indirizzo email',
9
+ template_path: 'lato/mailer/user'
10
+ )
11
+ end
12
+
13
+ def password_update_mail(user_id, code)
14
+ @user = Lato::User.find(user_id)
15
+ @code = code
16
+ mail(
17
+ to: @user.email,
18
+ subject: 'Imposta una nuova password',
19
+ template_path: 'lato/mailer/user'
20
+ )
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ module Lato
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,96 @@
1
+ module Lato
2
+ class Operation < ApplicationRecord
3
+ enum status: {
4
+ created: 0,
5
+ running: 1,
6
+ completed: 2,
7
+ failed: 3
8
+ }, _suffix: true
9
+
10
+ has_one_attached :input_file
11
+ has_one_attached :output_file
12
+
13
+ # Hooks
14
+ ##
15
+
16
+ before_create do
17
+ self.status = :created
18
+ self.active_job_input ||= {}
19
+ self.active_job_output ||= {}
20
+ end
21
+
22
+ # Questions
23
+ ##
24
+
25
+ def finished?
26
+ completed_status? || failed_status?
27
+ end
28
+
29
+ def output_message?
30
+ active_job_output && !active_job_output['_message'].blank?
31
+ end
32
+
33
+ def output_error?
34
+ active_job_output && !active_job_output['_error'].blank?
35
+ end
36
+
37
+ def output_file?
38
+ output_file.attached?
39
+ end
40
+
41
+ # Helpers
42
+ ##
43
+
44
+ def output_error
45
+ active_job_output['_error']
46
+ end
47
+
48
+ def output_message
49
+ active_job_output['_message']
50
+ end
51
+
52
+ # Operations
53
+
54
+ def start
55
+ begin
56
+ active_job_name.constantize.perform_later(active_job_input.merge(_operation_id: id))
57
+ rescue StandardError
58
+ errors.add(:base, 'Impossibile eseguire il job')
59
+ return false
60
+ end
61
+
62
+ true
63
+ end
64
+
65
+ def running
66
+ update(status: :running)
67
+ end
68
+
69
+ def failed(error = nil)
70
+ update(
71
+ status: :failed,
72
+ closed_at: Time.now,
73
+ active_job_output: error ? active_job_output.merge(_error: error) : active_job_output
74
+ )
75
+ end
76
+
77
+ def completed(message = nil)
78
+ update(
79
+ status: :completed,
80
+ closed_at: Time.now,
81
+ active_job_output: message ? active_job_output.merge(_message: message) : active_job_output
82
+ )
83
+ end
84
+
85
+ # Class
86
+ ##
87
+
88
+ def self.generate(active_job_name, active_job_input = {}, user_id = nil)
89
+ Operation.create(
90
+ active_job_name: active_job_name,
91
+ active_job_input: active_job_input,
92
+ lato_user_id: user_id
93
+ )
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,36 @@
1
+ module Lato
2
+ class Session
3
+ def initialize(session)
4
+ @session = session
5
+ end
6
+
7
+ # Questions
8
+ ##
9
+
10
+ def valid?
11
+ !@session.blank?
12
+ end
13
+
14
+ def really_valid?
15
+ !!user
16
+ end
17
+
18
+ # Helpers
19
+ ##
20
+
21
+ def user
22
+ @user ||= Lato::User.find_by(id: @session)
23
+ end
24
+
25
+ def user_id
26
+ @session
27
+ end
28
+
29
+ # Class
30
+ ##
31
+
32
+ def self.generate_session_per_user(user_id)
33
+ user_id
34
+ end
35
+ end
36
+ end