lato 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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