develoz-rails 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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +31 -0
- data/.github/workflows/release.yml +459 -0
- data/.rubocop.yml +52 -0
- data/.sisyphus/notepads/develoz-rails-template/learnings.md +855 -0
- data/.tool-versions +2 -0
- data/CHANGELOG.md +28 -0
- data/CONTRIBUTING.md +120 -0
- data/README.md +76 -0
- data/Rakefile +18 -0
- data/config/generators.yml +105 -0
- data/docs/guide/installation.md +574 -0
- data/exe/develoz +6 -0
- data/install +98 -0
- data/lib/develoz/canonical_fetcher.rb +56 -0
- data/lib/develoz/cli.rb +84 -0
- data/lib/develoz/generators/base.rb +134 -0
- data/lib/develoz/manifest.rb +44 -0
- data/lib/develoz/options.rb +55 -0
- data/lib/develoz/version.rb +5 -0
- data/lib/develoz/version_resolver.rb +103 -0
- data/lib/develoz.rb +15 -0
- data/lib/generators/develoz/active_resource/active_resource_generator.rb +25 -0
- data/lib/generators/develoz/active_resource/templates/application_resource.rb.tt +7 -0
- data/lib/generators/develoz/active_resource/templates/example_resource.rb.tt +5 -0
- data/lib/generators/develoz/admin/admin_generator.rb +44 -0
- data/lib/generators/develoz/admin/templates/admin_base_controller.rb.tt +7 -0
- data/lib/generators/develoz/admin/templates/admin_layout.html.erb.tt +38 -0
- data/lib/generators/develoz/admin/templates/dashboard_controller.rb.tt +7 -0
- data/lib/generators/develoz/admin/templates/dashboard_index.html.erb.tt +2 -0
- data/lib/generators/develoz/admin/templates/routes.rb.tt +3 -0
- data/lib/generators/develoz/agents_docs/agents_docs_generator.rb +50 -0
- data/lib/generators/develoz/agents_docs/templates/.github/pull_request_template.md.tt +13 -0
- data/lib/generators/develoz/agents_docs/templates/AGENTS.md.tt +36 -0
- data/lib/generators/develoz/agents_docs/templates/docs/development.md.tt +121 -0
- data/lib/generators/develoz/agents_docs/templates/docs/performance.md.tt +46 -0
- data/lib/generators/develoz/agents_docs/templates/docs/testing.md.tt +92 -0
- data/lib/generators/develoz/agents_docs/templates/spec/cassettes/Example_API/fetches_data_from_an_external_API.yml.tt +23 -0
- data/lib/generators/develoz/agents_docs/templates/spec/requests/example_api_spec.rb.tt +14 -0
- data/lib/generators/develoz/agents_docs/templates/spec/support/faraday.rb.tt +19 -0
- data/lib/generators/develoz/agents_docs/templates/spec/support/vcr.rb.tt +11 -0
- data/lib/generators/develoz/api/api_generator.rb +55 -0
- data/lib/generators/develoz/api/templates/app/blueprints/example_blueprint.rb.tt +6 -0
- data/lib/generators/develoz/api/templates/app/controllers/api/v1/base_controller.rb.tt +96 -0
- data/lib/generators/develoz/api/templates/config/initializers/blueprinter.rb.tt +16 -0
- data/lib/generators/develoz/api/templates/config/initializers/rswag_api.rb.tt +15 -0
- data/lib/generators/develoz/api/templates/config/initializers/rswag_ui.rb.tt +17 -0
- data/lib/generators/develoz/api/templates/spec/requests/api/v1/examples_spec.rb.tt +25 -0
- data/lib/generators/develoz/auth/auth_generator.rb +82 -0
- data/lib/generators/develoz/auth/templates/app/controllers/concerns/authentication.rb.tt +53 -0
- data/lib/generators/develoz/auth/templates/app/controllers/passwords_controller.rb.tt +35 -0
- data/lib/generators/develoz/auth/templates/app/controllers/sessions_controller.rb.tt +22 -0
- data/lib/generators/develoz/auth/templates/app/mailers/passwords_mailer.rb.tt +8 -0
- data/lib/generators/develoz/auth/templates/app/models/current.rb.tt +5 -0
- data/lib/generators/develoz/auth/templates/app/models/user.rb.tt +7 -0
- data/lib/generators/develoz/auth/templates/app/views/passwords/edit.html.erb.tt +9 -0
- data/lib/generators/develoz/auth/templates/app/views/passwords_mailer/reset.html.erb.tt +6 -0
- data/lib/generators/develoz/auth/templates/app/views/sessions/new.html.erb.tt +11 -0
- data/lib/generators/develoz/auth/templates/db/migrate/create_users.rb.tt +13 -0
- data/lib/generators/develoz/auth/templates/spec/requests/passwords_spec.rb.tt +60 -0
- data/lib/generators/develoz/auth/templates/spec/requests/sessions_spec.rb.tt +37 -0
- data/lib/generators/develoz/ci/ci_generator.rb +66 -0
- data/lib/generators/develoz/ci/templates/.github/workflows/ci.yml.tt +18 -0
- data/lib/generators/develoz/ci/templates/.haml-lint.yml.tt +12 -0
- data/lib/generators/develoz/ci/templates/.markdownlint.json.tt +10 -0
- data/lib/generators/develoz/ci/templates/.reek.yml.tt +28 -0
- data/lib/generators/develoz/ci/templates/.rubocop.yml.tt +22 -0
- data/lib/generators/develoz/ci/templates/.stylelintrc.json.tt +4 -0
- data/lib/generators/develoz/ci/templates/.yamllint.tt +14 -0
- data/lib/generators/develoz/ci/templates/Rakefile.tt +73 -0
- data/lib/generators/develoz/ci/templates/bin/ci.tt +6 -0
- data/lib/generators/develoz/ci/templates/biome.json.tt +41 -0
- data/lib/generators/develoz/ci/templates/config/ci.rb.tt +23 -0
- data/lib/generators/develoz/concerns/concerns_generator.rb +48 -0
- data/lib/generators/develoz/concerns/templates/app/models/concerns/configurable.rb.tt +120 -0
- data/lib/generators/develoz/concerns/templates/app/models/concerns/optimized_finders.rb.tt +93 -0
- data/lib/generators/develoz/concerns/templates/app/models/concerns/searchable_concern.rb.tt +31 -0
- data/lib/generators/develoz/concerns/templates/app/models/concerns/transitionable.rb.tt +144 -0
- data/lib/generators/develoz/concerns/templates/app/models/configuration.rb.tt +104 -0
- data/lib/generators/develoz/concerns/templates/db/migrate/add_status_transitions.rb.tt +30 -0
- data/lib/generators/develoz/concerns/templates/db/migrate/create_configurations.rb.tt +25 -0
- data/lib/generators/develoz/concerns/templates/spec/models/concerns/configurable_spec.rb.tt +172 -0
- data/lib/generators/develoz/concerns/templates/spec/models/concerns/optimized_finders_spec.rb.tt +200 -0
- data/lib/generators/develoz/concerns/templates/spec/models/concerns/searchable_concern_spec.rb.tt +84 -0
- data/lib/generators/develoz/concerns/templates/spec/models/concerns/transitionable_spec.rb.tt +234 -0
- data/lib/generators/develoz/concerns/templates/spec/models/configuration_spec.rb.tt +223 -0
- data/lib/generators/develoz/database/database_generator.rb +44 -0
- data/lib/generators/develoz/database/templates/config/database.yml.tt +53 -0
- data/lib/generators/develoz/database/templates/config/initializers/pg_search.rb.tt +5 -0
- data/lib/generators/develoz/database/templates/db/migrate/create_pg_extensions.rb.tt +13 -0
- data/lib/generators/develoz/db_backup/db_backup_generator.rb +47 -0
- data/lib/generators/develoz/db_backup/templates/backup_rake.tt +48 -0
- data/lib/generators/develoz/db_backup/templates/bin_db_backup.tt +25 -0
- data/lib/generators/develoz/db_backup/templates/compose_service.tt +14 -0
- data/lib/generators/develoz/doc_specs/doc_specs_generator.rb +33 -0
- data/lib/generators/develoz/doc_specs/templates/bin/generate-docs.tt +392 -0
- data/lib/generators/develoz/doc_specs/templates/lib/tasks/docs_check.rake.tt +8 -0
- data/lib/generators/develoz/doc_specs/templates/spec/support/doc_screenshot_helper.rb.tt +17 -0
- data/lib/generators/develoz/doc_specs/templates/spec/system/example_doc_spec.rb.tt +38 -0
- data/lib/generators/develoz/docker/docker_generator.rb +54 -0
- data/lib/generators/develoz/docker/templates/bin_dev.tt +5 -0
- data/lib/generators/develoz/docker/templates/bin_docker_entrypoint.tt +8 -0
- data/lib/generators/develoz/docker/templates/bin_run.tt +152 -0
- data/lib/generators/develoz/docker/templates/bin_setup.tt +15 -0
- data/lib/generators/develoz/docker/templates/docker-compose.yml.tt +59 -0
- data/lib/generators/develoz/docker/templates/dockerfile_dev.tt +24 -0
- data/lib/generators/develoz/docs_render/docs_render_generator.rb +46 -0
- data/lib/generators/develoz/docs_render/templates/app/assets/stylesheets/documentation.scss.tt +145 -0
- data/lib/generators/develoz/docs_render/templates/app/controllers/docs_controller.rb.tt +7 -0
- data/lib/generators/develoz/docs_render/templates/app/javascript/docs.js.tt +15 -0
- data/lib/generators/develoz/docs_render/templates/app/models/document.rb.tt +189 -0
- data/lib/generators/develoz/docs_render/templates/app/views/docs/show.html.erb.tt +7 -0
- data/lib/generators/develoz/docs_render/templates/config/initializers/redcarpet_rouge.rb.tt +16 -0
- data/lib/generators/develoz/frontend_core/frontend_core_generator.rb +40 -0
- data/lib/generators/develoz/frontend_core/templates/.annotaterb.yml.tt +16 -0
- data/lib/generators/develoz/frontend_core/templates/config/importmap.rb.tt +12 -0
- data/lib/generators/develoz/frontend_core/templates/config/initializers/pagy.rb.tt +10 -0
- data/lib/generators/develoz/install/install_generator.rb +51 -0
- data/lib/generators/develoz/kamal/kamal_generator.rb +59 -0
- data/lib/generators/develoz/kamal/templates/accessories_postgres.yml.tt +14 -0
- data/lib/generators/develoz/kamal/templates/deploy.yml.tt +43 -0
- data/lib/generators/develoz/kamal/templates/dockerfile_prod.tt +54 -0
- data/lib/generators/develoz/kamal/templates/kamal_secrets.tt +22 -0
- data/lib/generators/develoz/maintenance/maintenance_generator.rb +29 -0
- data/lib/generators/develoz/maintenance/templates/app/tasks/maintenance/example_task.rb.tt +20 -0
- data/lib/generators/develoz/maintenance/templates/lib/tasks/maintenance_counters.rake.tt +27 -0
- data/lib/generators/develoz/push/push_generator.rb +61 -0
- data/lib/generators/develoz/push/templates/app/javascript/pwa/subscription.js.tt +57 -0
- data/lib/generators/develoz/push/templates/app/models/push_subscription.rb.tt +14 -0
- data/lib/generators/develoz/push/templates/app/services/push_notification_service.rb.tt +36 -0
- data/lib/generators/develoz/push/templates/app/views/pwa/sw_push_handlers.js.tt +49 -0
- data/lib/generators/develoz/push/templates/constants_env.tt +5 -0
- data/lib/generators/develoz/push/templates/db/migrate/create_push_subscriptions.rb.tt +15 -0
- data/lib/generators/develoz/pwa/pwa_generator.rb +46 -0
- data/lib/generators/develoz/pwa/templates/app/controllers/pwa_controller.rb.tt +17 -0
- data/lib/generators/develoz/pwa/templates/app/javascript/pwa/registration.js.tt +25 -0
- data/lib/generators/develoz/pwa/templates/app/views/pwa/manifest.json.erb.tt +24 -0
- data/lib/generators/develoz/pwa/templates/app/views/pwa/offline.html.erb.tt +51 -0
- data/lib/generators/develoz/pwa/templates/app/views/pwa/service-worker.js.tt +109 -0
- data/lib/generators/develoz/pwa/templates/routes.rb.tt +3 -0
- data/lib/generators/develoz/solid/solid_generator.rb +52 -0
- data/lib/generators/develoz/solid/templates/app/jobs/application_job.rb.tt +7 -0
- data/lib/generators/develoz/solid/templates/config/cable.yml.tt +10 -0
- data/lib/generators/develoz/solid/templates/config/cache.yml.tt +16 -0
- data/lib/generators/develoz/solid/templates/config/initializers/mission_control.rb.tt +8 -0
- data/lib/generators/develoz/solid/templates/config/initializers/solid.rb.tt +7 -0
- data/lib/generators/develoz/solid/templates/config/queue.yml.tt +18 -0
- data/lib/generators/develoz/solid/templates/config/recurring.yml.tt +20 -0
- data/lib/generators/develoz/strict_loading/strict_loading_generator.rb +17 -0
- data/lib/generators/develoz/strict_loading/templates/config/initializers/strict_loading.rb.tt +13 -0
- data/lib/generators/develoz/testing/templates/rspec.tt +2 -0
- data/lib/generators/develoz/testing/templates/rspec_parallel.tt +2 -0
- data/lib/generators/develoz/testing/templates/spec/rails_helper.rb.tt +32 -0
- data/lib/generators/develoz/testing/templates/spec/spec_helper.rb.tt +31 -0
- data/lib/generators/develoz/testing/testing_generator.rb +39 -0
- data/lib/generators/develoz/tooling/templates/constants.rb.tt +17 -0
- data/lib/generators/develoz/tooling/templates/env.example.tt +4 -0
- data/lib/generators/develoz/tooling/templates/vscode/extensions.json.tt +8 -0
- data/lib/generators/develoz/tooling/templates/vscode/settings.json.tt +26 -0
- data/lib/generators/develoz/tooling/templates/vscode/tasks.json.tt +26 -0
- data/lib/generators/develoz/tooling/tooling_generator.rb +33 -0
- data/lib/generators/develoz/ui/templates/bin/setup_develoz_ui.tt +20 -0
- data/lib/generators/develoz/ui/ui_generator.rb +57 -0
- data/lib/generators/develoz/versioning/templates/app/helpers/application_helper.rb.tt +7 -0
- data/lib/generators/develoz/versioning/templates/app/views/shared/_app_version.html.erb.tt +3 -0
- data/lib/generators/develoz/versioning/templates/spec/helpers/application_helper_spec.rb.tt +11 -0
- data/lib/generators/develoz/versioning/versioning_generator.rb +48 -0
- data/lib/tasks/canonical.rake +27 -0
- data/templates/.keep +0 -0
- data/templates/CANONICAL_SOURCES.md +93 -0
- data/templates/pull_request_template.md.tt +13 -0
- metadata +383 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "develoz"
|
|
4
|
+
|
|
5
|
+
module Develoz
|
|
6
|
+
module Generators
|
|
7
|
+
class AuthGenerator < Develoz::Generators::Base
|
|
8
|
+
def self.source_root
|
|
9
|
+
File.expand_path("templates", __dir__)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def add_bcrypt_gem
|
|
13
|
+
add_gem "bcrypt"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create_user_model
|
|
17
|
+
template "app/models/user.rb.tt", "app/models/user.rb"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def create_current_model
|
|
21
|
+
template "app/models/current.rb.tt", "app/models/current.rb"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create_authentication_concern
|
|
25
|
+
template "app/controllers/concerns/authentication.rb.tt",
|
|
26
|
+
"app/controllers/concerns/authentication.rb"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def create_sessions_controller
|
|
30
|
+
template "app/controllers/sessions_controller.rb.tt",
|
|
31
|
+
"app/controllers/sessions_controller.rb"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def create_passwords_controller
|
|
35
|
+
template "app/controllers/passwords_controller.rb.tt",
|
|
36
|
+
"app/controllers/passwords_controller.rb"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def create_passwords_mailer
|
|
40
|
+
template "app/mailers/passwords_mailer.rb.tt",
|
|
41
|
+
"app/mailers/passwords_mailer.rb"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def create_sessions_views
|
|
45
|
+
template "app/views/sessions/new.html.erb.tt",
|
|
46
|
+
"app/views/sessions/new.html.erb"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def create_passwords_views
|
|
50
|
+
template "app/views/passwords/edit.html.erb.tt",
|
|
51
|
+
"app/views/passwords/edit.html.erb"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def create_passwords_mailer_views
|
|
55
|
+
template "app/views/passwords_mailer/reset.html.erb.tt",
|
|
56
|
+
"app/views/passwords_mailer/reset.html.erb"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def create_users_migration
|
|
60
|
+
return if migration_exists?("create_users")
|
|
61
|
+
|
|
62
|
+
template "db/migrate/create_users.rb.tt",
|
|
63
|
+
"db/migrate/#{next_migration_timestamp}_create_users.rb"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def insert_auth_routes
|
|
67
|
+
insert_route "resource :session, only: [:new, :create, :destroy]"
|
|
68
|
+
insert_route "resources :passwords, param: :token, only: [:new, :create, :edit, :update]"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def create_sessions_request_spec
|
|
72
|
+
template "spec/requests/sessions_spec.rb.tt",
|
|
73
|
+
"spec/requests/sessions_spec.rb"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def create_passwords_request_spec
|
|
77
|
+
template "spec/requests/passwords_spec.rb.tt",
|
|
78
|
+
"spec/requests/passwords_spec.rb"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Authentication
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
before_action :require_authentication
|
|
8
|
+
helper_method :authenticated?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class_methods do
|
|
12
|
+
def allow_authentication_as(**options)
|
|
13
|
+
skip_before_action :require_authentication, **options
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def authenticated?
|
|
20
|
+
resume_session
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def require_authentication
|
|
24
|
+
resume_session || request_authentication
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def resume_session
|
|
28
|
+
Current.user ||= find_user_by_cookie
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def find_user_by_cookie
|
|
32
|
+
User.find_by(id: cookies.signed[:user_id]) if cookies.signed[:user_id]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def request_authentication
|
|
36
|
+
session[:return_to_after_authenticating] = request.url
|
|
37
|
+
redirect_to new_session_path
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def after_authentication_url
|
|
41
|
+
session.delete(:return_to_after_authenticating) || root_url
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def start_new_session_for(user)
|
|
45
|
+
Current.user = user
|
|
46
|
+
cookies.signed.permanent[:user_id] = { value: user.id, httponly: true, same_site: :lax }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def terminate_session
|
|
50
|
+
Current.user = nil
|
|
51
|
+
cookies.delete(:user_id)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class PasswordsController < ApplicationController
|
|
4
|
+
allow_authentication_as
|
|
5
|
+
before_action :set_user_by_token, only: %i[edit update]
|
|
6
|
+
rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_password_path, alert: "Try again later." }
|
|
7
|
+
|
|
8
|
+
def new; end
|
|
9
|
+
|
|
10
|
+
def create
|
|
11
|
+
if (user = User.find_by(email: params[:email]))
|
|
12
|
+
PasswordsMailer.reset(user).deliver_later
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
redirect_to new_session_path, notice: "Password reset instructions sent (if user with that email address exists)."
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def edit; end
|
|
19
|
+
|
|
20
|
+
def update
|
|
21
|
+
if @user.update(params.permit(:password, :password_confirmation))
|
|
22
|
+
redirect_to new_session_path, notice: "Password has been reset."
|
|
23
|
+
else
|
|
24
|
+
redirect_to edit_password_path(params[:token]), alert: "Passwords did not match."
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def set_user_by_token
|
|
31
|
+
@user = User.find_by_password_reset_token!(params[:token])
|
|
32
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
|
33
|
+
redirect_to new_password_path, alert: "Password reset link is invalid or has expired."
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class SessionsController < ApplicationController
|
|
4
|
+
allow_authentication_as only: %i[new create]
|
|
5
|
+
rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_path, alert: "Try again later." }
|
|
6
|
+
|
|
7
|
+
def new; end
|
|
8
|
+
|
|
9
|
+
def create
|
|
10
|
+
if (user = User.authenticate_by(params.permit(:email, :password)))
|
|
11
|
+
start_new_session_for user
|
|
12
|
+
redirect_to after_authentication_url
|
|
13
|
+
else
|
|
14
|
+
redirect_to new_session_path, alert: "Try another email address or password."
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def destroy
|
|
19
|
+
terminate_session
|
|
20
|
+
redirect_to new_session_path, status: :see_other
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<h1>Update your password</h1>
|
|
2
|
+
|
|
3
|
+
<%%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
|
|
4
|
+
|
|
5
|
+
<%%= form_with url: password_path(params[:token]), method: :put do |form| %>
|
|
6
|
+
<%%= form.password_field :password, required: true, autocomplete: "new-password", placeholder: "Enter new password", maxlength: 72 %><br>
|
|
7
|
+
<%%= form.password_field :password_confirmation, required: true, autocomplete: "new-password", placeholder: "Repeat new password", maxlength: 72 %><br>
|
|
8
|
+
<%%= form.submit "Save" %>
|
|
9
|
+
<%% end %>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<%%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
|
|
2
|
+
<%%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %>
|
|
3
|
+
|
|
4
|
+
<%%= form_with url: session_path do |form| %>
|
|
5
|
+
<%%= form.email_field :email, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email] %><br>
|
|
6
|
+
<%%= form.password_field :password, required: true, autocomplete: "current-password", placeholder: "Enter your password", maxlength: 72 %><br>
|
|
7
|
+
<%%= form.submit "Sign in" %>
|
|
8
|
+
<%% end %>
|
|
9
|
+
<br>
|
|
10
|
+
|
|
11
|
+
<%%= link_to "Forgot password?", new_password_path %>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateUsers < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :users do |t|
|
|
6
|
+
t.string :email, null: false
|
|
7
|
+
t.string :password_digest, null: false
|
|
8
|
+
|
|
9
|
+
t.timestamps
|
|
10
|
+
end
|
|
11
|
+
add_index :users, :email, unique: true
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "Passwords", type: :request do
|
|
6
|
+
describe "GET /password/new" do
|
|
7
|
+
it "renders the forgot password form" do
|
|
8
|
+
get new_password_path
|
|
9
|
+
expect(response).to have_http_status(:ok)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe "POST /passwords" do
|
|
14
|
+
it "sends reset instructions when email exists" do
|
|
15
|
+
user = User.create!(email: "test@example.com", password: "password123")
|
|
16
|
+
|
|
17
|
+
expect { post passwords_path, params: { email: user.email } }
|
|
18
|
+
.to have_enqueued_mail(PasswordsMailer, :reset)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "redirects to sign in with notice" do
|
|
22
|
+
post passwords_path, params: { email: "nonexistent@example.com" }
|
|
23
|
+
expect(response).to redirect_to(new_session_path)
|
|
24
|
+
expect(flash[:notice]).to be_present
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe "GET /password/:token/edit" do
|
|
29
|
+
it "renders the edit form for a valid token" do
|
|
30
|
+
user = User.create!(email: "test@example.com", password: "password123")
|
|
31
|
+
|
|
32
|
+
get edit_password_path(user.password_reset_token)
|
|
33
|
+
expect(response).to have_http_status(:ok)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "redirects with alert for an invalid token" do
|
|
37
|
+
get edit_password_path("invalid-token")
|
|
38
|
+
expect(response).to redirect_to(new_password_path)
|
|
39
|
+
expect(flash[:alert]).to be_present
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe "PATCH /password/:token" do
|
|
44
|
+
it "resets the password with valid params" do
|
|
45
|
+
user = User.create!(email: "test@example.com", password: "password123")
|
|
46
|
+
|
|
47
|
+
patch password_path(user.password_reset_token), params: { password: "newpassword", password_confirmation: "newpassword" }
|
|
48
|
+
expect(response).to redirect_to(new_session_path)
|
|
49
|
+
expect(flash[:notice]).to be_present
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "redirects with alert when passwords do not match" do
|
|
53
|
+
user = User.create!(email: "test@example.com", password: "password123")
|
|
54
|
+
|
|
55
|
+
patch password_path(user.password_reset_token), params: { password: "newpassword", password_confirmation: "different" }
|
|
56
|
+
expect(response).to redirect_to(edit_password_path(user.password_reset_token))
|
|
57
|
+
expect(flash[:alert]).to be_present
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "Sessions", type: :request do
|
|
6
|
+
describe "GET /session/new" do
|
|
7
|
+
it "renders the sign in form" do
|
|
8
|
+
get new_session_path
|
|
9
|
+
expect(response).to have_http_status(:ok)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe "POST /session" do
|
|
14
|
+
it "signs in a valid user" do
|
|
15
|
+
user = User.create!(email: "test@example.com", password: "password123")
|
|
16
|
+
|
|
17
|
+
post session_path, params: { email: user.email, password: "password123" }
|
|
18
|
+
expect(response).to redirect_to(root_url)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "redirects with alert for invalid credentials" do
|
|
22
|
+
post session_path, params: { email: "wrong@example.com", password: "wrong" }
|
|
23
|
+
expect(response).to redirect_to(new_session_path)
|
|
24
|
+
expect(flash[:alert]).to be_present
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe "DELETE /session" do
|
|
29
|
+
it "signs out the current user" do
|
|
30
|
+
user = User.create!(email: "test@example.com", password: "password123")
|
|
31
|
+
post session_path, params: { email: user.email, password: "password123" }
|
|
32
|
+
|
|
33
|
+
delete session_path
|
|
34
|
+
expect(response).to redirect_to(new_session_path)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "develoz"
|
|
4
|
+
|
|
5
|
+
module Develoz
|
|
6
|
+
module Generators
|
|
7
|
+
class CiGenerator < Develoz::Generators::Base
|
|
8
|
+
def self.source_root
|
|
9
|
+
File.expand_path("templates", __dir__)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def add_ci_gems
|
|
13
|
+
add_gem "rubocop-rails-omakase", group: %i[development test]
|
|
14
|
+
add_gem "reek", group: %i[development test]
|
|
15
|
+
add_gem "flay", group: %i[development test]
|
|
16
|
+
add_gem "brakeman", group: %i[development test]
|
|
17
|
+
add_gem "bundler-audit", group: %i[development test]
|
|
18
|
+
add_gem "haml_lint", require: false, group: %i[development test]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def create_ci_entrypoint
|
|
22
|
+
template "bin/ci.tt", "bin/ci"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def create_ci_config
|
|
26
|
+
template "config/ci.rb.tt", "config/ci.rb"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def create_rakefile
|
|
30
|
+
template "Rakefile.tt", "Rakefile"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def create_rubocop_config
|
|
34
|
+
template ".rubocop.yml.tt", ".rubocop.yml"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def create_reek_config
|
|
38
|
+
template ".reek.yml.tt", ".reek.yml"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def create_biome_config
|
|
42
|
+
template "biome.json.tt", "biome.json"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def create_stylelint_config
|
|
46
|
+
template ".stylelintrc.json.tt", ".stylelintrc.json"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def create_haml_lint_config
|
|
50
|
+
template ".haml-lint.yml.tt", ".haml-lint.yml"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def create_markdownlint_config
|
|
54
|
+
template ".markdownlint.json.tt", ".markdownlint.json"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def create_yamllint_config
|
|
58
|
+
template ".yamllint.tt", ".yamllint"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def create_ci_workflow
|
|
62
|
+
template ".github/workflows/ci.yml.tt", ".github/workflows/ci.yml"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
ci:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: ruby/setup-ruby@v1
|
|
15
|
+
with:
|
|
16
|
+
bundler-cache: true
|
|
17
|
+
- name: Run CI pipeline
|
|
18
|
+
run: bin/ci
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
exclude_paths:
|
|
3
|
+
- app/assets
|
|
4
|
+
- config
|
|
5
|
+
- db
|
|
6
|
+
- lib/tasks
|
|
7
|
+
- vendor
|
|
8
|
+
- spec
|
|
9
|
+
- node_modules
|
|
10
|
+
|
|
11
|
+
detectors:
|
|
12
|
+
IrresponsibleModule:
|
|
13
|
+
enabled: false
|
|
14
|
+
UtilityFunction:
|
|
15
|
+
enabled: false
|
|
16
|
+
ControlParameter:
|
|
17
|
+
enabled: false
|
|
18
|
+
BooleanParameter:
|
|
19
|
+
enabled: false
|
|
20
|
+
FeatureEnvy:
|
|
21
|
+
enabled: false
|
|
22
|
+
TooManyStatements:
|
|
23
|
+
max_statements: 10
|
|
24
|
+
TooManyMethods:
|
|
25
|
+
max_methods: 25
|
|
26
|
+
UncommunicativeVariableName:
|
|
27
|
+
accept:
|
|
28
|
+
- e
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
inherit_gem:
|
|
2
|
+
rubocop-rails-omakase: rubocop.yml
|
|
3
|
+
|
|
4
|
+
AllCops:
|
|
5
|
+
NewCops: enable
|
|
6
|
+
TargetRubyVersion: 3.4
|
|
7
|
+
Exclude:
|
|
8
|
+
- "bin/*"
|
|
9
|
+
- "db/schema.rb"
|
|
10
|
+
- "vendor/bundle/**/*"
|
|
11
|
+
- "node_modules/**/*"
|
|
12
|
+
|
|
13
|
+
Layout/LineLength:
|
|
14
|
+
Max: 120
|
|
15
|
+
AllowedPatterns:
|
|
16
|
+
- !ruby/regexp /^ *# /
|
|
17
|
+
|
|
18
|
+
Style/Documentation:
|
|
19
|
+
Enabled: false
|
|
20
|
+
|
|
21
|
+
Style/FrozenStringLiteralComment:
|
|
22
|
+
Enabled: false
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
|
2
|
+
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
|
3
|
+
|
|
4
|
+
require_relative 'config/application'
|
|
5
|
+
|
|
6
|
+
Rails.application.load_tasks
|
|
7
|
+
|
|
8
|
+
if Rails.env.development?
|
|
9
|
+
require 'rubocop/rake_task'
|
|
10
|
+
require 'haml_lint/rake_task'
|
|
11
|
+
|
|
12
|
+
namespace :lint do
|
|
13
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
|
14
|
+
task.fail_on_error = true
|
|
15
|
+
task.options = %w[--parallel]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
HamlLint::RakeTask.new(:haml)
|
|
19
|
+
|
|
20
|
+
desc 'Lint JavaScript/TypeScript with Biome'
|
|
21
|
+
task :biome do # rubocop:disable Rails/RakeEnvironment
|
|
22
|
+
sh 'yarn lint'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
desc 'Lint CSS/SCSS with Stylelint'
|
|
26
|
+
task :stylelint do # rubocop:disable Rails/RakeEnvironment
|
|
27
|
+
sh 'yarn lint:css'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
desc 'Lint Markdown with markdownlint'
|
|
31
|
+
task :markdownlint do # rubocop:disable Rails/RakeEnvironment
|
|
32
|
+
sh 'yarn lint:md'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
desc 'Run Brakeman static security analysis'
|
|
36
|
+
task :brakeman do # rubocop:disable Rails/RakeEnvironment
|
|
37
|
+
sh 'bundle exec brakeman --quiet --no-pager --exit-on-warn'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
desc 'Lint YAML with yamllint'
|
|
41
|
+
task :yamllint do # rubocop:disable Rails/RakeEnvironment
|
|
42
|
+
sh 'yamllint .'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
desc 'Detect code smells with Reek'
|
|
46
|
+
task :reek do # rubocop:disable Rails/RakeEnvironment
|
|
47
|
+
sh 'bundle exec reek app lib'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
desc 'Detect structural duplication with Flay'
|
|
51
|
+
task :flay do # rubocop:disable Rails/RakeEnvironment
|
|
52
|
+
# Mass 300 clears the existing by-design Filters:: DSL similarity (max ~272)
|
|
53
|
+
# while still catching new egregious duplication. Lower it as that debt is paid down.
|
|
54
|
+
output = `bundle exec flay --mass 300 app lib`
|
|
55
|
+
puts output
|
|
56
|
+
abort 'Flay detected structural duplication' unless output.include?('Total score (lower is better) = 0')
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
desc 'Run all linters (RuboCop, haml-lint, Biome, Stylelint, markdownlint, yamllint, Brakeman, Reek, Flay)'
|
|
61
|
+
task lint: %w[lint:rubocop lint:haml lint:biome lint:stylelint lint:markdownlint lint:yamllint
|
|
62
|
+
lint:brakeman lint:reek lint:flay]
|
|
63
|
+
|
|
64
|
+
namespace :docs do
|
|
65
|
+
desc 'Check platform-guide docs are in sync with their documented system specs'
|
|
66
|
+
task :check do # rubocop:disable Rails/RakeEnvironment
|
|
67
|
+
ruby 'bin/generate-docs --check'
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
Rake::Task[:default].clear if Rake::Task.task_defined?(:default)
|
|
72
|
+
task default: %w[lint spec:parallel docs:check]
|
|
73
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
3
|
+
"vcs": {
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"clientKind": "git",
|
|
6
|
+
"useIgnoreFile": true
|
|
7
|
+
},
|
|
8
|
+
"files": {
|
|
9
|
+
"ignoreUnknown": false,
|
|
10
|
+
"includes": ["**", "!**/vendor/**", "!**/node_modules/**", "!public/**", "!**/coverage/**"]
|
|
11
|
+
},
|
|
12
|
+
"formatter": {
|
|
13
|
+
"enabled": true,
|
|
14
|
+
"useEditorconfig": true,
|
|
15
|
+
"formatWithErrors": false,
|
|
16
|
+
"indentStyle": "space",
|
|
17
|
+
"indentWidth": 2,
|
|
18
|
+
"lineEnding": "lf",
|
|
19
|
+
"lineWidth": 120
|
|
20
|
+
},
|
|
21
|
+
"linter": {
|
|
22
|
+
"enabled": true,
|
|
23
|
+
"rules": {
|
|
24
|
+
"recommended": true,
|
|
25
|
+
"correctness": {
|
|
26
|
+
"noUnusedVariables": "error"
|
|
27
|
+
},
|
|
28
|
+
"security": {
|
|
29
|
+
"noDangerouslySetInnerHtmlWithChildren": "error",
|
|
30
|
+
"noGlobalEval": "warn"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"javascript": {
|
|
35
|
+
"formatter": {
|
|
36
|
+
"quoteStyle": "single",
|
|
37
|
+
"semicolons": "asNeeded",
|
|
38
|
+
"trailingCommas": "es5"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|