nquery 0.1.0 → 0.1.2

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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -0
  3. data/README.md +263 -24
  4. data/app/assets/builds/nquery/application.css +61 -4
  5. data/app/assets/builds/nquery/application.js +107 -12
  6. data/app/controllers/concerns/nquery/breadcrumbs.rb +5 -3
  7. data/app/controllers/concerns/nquery/chart_actions.rb +1 -0
  8. data/app/controllers/concerns/nquery/chart_results.rb +17 -3
  9. data/app/controllers/nquery/admin/data_sources_controller.rb +49 -2
  10. data/app/controllers/nquery/admin/groups_controller.rb +8 -2
  11. data/app/controllers/nquery/admin/users_controller.rb +6 -1
  12. data/app/controllers/nquery/application_controller.rb +45 -28
  13. data/app/controllers/nquery/dashboards_controller.rb +30 -0
  14. data/app/controllers/nquery/embed/charts_controller.rb +1 -0
  15. data/app/controllers/nquery/embed/dashboards_controller.rb +1 -0
  16. data/app/controllers/nquery/errors_controller.rb +11 -0
  17. data/app/controllers/nquery/home_controller.rb +1 -0
  18. data/app/controllers/nquery/onboarding/admins_controller.rb +42 -0
  19. data/app/controllers/nquery/onboarding/base_controller.rb +27 -0
  20. data/app/controllers/nquery/onboarding/companies_controller.rb +30 -0
  21. data/app/controllers/nquery/onboarding/confirmations_controller.rb +48 -0
  22. data/app/controllers/nquery/onboarding/congrats_controller.rb +15 -0
  23. data/app/controllers/nquery/queries_controller.rb +0 -1
  24. data/app/controllers/nquery/sessions_controller.rb +14 -17
  25. data/app/helpers/nquery/data_sources_helper.rb +13 -0
  26. data/app/helpers/nquery/groups_helper.rb +19 -0
  27. data/app/javascript/nquery/controllers/data_source_form_controller.js +25 -0
  28. data/app/mailers/nquery/application_mailer.rb +7 -0
  29. data/app/mailers/nquery/devise_mailer.rb +9 -0
  30. data/app/models/nquery/audit.rb +2 -2
  31. data/app/models/nquery/collection.rb +1 -1
  32. data/app/models/nquery/dashboard.rb +1 -1
  33. data/app/models/nquery/data_source.rb +139 -4
  34. data/app/models/nquery/organization.rb +38 -0
  35. data/app/models/nquery/query.rb +8 -0
  36. data/app/models/nquery/user.rb +45 -10
  37. data/app/views/nquery/admin/data_sources/_form.html.erb +121 -0
  38. data/app/views/nquery/admin/data_sources/edit.html.erb +6 -8
  39. data/app/views/nquery/admin/data_sources/new.html.erb +6 -7
  40. data/app/views/nquery/admin/groups/_form.html.erb +47 -0
  41. data/app/views/nquery/admin/groups/edit.html.erb +15 -6
  42. data/app/views/nquery/admin/groups/index.html.erb +25 -20
  43. data/app/views/nquery/admin/groups/new.html.erb +6 -6
  44. data/app/views/nquery/admin/groups/show.html.erb +98 -19
  45. data/app/views/nquery/admin/logs/index.html.erb +26 -30
  46. data/app/views/nquery/charts/_builder_form.html.erb +6 -4
  47. data/app/views/nquery/charts/edit.html.erb +2 -1
  48. data/app/views/nquery/dashboards/index.html.erb +6 -0
  49. data/app/views/nquery/dashboards/new.html.erb +34 -0
  50. data/app/views/nquery/devise_mailer/confirmation_instructions.html.erb +5 -0
  51. data/app/views/nquery/devise_mailer/confirmation_instructions.text.erb +5 -0
  52. data/app/views/nquery/errors/internal_server_error.html.erb +10 -0
  53. data/app/views/nquery/errors/not_found.html.erb +10 -0
  54. data/app/views/nquery/home/index.html.erb +7 -15
  55. data/app/views/nquery/onboarding/admins/new.html.erb +21 -0
  56. data/app/views/nquery/onboarding/companies/new.html.erb +25 -0
  57. data/app/views/nquery/onboarding/confirmations/show.html.erb +17 -0
  58. data/app/views/nquery/onboarding/congrats/show.html.erb +11 -0
  59. data/app/views/nquery/sessions/new.html.erb +8 -9
  60. data/app/views/nquery/shared/_auth_shell.html.erb +1 -1
  61. data/app/views/nquery/shared/_topbar.html.erb +0 -4
  62. data/config/routes.rb +26 -7
  63. data/db/migrate/20260721000001_create_nquery_tables.rb +1 -1
  64. data/db/migrate/20260722000001_add_key_to_nquery_data_sources.rb +8 -0
  65. data/db/migrate/20260722000002_create_nquery_organizations.rb +11 -0
  66. data/db/migrate/20260722000003_add_devise_to_nquery_users.rb +35 -0
  67. data/db/migrate/20260722000004_add_onboarding_completed_at_to_nquery_organizations.rb +7 -0
  68. data/lib/generators/nquery/install_generator.rb +9 -17
  69. data/lib/nquery/configuration.rb +7 -23
  70. data/lib/nquery/csv_importer.rb +3 -3
  71. data/lib/nquery/data_sources/adapter.rb +4 -0
  72. data/lib/nquery/data_sources/postgresql_adapter.rb +15 -1
  73. data/lib/nquery/data_sources/rails_adapter.rb +5 -0
  74. data/lib/nquery/data_sources/syncer.rb +50 -0
  75. data/lib/nquery/encryption.rb +50 -0
  76. data/lib/nquery/engine.rb +42 -3
  77. data/lib/nquery/onboarding/admin_provisioner.rb +40 -0
  78. data/lib/nquery/onboarding/password_confirmation.rb +46 -0
  79. data/lib/nquery/onboarding.rb +40 -0
  80. data/lib/nquery/permissions/resolver.rb +14 -4
  81. data/lib/nquery/query_runner.rb +5 -9
  82. data/lib/nquery/readonly_sql.rb +25 -0
  83. data/lib/nquery/seeder.rb +25 -34
  84. data/lib/nquery/setup.rb +75 -0
  85. data/lib/nquery/version.rb +1 -1
  86. data/lib/nquery.rb +9 -0
  87. data/lib/tasks/nquery.rake +6 -0
  88. data/nquery.gemspec +11 -5
  89. metadata +135 -26
  90. data/app/controllers/nquery/registrations_controller.rb +0 -31
  91. data/app/views/nquery/registrations/new.html.erb +0 -34
@@ -9,14 +9,28 @@ module Nquery
9
9
  def chart_result(chart)
10
10
  return demo_result unless chart.query&.statement.present?
11
11
 
12
+ chart_query_result(chart)
13
+ rescue StandardError
14
+ demo_result
15
+ end
16
+
17
+ def chart_builder_result(chart)
18
+ return nil unless chart.query&.statement.present?
19
+
20
+ chart_query_result(chart, audit: false)
21
+ rescue QueryRunner::PermissionError, QueryRunner::Error => e
22
+ { error: e.message }
23
+ rescue StandardError => e
24
+ { error: e.message }
25
+ end
26
+
27
+ def chart_query_result(chart, audit: true)
12
28
  QueryRunner.new(
13
29
  data_source: chart.query.data_source || DataSource.first,
14
30
  statement: chart.query.statement,
15
31
  user: current_nquery_user,
16
32
  query: chart.query
17
- ).run
18
- rescue StandardError
19
- demo_result
33
+ ).run(audit: audit)
20
34
  end
21
35
 
22
36
  def demo_result
@@ -15,6 +15,8 @@ module Nquery
15
15
 
16
16
  def create
17
17
  @data_source = DataSource.new(data_source_params)
18
+ @data_source.connection_fields_submitted = connection_fields_required?(@data_source.adapter)
19
+
18
20
  if @data_source.save
19
21
  redirect_to admin_data_sources_path, notice: "Data source created."
20
22
  else
@@ -23,24 +25,69 @@ module Nquery
23
25
  end
24
26
 
25
27
  def edit
28
+ @data_source.assign_connection_fields_from_config
26
29
  end
27
30
 
28
31
  def update
29
- if @data_source.update(data_source_params)
32
+ @data_source.assign_attributes(data_source_params)
33
+ @data_source.connection_fields_submitted = connection_fields_required?(@data_source.adapter)
34
+
35
+ if @data_source.save
30
36
  redirect_to admin_data_sources_path, notice: "Data source updated."
31
37
  else
38
+ @data_source.assign_connection_fields_from_config unless @data_source.connection_fields_submitted?
32
39
  render :edit, status: :unprocessable_content
33
40
  end
34
41
  end
35
42
 
43
+ def test_connection
44
+ @data_source = data_source_for_connection_test
45
+ unless @data_source
46
+ render json: { ok: false, error: "Data source not found." }, status: :not_found
47
+ return
48
+ end
49
+
50
+ @data_source.assign_attributes(data_source_params)
51
+ @data_source.connection_fields_submitted = connection_fields_required?(@data_source.adapter)
52
+
53
+ if @data_source.test_connection
54
+ render json: { ok: true, message: "Connection successful." }
55
+ else
56
+ render json: { ok: false, error: @data_source.errors.full_messages.to_sentence },
57
+ status: :unprocessable_content
58
+ end
59
+ end
60
+
36
61
  private
37
62
 
38
63
  def set_data_source
39
64
  @data_source = DataSource.find(params[:id])
40
65
  end
41
66
 
67
+ def data_source_for_connection_test
68
+ id = params[:data_source_id].presence
69
+ return DataSource.new if id.blank?
70
+
71
+ DataSource.find_by(id: id)
72
+ end
73
+
42
74
  def data_source_params
43
- params.require(:data_source).permit(:name, :adapter, :active, :connection_config)
75
+ params.require(:data_source).permit(
76
+ :name,
77
+ :adapter,
78
+ :active,
79
+ :host,
80
+ :port,
81
+ :database,
82
+ :username,
83
+ :password,
84
+ :database_path,
85
+ :sslmode
86
+ )
87
+ end
88
+
89
+ def connection_fields_required?(adapter)
90
+ DataSource::REMOTE_ADAPTERS.include?(adapter) || adapter == "sqlite"
44
91
  end
45
92
  end
46
93
  end
@@ -10,7 +10,8 @@ module Nquery
10
10
  end
11
11
 
12
12
  def show
13
- @available_users = User.active.where.not(id: @group.user_ids).order(:email)
13
+ @members = @group.users.order(:first_name, :last_name, :email)
14
+ @available_users = User.active.where.not(id: @group.user_ids).order(:first_name, :last_name, :email)
14
15
  end
15
16
 
16
17
  def new
@@ -44,8 +45,13 @@ module Nquery
44
45
  end
45
46
 
46
47
  def remove_member
48
+ if @group.system_group == "all_users"
49
+ redirect_to admin_group_path(@group), alert: "Members cannot be removed from the All Users group."
50
+ return
51
+ end
52
+
47
53
  membership = @group.group_memberships.find_by!(user_id: params[:user_id])
48
- membership.destroy unless @group.system_group == "all_users"
54
+ membership.destroy!
49
55
  redirect_to admin_group_path(@group), notice: "Member removed."
50
56
  end
51
57
 
@@ -23,7 +23,12 @@ module Nquery
23
23
  assign_groups(@user)
24
24
  @user.ensure_all_users_membership!
25
25
  @user.ensure_personal_collection!
26
- redirect_to admin_users_path, notice: "User created."
26
+ if @user.last_devise_notification_delivered == false
27
+ redirect_to admin_users_path,
28
+ alert: "User invited, but the confirmation email could not be sent. Check your mailer settings."
29
+ else
30
+ redirect_to admin_users_path, notice: "User invited. A confirmation email was sent."
31
+ end
27
32
  else
28
33
  @groups = Group.order(:name)
29
34
  render :new, status: :unprocessable_content
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Nquery
4
4
  class ApplicationController < ActionController::Base
5
+ include Devise::Controllers::Helpers
5
6
  include Nquery::AuthorizesCollection
6
7
  include Nquery::Breadcrumbs
7
8
  include Nquery::Engine.routes.url_helpers
@@ -9,50 +10,66 @@ module Nquery
9
10
  protect_from_forgery with: :exception
10
11
  layout "nquery/application"
11
12
 
12
- helper_method :current_nquery_user, :permission_resolver
13
+ helper_method :current_nquery_user, :permission_resolver, :login_path, :logout_path
13
14
 
14
- before_action :_authenticate!
15
+ before_action :redirect_to_onboarding, unless: :skip_onboarding_redirect?
16
+ before_action :authenticate_nquery_user!, unless: :auth_exempt?
15
17
  before_action :set_current_user_context
16
18
 
19
+ rescue_from StandardError, with: :render_internal_server_error
20
+ rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
21
+
17
22
  private
18
23
 
19
- def _authenticate!
20
- if Nquery.configuration.authenticate
21
- instance_eval(&Nquery.configuration.authenticate)
22
- else
23
- default_authenticate!
24
- end
25
- end
24
+ def redirect_to_onboarding
25
+ return if Onboarding.complete?
26
26
 
27
- def default_authenticate!
28
- return if current_nquery_user
29
- return if auth_exempt?
27
+ redirect_to new_onboarding_company_path
28
+ end
30
29
 
31
- redirect_to login_path, alert: "Please sign in to continue."
30
+ def skip_onboarding_redirect?
31
+ auth_exempt? || Onboarding.complete?
32
32
  end
33
33
 
34
34
  def auth_exempt?
35
- controller_path.in?(%w[nquery/sessions nquery/registrations nquery/embed/charts nquery/embed/dashboards])
35
+ controller_path.in?(%w[
36
+ nquery/sessions
37
+ nquery/onboarding/companies
38
+ nquery/onboarding/admins
39
+ nquery/onboarding/congrats
40
+ nquery/onboarding/confirmations
41
+ nquery/embed/charts
42
+ nquery/embed/dashboards
43
+ nquery/errors
44
+ ])
36
45
  end
37
46
 
38
- def current_nquery_user
39
- @current_nquery_user ||= resolve_current_user
47
+ def render_not_found(_error = nil)
48
+ render template: "nquery/errors/not_found", layout: "nquery/auth", status: :not_found
40
49
  end
41
50
 
42
- def resolve_current_user
43
- if session[:nquery_user_id]
44
- return User.active.find_by(id: session[:nquery_user_id])
45
- end
51
+ def render_internal_server_error(error)
52
+ raise error if show_detailed_exceptions?
46
53
 
47
- if Nquery.configuration.authentication_mode.in?(%i[sso hybrid])
48
- host_method = Nquery.configuration.current_user_method
49
- host_user = host_method.is_a?(Symbol) ? send(host_method) : instance_eval(&host_method)
50
- if host_user && Nquery.configuration.resolve_user
51
- return instance_eval { Nquery.configuration.resolve_user.call(host_user) }
52
- end
53
- end
54
+ Rails.logger.error("[nquery] #{error.class}: #{error.message}\n#{error.backtrace&.first(15)&.join("\n")}")
55
+ render template: "nquery/errors/internal_server_error", layout: "nquery/auth", status: :internal_server_error
56
+ end
57
+
58
+ def show_detailed_exceptions?
59
+ Rails.application.config.consider_all_requests_local
60
+ end
61
+
62
+
63
+ def login_path(**options)
64
+ new_nquery_user_session_path(**options)
65
+ end
66
+
67
+ def logout_path(**options)
68
+ destroy_nquery_user_session_path(**options)
69
+ end
54
70
 
55
- nil
71
+ def sign_in_nquery_user(user)
72
+ sign_in(:nquery_user, user)
56
73
  end
57
74
 
58
75
  def set_current_user_context
@@ -11,6 +11,29 @@ module Nquery
11
11
 
12
12
  def index
13
13
  @dashboards = filter_viewable_dashboards(Dashboard.active.includes(:collection).order(:name))
14
+ @collections = assignable_collections
15
+ end
16
+
17
+ def new
18
+ @collections = assignable_collections
19
+ if @collections.empty?
20
+ redirect_to dashboards_path, alert: "You do not have permission to create a dashboard."
21
+ return
22
+ end
23
+ @dashboard = Dashboard.new(collection: default_dashboard_collection)
24
+ end
25
+
26
+ def create
27
+ @collections = assignable_collections
28
+ @dashboard = Dashboard.new(dashboard_params.merge(creator: current_nquery_user))
29
+ authorize_collection_access!(@dashboard.collection, required: :curate) if @dashboard.collection
30
+ return if performed?
31
+
32
+ if @dashboard.save
33
+ redirect_to dashboard_path(@dashboard), notice: "Dashboard created."
34
+ else
35
+ render :new, status: :unprocessable_content
36
+ end
14
37
  end
15
38
 
16
39
  def show
@@ -86,5 +109,12 @@ module Nquery
86
109
  def dashboard_params
87
110
  params.require(:dashboard).permit(:name, :description, :collection_id, settings: {})
88
111
  end
112
+
113
+ def default_dashboard_collection
114
+ root = Collection.roots.first
115
+ return root if root && @collections.include?(root)
116
+
117
+ @collections.first
118
+ end
89
119
  end
90
120
  end
@@ -4,6 +4,7 @@ module Nquery
4
4
  module Embed
5
5
  class ChartsController < ActionController::Base
6
6
  include Nquery::Engine.routes.url_helpers
7
+ protect_from_forgery with: :exception
7
8
  layout "nquery/embed"
8
9
 
9
10
  def show
@@ -4,6 +4,7 @@ module Nquery
4
4
  module Embed
5
5
  class DashboardsController < ActionController::Base
6
6
  include Nquery::Engine.routes.url_helpers
7
+ protect_from_forgery with: :exception
7
8
  layout "nquery/embed"
8
9
 
9
10
  def show
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nquery
4
+ class ErrorsController < ApplicationController
5
+ layout "nquery/auth"
6
+
7
+ def not_found
8
+ render status: :not_found
9
+ end
10
+ end
11
+ end
@@ -11,6 +11,7 @@ module Nquery
11
11
  @recent_charts = filter_viewable_charts(charts).first(6)
12
12
  @recent_dashboards = filter_viewable_dashboards(dashboards).first(6)
13
13
  @root_collection = Collection.roots.first
14
+ @collections = assignable_collections
14
15
  end
15
16
  end
16
17
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nquery
4
+ module Onboarding
5
+ class AdminsController < BaseController
6
+ before_action :redirect_if_pending_confirmation!, only: %i[new create]
7
+ before_action :require_organization!
8
+
9
+ def new
10
+ @user = User.new
11
+ end
12
+
13
+ def create
14
+ @user = User.new(admin_params)
15
+ if @user.valid?
16
+ result = Onboarding::AdminProvisioner.call(@user)
17
+ session[:onboarding_admin_email] = result.user.email
18
+ if result.mail_delivered?
19
+ redirect_to onboarding_congrats_path
20
+ else
21
+ redirect_to onboarding_congrats_path,
22
+ alert: "Your admin account was created, but the confirmation email could not be sent. Check your mailer settings."
23
+ end
24
+ else
25
+ render :new, status: :unprocessable_content
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def require_organization!
32
+ return if session[:onboarding_organization_id].present? && Organization.exists?(id: session[:onboarding_organization_id])
33
+
34
+ redirect_to new_onboarding_company_path, alert: "Please set up your company first."
35
+ end
36
+
37
+ def admin_params
38
+ params.require(:user).permit(:email, :first_name, :last_name)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nquery
4
+ module Onboarding
5
+ class BaseController < ApplicationController
6
+ skip_before_action :authenticate_nquery_user!
7
+ skip_before_action :redirect_to_onboarding
8
+ before_action :ensure_onboarding_available!
9
+
10
+ layout "nquery/auth"
11
+
12
+ private
13
+
14
+ def ensure_onboarding_available!
15
+ return unless Onboarding.complete?
16
+
17
+ redirect_to login_path, alert: "Onboarding has already been completed."
18
+ end
19
+
20
+ def redirect_if_pending_confirmation!
21
+ return unless Onboarding.pending_admin_confirmation?
22
+
23
+ redirect_to onboarding_congrats_path, alert: "Please confirm your email to continue setup."
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nquery
4
+ module Onboarding
5
+ class CompaniesController < BaseController
6
+ before_action :redirect_if_pending_confirmation!, only: %i[new create]
7
+
8
+ def new
9
+ @organization = Organization.new
10
+ end
11
+
12
+ def create
13
+ @organization = Organization.first_or_initialize
14
+ @organization.assign_attributes(organization_params)
15
+ if @organization.save
16
+ session[:onboarding_organization_id] = @organization.id
17
+ redirect_to new_onboarding_admin_path
18
+ else
19
+ render :new, status: :unprocessable_content
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def organization_params
26
+ params.require(:organization).permit(:name, :website, :logo, :cover_image)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nquery
4
+ module Onboarding
5
+ class ConfirmationsController < BaseController
6
+ skip_before_action :ensure_onboarding_available!
7
+
8
+ def show
9
+ @user = find_confirmable_user
10
+ redirect_to login_path, alert: "Invalid confirmation link." unless @user
11
+ end
12
+
13
+ def update
14
+ @user = find_confirmable_user
15
+ return redirect_to login_path, alert: "Invalid confirmation link." unless @user
16
+
17
+ result = Onboarding::PasswordConfirmation.call(
18
+ user: @user,
19
+ password: confirmation_params[:password],
20
+ password_confirmation: confirmation_params[:password_confirmation]
21
+ )
22
+
23
+ if result.success?
24
+ sign_in_nquery_user(result.user)
25
+ redirect_to root_path, notice: "Your account is ready. Welcome to nquery!"
26
+ else
27
+ render :show, status: :unprocessable_content
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def find_confirmable_user
34
+ token = params[:confirmation_token].to_s
35
+ return if token.blank?
36
+
37
+ user = User.find_by(confirmation_token: token, confirmed_at: nil)
38
+ return if user.nil? || user.confirmation_expired?
39
+
40
+ user
41
+ end
42
+
43
+ def confirmation_params
44
+ params.require(:user).permit(:password, :password_confirmation)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nquery
4
+ module Onboarding
5
+ class CongratsController < BaseController
6
+ def show
7
+ @email = session[:onboarding_admin_email]
8
+ return if @email.present?
9
+ return if Onboarding.pending_admin_confirmation?
10
+
11
+ redirect_to new_onboarding_company_path, alert: "Please complete onboarding."
12
+ end
13
+ end
14
+ end
15
+ end
@@ -2,7 +2,6 @@
2
2
 
3
3
  module Nquery
4
4
  class QueriesController < ApplicationController
5
- skip_before_action :_authenticate!, only: [] # always auth
6
5
  before_action :set_query, only: %i[show edit update]
7
6
  before_action :authorize_query_collection!, only: %i[show edit update]
8
7
  before_action :set_data_source_for_run, only: %i[run schema]
@@ -1,28 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Nquery
4
- class SessionsController < ApplicationController
5
- skip_before_action :_authenticate!
4
+ # Inherits Devise::SessionsController (→ host ApplicationController via Devise's
5
+ # global parent_controller). Intentionally does NOT inherit Nquery::ApplicationController
6
+ # so engine auth/onboarding callbacks are not applied to the sign-in flow.
7
+ class SessionsController < Devise::SessionsController
8
+ include Nquery::Engine.routes.url_helpers
9
+
10
+ helper Nquery::ComponentsHelper
11
+ helper Nquery::IconHelper
12
+
6
13
  layout "nquery/auth"
7
14
 
8
- def new
9
- redirect_to root_path if current_nquery_user
10
- end
15
+ private
11
16
 
12
- def create
13
- user = User.active.find_by(email: params[:email]&.downcase)
14
- if user&.authenticate(params[:password])
15
- session[:nquery_user_id] = user.id
16
- redirect_to root_path, notice: "Signed in successfully."
17
- else
18
- flash.now[:alert] = "Invalid email or password."
19
- render :new, status: :unprocessable_content
20
- end
17
+ def after_sign_in_path_for(_resource)
18
+ stored_location_for(:nquery_user) || root_path
21
19
  end
22
20
 
23
- def destroy
24
- session.delete(:nquery_user_id)
25
- redirect_to login_path, notice: "Signed out."
21
+ def after_sign_out_path_for(_resource_or_scope)
22
+ new_nquery_user_session_path
26
23
  end
27
24
  end
28
25
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nquery
4
+ module DataSourcesHelper
5
+ def data_source_form_url(data_source)
6
+ data_source.persisted? ? admin_data_source_path(data_source) : admin_data_sources_path
7
+ end
8
+
9
+ def data_source_form_method(data_source)
10
+ data_source.persisted? ? :patch : :post
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nquery
4
+ module GroupsHelper
5
+ GROUP_TYPE_LABELS = {
6
+ "administrators" => "Administrators",
7
+ "all_users" => "All users",
8
+ "custom" => "Custom"
9
+ }.freeze
10
+
11
+ def group_type_label(group)
12
+ GROUP_TYPE_LABELS.fetch(group.system_group, group.system_group.humanize)
13
+ end
14
+
15
+ def group_type_badge_class(group)
16
+ group.system? ? "nq-badge-system" : "nq-badge-custom"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["adapter", "remoteFields", "sqliteFields", "railsHint", "port", "testButton", "testStatus"]
5
+ static values = { adapter: String, testUrl: String, id: String }
6
+
7
+ connect() {
8
+ this.toggle()
9
+ }
10
+
11
+ toggle() {
12
+ const adapter = this.adapterTarget?.value || this.element.dataset.dataSourceFormAdapterValue || "postgresql"
13
+ const isRemote = adapter === "postgresql" || adapter === "mysql"
14
+ const isSqlite = adapter === "sqlite"
15
+ const isRails = adapter === "rails"
16
+
17
+ this.remoteFieldsTarget?.toggleAttribute("hidden", !isRemote)
18
+ this.sqliteFieldsTarget?.toggleAttribute("hidden", !isSqlite)
19
+ this.railsHintTarget?.toggleAttribute("hidden", !isRails)
20
+
21
+ if (this.hasPortTarget) {
22
+ this.portTarget.placeholder = adapter === "mysql" ? "3306" : "5432"
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nquery
4
+ class ApplicationMailer < ActionMailer::Base
5
+ default from: -> { Nquery.configuration.mailer_sender || "noreply@example.com" }
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nquery
4
+ class DeviseMailer < Devise::Mailer
5
+ include Nquery::Engine.routes.url_helpers
6
+
7
+ default template_path: "nquery/devise_mailer"
8
+ end
9
+ end
@@ -11,9 +11,9 @@ module Nquery
11
11
  pattern = "%#{sanitize_sql_like(term.downcase)}%"
12
12
  full_name_sql = if connection.adapter_name.downcase.include?("mysql")
13
13
  "CONCAT(nquery_users.first_name, ' ', nquery_users.last_name)"
14
- else
14
+ else
15
15
  "nquery_users.first_name || ' ' || nquery_users.last_name"
16
- end
16
+ end
17
17
 
18
18
  joins(:user).where(
19
19
  "LOWER(nquery_users.email) LIKE :q OR LOWER(#{full_name_sql}) LIKE :q",
@@ -10,7 +10,7 @@ module Nquery
10
10
  has_many :collection_permissions, class_name: "Nquery::CollectionPermission", dependent: :destroy
11
11
  has_many :queries, class_name: "Nquery::Query", dependent: :nullify
12
12
  has_many :charts, class_name: "Nquery::Chart", dependent: :nullify
13
- has_many :dashboards, class_name: "Nquery::Dashboard", dependent: :nullify
13
+ has_many :dashboards, class_name: "Nquery::Dashboard", dependent: :destroy
14
14
 
15
15
  validates :name, presence: true
16
16
  validates :kind, inclusion: { in: KINDS }
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Nquery
4
4
  class Dashboard < ApplicationRecord
5
- belongs_to :collection, class_name: "Nquery::Collection", optional: true
5
+ belongs_to :collection, class_name: "Nquery::Collection"
6
6
  belongs_to :creator, class_name: "Nquery::User", optional: true
7
7
  has_many :dashboard_cards, class_name: "Nquery::DashboardCard", dependent: :destroy
8
8
  has_many :charts, through: :dashboard_cards, class_name: "Nquery::Chart"