From 0e1432f1a0267c10022932f0bbb21fd29d689746 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Thu, 2 Feb 2023 15:48:04 +0100 Subject: [PATCH 001/104] add Containerfile --- Containerfile | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 Containerfile diff --git a/Containerfile b/Containerfile new file mode 100644 index 00000000..9fcc7ed0 --- /dev/null +++ b/Containerfile @@ -0,0 +1,77 @@ +# Container image for a production Retrospring setup + +FROM registry.opensuse.org/opensuse/leap:15.4 + +ARG RETROSPRING_VERSION=2023.0131.1 +ARG RUBY_VERSION=3.1.2 +ARG RUBY_INSTALL_VERSION=0.9.0 +ARG BUNDLER_VERSION=2.3.18 + +ENV RAILS_ENV=production + +# update and install dependencies +RUN zypper up -y \ + && zypper in -y \ + # build dependencies (ruby-install) + automake \ + gcc \ + gdbm-devel \ + gzip \ + libffi-devel \ + libopenssl-devel \ + libyaml-devel \ + make \ + ncurses-devel \ + readline-devel \ + tar \ + xz \ + zlib-devel \ + # build dependencies (app) + gcc-c++ \ + git \ + libidn-devel \ + nodejs14 \ + npm14 \ + postgresql-devel \ + # runtime dependencies + ImageMagick \ + # cleanup repos + && zypper clean -a \ + # install yarn as another build dependency + && npm install -g yarn + +# install Ruby via ruby-install +RUN curl -Lo ruby-install-${RUBY_INSTALL_VERSION}.tar.gz https://github.com/postmodern/ruby-install/archive/v${RUBY_INSTALL_VERSION}.tar.gz \ + && tar xvf ruby-install-${RUBY_INSTALL_VERSION}.tar.gz \ + && (cd ruby-install-${RUBY_INSTALL_VERSION} && make install) \ + && rm -rf ruby-install-${RUBY_INSTALL_VERSION} ruby-install-${RUBY_INSTALL_VERSION}.tar.gz \ + && ruby-install --no-install-deps --cleanup --system --jobs=$(nproc) ruby ${RUBY_VERSION} -- --disable-install-rdoc \ + && gem install bundler:${BUNDLER_VERSION} + +# create user and dirs to run retrospring in +RUN useradd -m justask \ + && install -o justask -g users -m 0755 -d /opt/retrospring/app \ + && install -o justask -g users -m 0755 -d /opt/retrospring/bundle + +WORKDIR /opt/retrospring/app +USER justask:users + +# install the app +RUN curl -L https://github.com/Retrospring/retrospring/archive/refs/tags/${RETROSPRING_VERSION}.tar.gz | tar xz --strip-components=1 + +RUN bundle config set without 'development test' \ + && bundle config set path '/opt/retrospring/bundle' \ + && bundle install --jobs=$(nproc) \ + && yarn install --frozen-lockfile + +# temporarily set a SECRET_KEY_BASE and copy config files so rake tasks can run +ENV SECRET_KEY_BASE=secret_for_build +RUN cp config/justask.yml.example config/justask.yml \ + && cp config/database.yml.postgres config/database.yml \ + && bundle exec rails locale:generate \ + && bundle exec i18n export \ + && bundle exec rails assets:precompile \ + && rm config/justask.yml config/database.yml +ENV SECRET_KEY_BASE= + +EXPOSE 3000 From 1f4a92b6d414e345871c22478d6c0581552a5786 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Sat, 4 Feb 2023 07:07:43 +0100 Subject: [PATCH 002/104] allow log levels to be configurable via ENV --- config/environments/development.rb | 6 +++++- config/environments/production.rb | 2 +- config/environments/test.rb | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/config/environments/development.rb b/config/environments/development.rb index 317e27e3..c2434c8c 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -51,6 +51,10 @@ Rails.application.configure do config.action_mailer.perform_caching = false + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = ENV.fetch("LOG_LEVEL") { "debug" }.to_sym + # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log @@ -73,6 +77,6 @@ Rails.application.configure do # config.file_watcher = ActiveSupport::EventedFileUpdateChecker end -# For better_errors to work inside Docker we need +# For better_errors to work inside Docker we need # to allow 0.0.0.0 as an IP in development context BetterErrors::Middleware.allow_ip! "0.0.0.0/0" diff --git a/config/environments/production.rb b/config/environments/production.rb index 5964fe5c..3ae984d5 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -45,7 +45,7 @@ Rails.application.configure do # Use the lowest log level to ensure availability of diagnostic information # when problems arise. - config.log_level = :debug + config.log_level = ENV.fetch("LOG_LEVEL") { "info" }.to_sym # Prepend all log lines with the following tags. config.log_tags = [ :request_id ] diff --git a/config/environments/test.rb b/config/environments/test.rb index 30587ef6..036f3f7a 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -34,6 +34,10 @@ Rails.application.configure do # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = ENV.fetch("LOG_LEVEL") { "debug" }.to_sym + # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr From f14c0f1f83549cf59d39d85c51a2c8a706c32730 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Sat, 4 Feb 2023 07:09:32 +0100 Subject: [PATCH 003/104] Containerfile: always log to stdout --- Containerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Containerfile b/Containerfile index 9fcc7ed0..4d3a6049 100644 --- a/Containerfile +++ b/Containerfile @@ -74,4 +74,7 @@ RUN cp config/justask.yml.example config/justask.yml \ && rm config/justask.yml config/database.yml ENV SECRET_KEY_BASE= +# set some defaults +ENV RAILS_LOG_TO_STDOUT=true + EXPOSE 3000 From 8f3973bbffbbb7223f832e86e96e2d84a3f33656 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Sat, 4 Feb 2023 07:13:26 +0100 Subject: [PATCH 004/104] Containerfile: turn `SECRET_KEY_BASE` at build-time into a buildarg this is to not have `SECRET_KEY_BASE` present in the resulting container as it's only required to run some rake tasks --- Containerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Containerfile b/Containerfile index 4d3a6049..637aebee 100644 --- a/Containerfile +++ b/Containerfile @@ -65,14 +65,13 @@ RUN bundle config set without 'development test' \ && yarn install --frozen-lockfile # temporarily set a SECRET_KEY_BASE and copy config files so rake tasks can run -ENV SECRET_KEY_BASE=secret_for_build +ARG SECRET_KEY_BASE=secret_for_build RUN cp config/justask.yml.example config/justask.yml \ && cp config/database.yml.postgres config/database.yml \ && bundle exec rails locale:generate \ && bundle exec i18n export \ && bundle exec rails assets:precompile \ && rm config/justask.yml config/database.yml -ENV SECRET_KEY_BASE= # set some defaults ENV RAILS_LOG_TO_STDOUT=true From b0644b26c7f4b2550b81ce2d80981a48f2269647 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Sat, 4 Feb 2023 07:24:14 +0100 Subject: [PATCH 005/104] allow to set some configuration options via ENV --- config/initializers/10_config.rb | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/config/initializers/10_config.rb b/config/initializers/10_config.rb index 2e12b5f4..deba98c4 100644 --- a/config/initializers/10_config.rb +++ b/config/initializers/10_config.rb @@ -1,5 +1,23 @@ +# frozen_string_literal: true + # Auxiliary config -APP_CONFIG = YAML.load_file(Rails.root.join('config', 'justask.yml')).with_indifferent_access + +APP_CONFIG = {}.with_indifferent_access + +# load yml config if it's present +justask_yml_path = Rails.root.join("config/justask.yml") +APP_CONFIG.merge!(YAML.load_file(justask_yml_path)) if File.exist?(justask_yml_path) + +# load config from ENV where possible +env_config = { + # The site name, shown everywhere + site_name: ENV.fetch("SITE_NAME", nil), + + hostname: ENV.fetch("HOSTNAME", nil), +}.compact +APP_CONFIG.merge!(env_config) # Update rails config for mail -Rails.application.config.action_mailer.default_url_options = { host: APP_CONFIG['hostname'] } +Rails.application.config.action_mailer.default_url_options = { + host: APP_CONFIG["hostname"], +} From 55ff8a0d241a39a77144d77ebe02ed58bfdfd425 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Sat, 4 Feb 2023 07:25:10 +0100 Subject: [PATCH 006/104] enforce trailing commas in multi-line hash literals --- .rubocop.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index 6091bca9..acd3db74 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -131,3 +131,6 @@ Style/Encoding: Style/EndlessMethod: EnforcedStyle: allow_always + +Style/TrailingCommaInHashLiteral: + EnforcedStyleForMultiline: consistent_comma From f1a545aea55c0fd6e7819983c193a775fd8ff930 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Sat, 4 Feb 2023 22:31:53 +0100 Subject: [PATCH 007/104] use remote_ip --- app/controllers/ajax/question_controller.rb | 2 +- app/controllers/application_controller.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/ajax/question_controller.rb b/app/controllers/ajax/question_controller.rb index e86e1572..5d10cf2f 100644 --- a/app/controllers/ajax/question_controller.rb +++ b/app/controllers/ajax/question_controller.rb @@ -41,7 +41,7 @@ class Ajax::QuestionController < AjaxController target_user_id: params[:rcpt], content: params[:question], anonymous: params[:anonymousQuestion], - author_identifier: AnonymousBlock.get_identifier(request.ip) + author_identifier: AnonymousBlock.get_identifier(request.remote_ip) ) end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 05b7ad28..64ed88c7 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -60,7 +60,7 @@ class ApplicationController < ActionController::Base if current_user.present? Sentry.set_user({ id: current_user.id }) else - Sentry.set_user({ ip_address: request.ip }) + Sentry.set_user({ ip_address: request.remote_ip }) end end end From 482ffe7d0ba9109abd14eebca57f93d105a6d067 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 18:00:30 +0100 Subject: [PATCH 008/104] Add migration to add sharing fields to users --- .../20230205145011_add_sharing_fields_to_user.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 db/migrate/20230205145011_add_sharing_fields_to_user.rb diff --git a/db/migrate/20230205145011_add_sharing_fields_to_user.rb b/db/migrate/20230205145011_add_sharing_fields_to_user.rb new file mode 100644 index 00000000..cc2edc44 --- /dev/null +++ b/db/migrate/20230205145011_add_sharing_fields_to_user.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class AddSharingFieldsToUser < ActiveRecord::Migration[6.1] + def up + add_column :users, :sharing_enabled, :boolean, default: false + add_column :users, :sharing_autoclose, :boolean, default: false + add_column :users, :sharing_custom_url, :string + end + + def down + remove_column :users, :sharing_enabled + remove_column :users, :sharing_autoclose + remove_column :users, :sharing_custom_url + end +end From a2e45c85bf07859a6cdea22332202b68faa0bac4 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 18:00:48 +0100 Subject: [PATCH 009/104] Add migration to enable sharing for legacy service owners --- .../controllers/inbox_sharing_controller.ts | 48 +++++++++++++++++++ app/models/user/sharing_methods.rb | 7 +++ ...62103_enable_sharing_for_service_owners.rb | 13 +++++ 3 files changed, 68 insertions(+) create mode 100644 app/javascript/retrospring/controllers/inbox_sharing_controller.ts create mode 100644 app/models/user/sharing_methods.rb create mode 100644 db/migrate/20230205162103_enable_sharing_for_service_owners.rb diff --git a/app/javascript/retrospring/controllers/inbox_sharing_controller.ts b/app/javascript/retrospring/controllers/inbox_sharing_controller.ts new file mode 100644 index 00000000..bec8dd0e --- /dev/null +++ b/app/javascript/retrospring/controllers/inbox_sharing_controller.ts @@ -0,0 +1,48 @@ +import { Controller } from '@hotwired/stimulus'; + +export default class extends Controller { + static targets = ['twitter', 'tumblr', 'custom']; + + declare readonly twitterTarget: HTMLAnchorElement; + declare readonly tumblrTarget: HTMLAnchorElement; + declare readonly customTarget: HTMLAnchorElement; + declare readonly hasCustomTarget: boolean; + + static values = { + config: Object, + autoClose: Boolean + }; + + declare readonly configValue: Record; + declare readonly autoCloseValue: boolean; + + connect() { + if (this.autoCloseValue) { + this.twitterTarget.addEventListener('click', () => this.close()); + this.tumblrTarget.addEventListener('click', () => this.close()); + + if (this.hasCustomTarget) { + this.customTarget.addEventListener('click', () => this.close()); + } + } + } + + configValueChanged(value): void { + if (Object.keys(value).length === 0) { + return; + } + + this.element.classList.remove('d-none'); + + this.twitterTarget.href = this.configValue['twitter']; + this.tumblrTarget.href = this.configValue['tumblr']; + + if (this.hasCustomTarget) { + this.customTarget.href = `${this.customTarget.href}${this.configValue['custom']}`; + } + } + + close(): void { + (this.element.closest(".inbox-entry")).remove(); + } +} diff --git a/app/models/user/sharing_methods.rb b/app/models/user/sharing_methods.rb new file mode 100644 index 00000000..2c4e38b4 --- /dev/null +++ b/app/models/user/sharing_methods.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module User::SharingMethods + def display_sharing_custom_url + URI(sharing_custom_url).host + end +end diff --git a/db/migrate/20230205162103_enable_sharing_for_service_owners.rb b/db/migrate/20230205162103_enable_sharing_for_service_owners.rb new file mode 100644 index 00000000..f194722c --- /dev/null +++ b/db/migrate/20230205162103_enable_sharing_for_service_owners.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class EnableSharingForServiceOwners < ActiveRecord::Migration[6.1] + def up + execute <<~SQUIRREL + UPDATE users + SET sharing_enabled = true + WHERE id IN (SELECT user_id FROM services); + SQUIRREL + end + + def down; end +end From 9246148db41a8eedf8860e117d10eb80ad84dac8 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 18:01:21 +0100 Subject: [PATCH 010/104] Add migration to drop services table --- .../20230205162800_drop_services_table.rb | 11 +++++++++++ db/schema.rb | 18 ++++-------------- 2 files changed, 15 insertions(+), 14 deletions(-) create mode 100644 db/migrate/20230205162800_drop_services_table.rb diff --git a/db/migrate/20230205162800_drop_services_table.rb b/db/migrate/20230205162800_drop_services_table.rb new file mode 100644 index 00000000..6bb783df --- /dev/null +++ b/db/migrate/20230205162800_drop_services_table.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class DropServicesTable < ActiveRecord::Migration[6.1] + def up + drop_table :services + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/schema.rb b/db/schema.rb index 82edbdda..79160ab2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_01_29_194809) do +ActiveRecord::Schema.define(version: 2023_02_05_162800) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -253,19 +253,6 @@ ActiveRecord::Schema.define(version: 2023_01_29_194809) do t.index ["delivered", "failed", "processing", "deliver_after", "created_at"], name: "index_rpush_notifications_multi", where: "((NOT delivered) AND (NOT failed))" end - create_table "services", id: :serial, force: :cascade do |t| - t.string "type", null: false - t.bigint "user_id", null: false - t.string "uid" - t.string "access_token" - t.string "access_secret" - t.string "nickname" - t.datetime "created_at" - t.datetime "updated_at" - t.string "post_tag", limit: 20 - t.index ["user_id"], name: "index_services_on_user_id" - end - create_table "subscriptions", id: :serial, force: :cascade do |t| t.bigint "user_id", null: false t.bigint "answer_id", null: false @@ -371,6 +358,9 @@ ActiveRecord::Schema.define(version: 2023_01_29_194809) do t.boolean "privacy_require_user", default: false t.boolean "privacy_hide_social_graph", default: false t.boolean "privacy_noindex", default: false + t.boolean "sharing_enabled", default: false + t.boolean "sharing_autoclose", default: false + t.string "sharing_custom_url" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true From c327eee38eaa25ff9c17a9270aa3dcc23b1792cd Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 18:02:58 +0100 Subject: [PATCH 011/104] Remove services controller --- app/controllers/services_controller.rb | 69 ---------- config/locales/controllers.en.yml | 12 -- config/routes.rb | 11 -- spec/controllers/services_controller_spec.rb | 129 ------------------- 4 files changed, 221 deletions(-) delete mode 100644 app/controllers/services_controller.rb delete mode 100644 spec/controllers/services_controller_spec.rb diff --git a/app/controllers/services_controller.rb b/app/controllers/services_controller.rb deleted file mode 100644 index deaa39a0..00000000 --- a/app/controllers/services_controller.rb +++ /dev/null @@ -1,69 +0,0 @@ -# frozen_string_literal: true - -class ServicesController < ApplicationController - before_action :authenticate_user! - before_action :mark_notifications_as_read, only: %i[index] - - def index - @services = current_user.services - end - - def create - service = Service.initialize_from_omniauth(omniauth_hash) - service.user = current_user - service_name = service.type.split("::").last.titleize - - if service.save - flash[:success] = t(".success", service: service_name) - else - flash[:error] = if service.errors.details[:uid]&.any? { |err| err[:error] == :taken } - t(".duplicate", service: service_name, app: APP_CONFIG["site_name"]) - else - t(".error", service: service_name) - end - end - - redirect_to origin || services_path - end - - def update - service = current_user.services.find(params[:id]) - service.post_tag = params[:service][:post_tag].tr("@", "") - if service.save - flash[:success] = t(".success") - else - flash[:error] = t(".error") - end - redirect_to services_path - end - - def failure - Rails.logger.info "oauth error: #{params.inspect}" - flash[:error] = t(".error") - redirect_to services_path - end - - def destroy - @service = current_user.services.find(params[:id]) - service_name = @service.type.split("::").last.titleize - @service.destroy - flash[:success] = t(".success", service: service_name) - redirect_to services_path - end - - private - - def origin - request.env["omniauth.origin"] - end - - def omniauth_hash - request.env["omniauth.auth"] - end - - def mark_notifications_as_read - Notification::ServiceTokenExpired - .where(recipient: current_user, new: true) - .update_all(new: false) # rubocop:disable Rails/SkipsModelValidations - end -end diff --git a/config/locales/controllers.en.yml b/config/locales/controllers.en.yml index 1373b154..7c047680 100644 --- a/config/locales/controllers.en.yml +++ b/config/locales/controllers.en.yml @@ -148,18 +148,6 @@ en: author: info: "No questions from @%{author} found, showing entries from all users instead!" error: "No user with the name @%{author} found, showing entries from all users instead!" - services: - create: - success: "%{service} connected successfully." - duplicate: "The %{service} account you are trying to connect is already connected to another %{app} account. If you are unable to disconnect the account yourself, please send us a Direct Message on Twitter: @retrospring." - error: "Unable to connect to %{service}." - update: - success: "Service updated successfully." - error: "Unable to update service." - failure: - error: :errors.base - destroy: - success: "%{service} disconnected successfully." settings: export: index: diff --git a/config/routes.rb b/config/routes.rb index 7dca9c1a..fbc7aa0a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -101,17 +101,6 @@ Rails.application.routes.draw do resolve("Theme") { [:settings_theme] } # to make link_to/form_for work nicely when passing a `Theme` object to it, see also: https://api.rubyonrails.org/v6.1.5.1/classes/ActionDispatch/Routing/Mapper/CustomUrls.html#method-i-resolve resolve("Profile") { [:settings_profile] } - # resources :services, only: [:index, :destroy] - get "/settings/services", to: "services#index", as: :services - patch "/settings/services/:id", to: "services#update", as: :update_service - delete "/settings/services/:id", to: "services#destroy", as: :service - controller :services do - scope "/auth", as: "auth" do - get ":provider/callback" => :create - get :failure - end - end - namespace :ajax do post "/ask", to: "question#create", as: :ask post "/destroy_question", to: "question#destroy", as: :destroy_question diff --git a/spec/controllers/services_controller_spec.rb b/spec/controllers/services_controller_spec.rb deleted file mode 100644 index 5e9582ab..00000000 --- a/spec/controllers/services_controller_spec.rb +++ /dev/null @@ -1,129 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe ServicesController, type: :controller do - describe "#index" do - subject { get :index } - - context "user signed in" do - let(:user) { FactoryBot.create(:user) } - - before { sign_in user } - - it "renders the services settings page with no services" do - subject - expect(response).to render_template("index") - expect(controller.instance_variable_get(:@services)).to be_empty - end - - context "user has a service token expired notification" do - let(:notification) do - Notification::ServiceTokenExpired.create( - target_id: user.id, - target_type: "User::ExpiredTwitterServiceConnection", - recipient_id: user.id, - new: true - ) - end - - it "marks the notification as read" do - expect { subject }.to change { notification.reload.new }.from(true).to(false) - end - end - - context "user has Twitter connected" do - before do - Services::Twitter.create(user:, uid: 12) - end - - it "renders the services settings page" do - subject - expect(response).to render_template("index") - expect(controller.instance_variable_get(:@services)).not_to be_empty - end - end - end - end - - describe "#create" do - subject { get :create, params: { provider: "twitter" } } - - context "successful Twitter sign in" do - let(:user) { FactoryBot.create(:user) } - - before do - sign_in user - OmniAuth.config.test_mode = true - OmniAuth.config.mock_auth[:twitter] = OmniAuth::AuthHash.new({ - "provider" => "twitter", - "uid" => "12", - "info" => { "nickname" => "jack" }, - "credentials" => { "token" => "AAAA", "secret" => "BBBB" } - }) - request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter] - end - - after do - OmniAuth.config.mock_auth[:twitter] = nil - end - - context "no services connected" do - it "creates a service integration" do - expect { subject }.to change { Service.count }.by(1) - end - end - - context "a user has a service connected" do - let(:other_user) { FactoryBot.create(:user) } - let!(:service) { Services::Twitter.create(user: other_user, uid: 12) } - - it "shows an error when trying to attach a service account which is already connected" do - subject - expect(flash[:error]).to eq("The Twitter account you are trying to connect is already connected to another #{APP_CONFIG['site_name']} account. If you are unable to disconnect the account yourself, please send us a Direct Message on Twitter: @retrospring.") - end - end - end - end - - describe "#update" do - subject { patch :update, params: } - - context "not signed in" do - let(:params) { { id: 1 } } - - it "redirects to sign in page" do - subject - expect(response).to redirect_to(new_user_session_path) - end - end - - context "user with Twitter connection" do - before { sign_in user } - - let(:user) { FactoryBot.create(:user) } - let(:service) { Services::Twitter.create(user:, uid: 12) } - let(:params) { { id: service.id, service: { post_tag: } } } - - context "tag is valid" do - let(:post_tag) { "#askaraccoon" } - - it "updates a service connection" do - expect { subject }.to change { service.reload.post_tag }.to("#askaraccoon") - expect(response).to redirect_to(services_path) - expect(flash[:success]).to eq("Service updated successfully.") - end - end - - context "tag is too long" do - let(:post_tag) { "a" * 21 } # 1 character over the limit - - it "shows an error" do - subject - expect(response).to redirect_to(services_path) - expect(flash[:error]).to eq("Unable to update service.") - end - end - end - end -end From d520755bc2e0f00ae34d8a1d9db8e286cbe8f903 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 18:03:52 +0100 Subject: [PATCH 012/104] Remove service model --- app/models/service.rb | 41 ------------------------------ app/models/services/twitter.rb | 28 -------------------- app/models/user.rb | 1 - config/locales/activerecord.en.yml | 4 --- 4 files changed, 74 deletions(-) delete mode 100644 app/models/service.rb delete mode 100644 app/models/services/twitter.rb diff --git a/app/models/service.rb b/app/models/service.rb deleted file mode 100644 index 7cdf4524..00000000 --- a/app/models/service.rb +++ /dev/null @@ -1,41 +0,0 @@ -class Service < ApplicationRecord - attr_accessor :provider, :info - - belongs_to :user - validates_uniqueness_of :uid, scope: :type - validates_length_of :post_tag, maximum: 20 - - class << self - - def first_from_omniauth(auth_hash) - @@auth = auth_hash - where(type: service_type, uid: options[:uid]).first - end - - def initialize_from_omniauth(auth_hash) - @@auth = auth_hash - service_type.constantize.new(options) - end - - private - - def auth - @@auth - end - - def service_type - "Services::#{options[:provider].camelize}" - end - - def options - { - nickname: auth['info']['nickname'], - access_token: auth['credentials']['token'], - access_secret: auth['credentials']['secret'], - uid: auth['uid'], - provider: auth['provider'], - info: auth['info'] - } - end - end -end diff --git a/app/models/services/twitter.rb b/app/models/services/twitter.rb deleted file mode 100644 index 388c316d..00000000 --- a/app/models/services/twitter.rb +++ /dev/null @@ -1,28 +0,0 @@ -class Services::Twitter < Service - include Rails.application.routes.url_helpers - include SocialHelper::TwitterMethods - - def provider - "twitter" - end - - def post(answer) - Rails.logger.debug "posting to Twitter {'answer' => #{answer.id}, 'user' => #{self.user_id}}" - post_tweet answer - end - - private - - def client - @client ||= Twitter::REST::Client.new( - consumer_key: APP_CONFIG['sharing']['twitter']['consumer_key'], - consumer_secret: APP_CONFIG['sharing']['twitter']['consumer_secret'], - access_token: self.access_token, - access_token_secret: self.access_secret - ) - end - - def post_tweet(answer) - client.update! prepare_tweet(answer, self.post_tag) - end -end diff --git a/app/models/user.rb b/app/models/user.rb index fb32d232..df628cc5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -33,7 +33,6 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength has_many :comments, dependent: :destroy_async has_many :inboxes, dependent: :destroy_async has_many :smiles, class_name: "Appendable::Reaction", dependent: :destroy_async - has_many :services, dependent: :destroy_async has_many :notifications, foreign_key: :recipient_id, dependent: :destroy_async has_many :reports, dependent: :destroy_async has_many :lists, dependent: :destroy_async diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml index 2b1b178e..3a0a0742 100644 --- a/config/locales/activerecord.en.yml +++ b/config/locales/activerecord.en.yml @@ -30,8 +30,6 @@ en: location: "Location" motivation_header: "Motivation header" website: "Website" - service: - post_tag: "Tag" theme: background_color: "Background colour" body_text: "Body text colour" @@ -90,8 +88,6 @@ en: profile: anon_display_name: "This name will be used for questions asked to you by anonymous users." motivation_header: "Shown in the header of the question box on your profile. Motivate users to ask you questions!" - services/twitter: - post_tag: "Automatically append a tag to your shared answers. A # symbol is not automatically prepended." theme: danger_color: "Colour used for errors or critical actions like deleting something." info_color: "Colour used for informational popups or messages." From 06d7db7ff8ab31c32e529443d995902c2b13a50f Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 18:04:30 +0100 Subject: [PATCH 013/104] Remove ShareWorker --- app/workers/share_worker.rb | 43 --------- .../ajax/answer_controller_spec.rb | 12 --- spec/workers/share_worker_spec.rb | 88 ------------------- 3 files changed, 143 deletions(-) delete mode 100644 app/workers/share_worker.rb delete mode 100644 spec/workers/share_worker_spec.rb diff --git a/app/workers/share_worker.rb b/app/workers/share_worker.rb deleted file mode 100644 index 7edcf141..00000000 --- a/app/workers/share_worker.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -class ShareWorker - include Sidekiq::Worker - - sidekiq_options queue: :share, retry: 5 - - # @param user_id [Integer] the user id - # @param answer_id [Integer] the user id - # @param service [String] the service to post to - def perform(user_id, answer_id, service) # rubocop:disable Metrics/AbcSize - @user_service = User.find(user_id).services.find_by!(type: "Services::#{service.camelize}") - - @user_service.post(Answer.find(answer_id)) - rescue ActiveRecord::RecordNotFound - logger.info "Tried to post answer ##{answer_id} for user ##{user_id} to #{service.titleize} but the user/answer/service did not exist (likely deleted), will not retry." - # The question to be posted was deleted - rescue Twitter::Error::DuplicateStatus - logger.info "Tried to post answer ##{answer_id} from user ##{user_id} to Twitter but the status was already posted." - rescue Twitter::Error::Forbidden - # User's Twitter account is suspended - logger.info "Tried to post answer ##{answer_id} from user ##{user_id} to Twitter but the account is suspended." - rescue Twitter::Error::Unauthorized - # User's Twitter token has expired or been revoked - logger.info "Tried to post answer ##{answer_id} from user ##{user_id} to Twitter but the token has expired or been revoked." - revoke_and_notify(user_id, service) - rescue => e - logger.info "failed to post answer #{answer_id} to #{service} for user #{user_id}: #{e.message}" - Sentry.capture_exception(e) - raise - end - - def revoke_and_notify(user_id, service) - @user_service.destroy - - Notification::ServiceTokenExpired.create( - target_id: user_id, - target_type: "User::Expired#{service.camelize}ServiceConnection", - recipient_id: user_id, - new: true - ) - end -end diff --git a/spec/controllers/ajax/answer_controller_spec.rb b/spec/controllers/ajax/answer_controller_spec.rb index 79c911dc..93374bb5 100644 --- a/spec/controllers/ajax/answer_controller_spec.rb +++ b/spec/controllers/ajax/answer_controller_spec.rb @@ -25,13 +25,6 @@ describe Ajax::AnswerController, :ajax_controller, type: :controller do expect { subject }.to(change { Answer.count }.by(1)) end - it "enqueues a job for sharing the answer to social networks" do - subject - shared_services.each do |service| - expect(ShareWorker).to have_enqueued_sidekiq_job(user.id, Answer.last.id, service) - end - end - include_examples "returns the expected response" end @@ -40,11 +33,6 @@ describe Ajax::AnswerController, :ajax_controller, type: :controller do expect { subject }.not_to(change { Answer.count }) end - it "does not enqueue a job for sharing the answer to social networks" do - subject - expect(ShareWorker).not_to have_enqueued_sidekiq_job(anything, anything, anything) - end - include_examples "returns the expected response" end diff --git a/spec/workers/share_worker_spec.rb b/spec/workers/share_worker_spec.rb deleted file mode 100644 index c8003258..00000000 --- a/spec/workers/share_worker_spec.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe ShareWorker do - let(:user) { FactoryBot.create(:user) } - let(:answer) { FactoryBot.create(:answer, user: user) } - let!(:service) do - Services::Twitter.create!(type: "Services::Twitter", - user: user) - end - - before do - stub_const("APP_CONFIG", { - "hostname" => "example.com", - "anonymous_name" => "Anonymous", - "https" => true, - "items_per_page" => 5, - "sharing" => { - "twitter" => { - "consumer_key" => "" - } - } - }) - end - - describe "#perform" do - before do - allow(Sidekiq.logger).to receive(:info) - end - - subject do - Sidekiq::Testing.fake! do - ShareWorker.perform_async(user.id, answer.id, "twitter") - end - end - - context "when answer doesn't exist" do - it "prevents the job from retrying and logs a message" do - answer.destroy! - expect { subject }.to change(ShareWorker.jobs, :size).by(1) - expect { ShareWorker.drain }.to change(ShareWorker.jobs, :size).by(-1) - expect(Sidekiq.logger).to have_received(:info).with("Tried to post answer ##{answer.id} for user ##{user.id} to Twitter but the user/answer/service did not exist (likely deleted), will not retry.") - end - end - - context "when answer exists" do - it "handles Twitter::Error::DuplicateStatus" do - allow_any_instance_of(Services::Twitter).to receive(:post).with(answer).and_raise(Twitter::Error::DuplicateStatus) - subject - ShareWorker.drain - expect(Sidekiq.logger).to have_received(:info).with("Tried to post answer ##{answer.id} from user ##{user.id} to Twitter but the status was already posted.") - end - - it "handles Twitter::Error::Unauthorized" do - allow_any_instance_of(Services::Twitter).to receive(:post).with(answer).and_raise(Twitter::Error::Unauthorized) - subject - ShareWorker.drain - expect(Sidekiq.logger).to have_received(:info).with("Tried to post answer ##{answer.id} from user ##{user.id} to Twitter but the token has expired or been revoked.") - end - - it "revokes the service connection when Twitter::Error::Unauthorized is raised" do - allow_any_instance_of(Services::Twitter).to receive(:post).with(answer).and_raise(Twitter::Error::Unauthorized) - subject - expect { ShareWorker.drain }.to change { Services::Twitter.count }.by(-1) - end - - it "sends the user a notification when Twitter::Error::Unauthorized is raised" do - allow_any_instance_of(Services::Twitter).to receive(:post).with(answer).and_raise(Twitter::Error::Unauthorized) - subject - expect { ShareWorker.drain }.to change { Notification::ServiceTokenExpired.count }.by(1) - end - - it "handles Twitter::Error::Forbidden" do - allow_any_instance_of(Services::Twitter).to receive(:post).with(answer).and_raise(Twitter::Error::Forbidden) - subject - ShareWorker.drain - expect(Sidekiq.logger).to have_received(:info).with("Tried to post answer ##{answer.id} from user ##{user.id} to Twitter but the account is suspended.") - end - - it "retries on unhandled exceptions" do - expect { subject }.to change(ShareWorker.jobs, :size).by(1) - expect { ShareWorker.drain }.to raise_error(Twitter::Error::BadRequest) - expect(Sidekiq.logger).to have_received(:info) - end - end - end -end From a86dcde30a374cb94cc6dbf86b873e7a0ac70cb2 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 18:05:29 +0100 Subject: [PATCH 014/104] Remove omniauth and twitter (sharing) related gems `twitter-text` is still required to generate the shared post bodies --- Gemfile | 8 ----- Gemfile.lock | 56 --------------------------------- config/initializers/omniauth.rb | 7 ----- config/initializers/sentry.rb | 1 - 4 files changed, 72 deletions(-) delete mode 100644 config/initializers/omniauth.rb diff --git a/Gemfile b/Gemfile index 6c128e9a..b4a30a43 100644 --- a/Gemfile +++ b/Gemfile @@ -58,12 +58,6 @@ gem "httparty" gem "redcarpet" gem "sanitize" -# OmniAuth and providers -gem "omniauth" -gem "omniauth-twitter" - -# OAuth clients -gem "twitter" gem "twitter-text" gem "redis" @@ -109,8 +103,6 @@ group :production do gem "lograge" end -gem "omniauth-rails_csrf_protection", "~> 1.0" - gem "net-imap" gem "net-pop" gem "net-smtp" diff --git a/Gemfile.lock b/Gemfile.lock index 9361cd44..41852740 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -90,7 +90,6 @@ GEM bootstrap_form (5.1.0) actionpack (>= 5.2) activemodel (>= 5.2) - buftok (0.2.0) builder (3.2.4) bullet (7.0.7) activesupport (>= 3.0.0) @@ -131,8 +130,6 @@ GEM devise (>= 4.8.0) diff-lcs (1.5.0) docile (1.4.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) dry-core (1.0.0) concurrent-ruby (~> 1.0) zeitwerk (~> 2.6) @@ -148,7 +145,6 @@ GEM dry-inflector (~> 1.0, < 2) dry-logic (>= 1.4, < 2) zeitwerk (~> 2.6) - equalizer (0.0.11) erubi (1.12.0) excon (0.98.0) factory_bot (6.2.0) @@ -162,9 +158,6 @@ GEM faker (3.1.0) i18n (>= 1.8.11, < 2) ffi (1.15.5) - ffi-compiler (1.0.1) - ffi (>= 1.0.0) - rake fog-aws (3.16.0) fog-core (~> 2.1) fog-json (~> 1.1) @@ -196,22 +189,10 @@ GEM rainbow rubocop (>= 0.50.0) sysexits (~> 1.1) - hashie (5.0.0) hcaptcha (7.1.0) json hkdf (0.3.0) http-2 (0.11.0) - http (4.4.1) - addressable (~> 2.3) - http-cookie (~> 1.0) - http-form_data (~> 2.2) - http-parser (~> 1.2.0) - http-cookie (1.0.4) - domain_name (~> 0.5) - http-form_data (2.3.0) - http-parser (1.2.3) - ffi-compiler (>= 1.0, < 2.0) - http_parser.rb (0.6.0) httparty (0.21.0) mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) @@ -257,8 +238,6 @@ GEM mail (2.7.1) mini_mime (>= 0.1.1) marcel (1.0.2) - memoizable (0.4.2) - thread_safe (~> 0.3, >= 0.3.1) method_source (1.0.0) mime-types (3.4.1) mime-types-data (~> 3.2015) @@ -273,8 +252,6 @@ GEM msgpack (1.6.0) multi_json (1.15.0) multi_xml (0.6.0) - multipart-post (2.1.1) - naught (1.1.0) nested_form (0.3.2) net-http-persistent (4.0.1) connection_pool (~> 2.2) @@ -293,21 +270,7 @@ GEM nokogiri (1.14.0) mini_portile2 (~> 2.8.0) racc (~> 1.4) - oauth (0.5.8) oj (3.13.23) - omniauth (2.1.0) - hashie (>= 3.4.6) - rack (>= 2.2.3) - rack-protection - omniauth-oauth (1.2.0) - oauth - omniauth (>= 1.0, < 3) - omniauth-rails_csrf_protection (1.0.1) - actionpack (>= 4.2) - omniauth (~> 2.0) - omniauth-twitter (1.4.0) - omniauth-oauth (~> 1.1) - rack openssl (3.1.0) orm_adapter (0.5.0) parallel (1.22.1) @@ -324,8 +287,6 @@ GEM questiongenerator (1.1.0) racc (1.6.2) rack (2.2.6.2) - rack-protection (3.0.5) - rack rack-test (2.0.2) rack (>= 1.3) rails (6.1.7.2) @@ -462,7 +423,6 @@ GEM connection_pool (>= 2.2.5, < 3) rack (~> 2.0) redis (>= 4.5.0, < 5) - simple_oauth (0.3.1) simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) @@ -486,7 +446,6 @@ GEM sysexits (1.2.0) temple (0.10.0) thor (1.2.1) - thread_safe (0.3.6) tilt (2.0.11) timeout (0.3.1) tldv (0.1.0) @@ -496,17 +455,6 @@ GEM actionpack (>= 6.0.0) activejob (>= 6.0.0) railties (>= 6.0.0) - twitter (7.0.0) - addressable (~> 2.3) - buftok (~> 0.2.0) - equalizer (~> 0.0.11) - http (~> 4.0) - http-form_data (~> 2.0) - http_parser.rb (~> 0.6.0) - memoizable (~> 0.4.0) - multipart-post (~> 2.0) - naught (~> 1.0) - simple_oauth (~> 0.3.0) twitter-text (3.1.0) idn-ruby unf (~> 0.1.0) @@ -570,9 +518,6 @@ DEPENDENCIES net-pop net-smtp oj - omniauth - omniauth-rails_csrf_protection (~> 1.0) - omniauth-twitter openssl (~> 3.1) pg pghero @@ -612,7 +557,6 @@ DEPENDENCIES sprockets-rails tldv (~> 0.1.0) turbo-rails - twitter twitter-text BUNDLED WITH diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb deleted file mode 100644 index c34c2b2f..00000000 --- a/config/initializers/omniauth.rb +++ /dev/null @@ -1,7 +0,0 @@ -Rails.application.config.middleware.use OmniAuth::Builder do - APP_CONFIG.fetch('sharing').each do |service, opts| - if opts['enabled'] - provider service.to_sym, opts['consumer_key'], opts['consumer_secret'] - end - end -end diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb index 1124fc37..0001a1e0 100644 --- a/config/initializers/sentry.rb +++ b/config/initializers/sentry.rb @@ -11,7 +11,6 @@ Sentry.init do |config| exception_fingerprints = { Excon::Error::ServiceUnavailable => 'external-service', - Twitter::Error::InternalServerError => 'external-service', } config.before_send = lambda do |event, hint| # These are used for user-facing errors, not when something goes wrong From 52ae7fbb817f5ebb956ee053de7277cf74630a6b Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 18:06:48 +0100 Subject: [PATCH 015/104] Remove service-related views --- app/controllers/inbox_controller.rb | 5 ++--- app/views/inbox/show.html.haml | 2 +- app/views/question/show.html.haml | 4 ---- app/views/services/index.html.haml | 4 ---- app/views/settings/_services.html.haml | 27 -------------------------- 5 files changed, 3 insertions(+), 39 deletions(-) delete mode 100644 app/views/services/index.html.haml delete mode 100644 app/views/settings/_services.html.haml diff --git a/app/controllers/inbox_controller.rb b/app/controllers/inbox_controller.rb index a31080e8..0d4313c4 100644 --- a/app/controllers/inbox_controller.rb +++ b/app/controllers/inbox_controller.rb @@ -18,12 +18,11 @@ class InboxController < ApplicationController @delete_id = find_delete_id @disabled = true if @inbox.empty? - services = current_user.services.to_a respond_to do |format| - format.html { render "show", locals: { services: } } + format.html { render "show" } format.turbo_stream do - render "show", locals: { services: }, layout: false, status: :see_other + render "show", layout: false, status: :see_other # rubocop disabled as just flipping a flag doesn't need to have validations to be run @inbox.update_all(new: false) # rubocop:disable Rails/SkipsModelValidations diff --git a/app/views/inbox/show.html.haml b/app/views/inbox/show.html.haml index 94d7d883..5c5de4e4 100644 --- a/app/views/inbox/show.html.haml +++ b/app/views/inbox/show.html.haml @@ -1,6 +1,6 @@ #entries - @inbox.each do |i| - = render "inbox/entry", services:, i: + = render "inbox/entry", i: - if @inbox.empty? %p.empty= t(".empty") diff --git a/app/views/question/show.html.haml b/app/views/question/show.html.haml index 614a62e7..57137240 100644 --- a/app/views/question/show.html.haml +++ b/app/views/question/show.html.haml @@ -22,7 +22,3 @@ %br/ %button.btn.btn-success#q-answer-btn{ data: { q_id: @question.id } } = t("voc.answer") - - current_user.services.each do |service| - %label - %input{ type: "checkbox", name: "share", checked: :checked, data: { q_id: @question.id, service: service.provider } } - = t("inbox.entry.sharing.post_to", service: service.provider.capitalize) diff --git a/app/views/services/index.html.haml b/app/views/services/index.html.haml deleted file mode 100644 index 0863d21d..00000000 --- a/app/views/services/index.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -= render "settings/services", services: @services - -- provide(:title, generate_title(t(".title"))) -- parent_layout "user/settings" diff --git a/app/views/settings/_services.html.haml b/app/views/settings/_services.html.haml deleted file mode 100644 index d92b6a72..00000000 --- a/app/views/settings/_services.html.haml +++ /dev/null @@ -1,27 +0,0 @@ -.card - .card-body - = t(".services", count: services.count) - - - APP_CONFIG["sharing"].each do |service, service_options| - - if service_options["enabled"] && services.none? { |x| x.provider == service.to_s } - %p= button_to t(".connect", service: service.capitalize), "/auth/#{service}", method: :post, class: "btn btn-info", - form: { data: { turbo: false } } - - - if services.count.positive? - %ul.list-group - - services.each do |service| - %li.list-group-item - %i{ class: "fa fa-#{service.provider}" } - %strong= service.provider.capitalize - (#{service.nickname}) - = button_to t(".disconnect"), - service_path(service), - data: { confirm: t(".confirm", service: service.provider.capitalize) }, - method: :delete, - class: "btn btn-link text-danger", - form: { class: "d-inline", data: { turbo: false } } - - .col-md-6.mt-2 - = bootstrap_form_for(service, as: "service", url: update_service_path(service), data: { turbo: false }) do |f| - = f.text_field :post_tag, label_as_placeholder: true, - append: f.submit(t("voc.update"), class: "btn btn-primary"), maxlength: 20, pattern: "^[^@]*$" From 83d386267a861e69d6bcaf873dcafe8193cace8e Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 18:17:12 +0100 Subject: [PATCH 016/104] Remove inbox entry options collapse --- .../retrospring/features/inbox/entry/index.ts | 2 -- .../retrospring/features/inbox/entry/options.ts | 16 ---------------- 2 files changed, 18 deletions(-) delete mode 100644 app/javascript/retrospring/features/inbox/entry/options.ts diff --git a/app/javascript/retrospring/features/inbox/entry/index.ts b/app/javascript/retrospring/features/inbox/entry/index.ts index 54022129..423a8191 100644 --- a/app/javascript/retrospring/features/inbox/entry/index.ts +++ b/app/javascript/retrospring/features/inbox/entry/index.ts @@ -1,7 +1,6 @@ import registerEvents from 'utilities/registerEvents'; import { answerEntryHandler, answerEntryInputHandler } from './answer'; import { deleteEntryHandler } from './delete'; -import optionsEntryHandler from './options'; import { reportEventHandler } from './report'; export default (): void => { @@ -9,7 +8,6 @@ export default (): void => { { type: 'click', target: 'button[name="ib-answer"]', handler: answerEntryHandler, global: true }, { type: 'click', target: '[name="ib-destroy"]', handler: deleteEntryHandler, global: true }, { type: 'click', target: '[name=ib-report]', handler: reportEventHandler, global: true }, - { type: 'click', target: 'button[name=ib-options]', handler: optionsEntryHandler, global: true }, { type: 'keydown', target: 'textarea[name=ib-answer]', handler: answerEntryInputHandler, global: true } ]); } diff --git a/app/javascript/retrospring/features/inbox/entry/options.ts b/app/javascript/retrospring/features/inbox/entry/options.ts deleted file mode 100644 index 00a50905..00000000 --- a/app/javascript/retrospring/features/inbox/entry/options.ts +++ /dev/null @@ -1,16 +0,0 @@ -export default function optionsEntryHandler(event: Event): void { - const button = event.target as HTMLElement; - const inboxId = button.dataset.ibId; - - const options = document.querySelector(`#ib-options-${inboxId}`); - options.classList.toggle('d-none'); - - const buttonIcon = button.getElementsByTagName('i')[0]; - if (buttonIcon.classList.contains('fa-chevron-down')) { - buttonIcon.classList.remove('fa-chevron-down'); - buttonIcon.classList.add('fa-chevron-up'); - } else { - buttonIcon.classList.remove('fa-chevron-up'); - buttonIcon.classList.add('fa-chevron-down'); - } -} From 926be13fa658cb11a8618c02476b37037833db37 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 19:15:20 +0100 Subject: [PATCH 017/104] Add `Settings::SharingController` --- .../settings/sharing_controller.rb | 19 ++++++++++++++++++ app/views/settings/sharing/edit.html.haml | 20 +++++++++++++++++++ app/views/tabs/_settings.html.haml | 2 +- config/locales/activerecord.en.yml | 6 ++++++ config/locales/controllers.en.yml | 4 ++++ config/locales/views.en.yml | 11 +++++++--- config/routes.rb | 3 +++ 7 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 app/controllers/settings/sharing_controller.rb create mode 100644 app/views/settings/sharing/edit.html.haml diff --git a/app/controllers/settings/sharing_controller.rb b/app/controllers/settings/sharing_controller.rb new file mode 100644 index 00000000..a92bd364 --- /dev/null +++ b/app/controllers/settings/sharing_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class Settings::SharingController < ApplicationController + before_action :authenticate_user! + + def edit; end + + def update + user_attributes = params.require(:user).permit(:sharing_enabled, + :sharing_autoclose, + :sharing_custom_url) + if current_user.update(user_attributes) + flash[:success] = t(".success") + else + flash[:error] = t(".error") + end + redirect_to settings_sharing_path + end +end diff --git a/app/views/settings/sharing/edit.html.haml b/app/views/settings/sharing/edit.html.haml new file mode 100644 index 00000000..002fc279 --- /dev/null +++ b/app/views/settings/sharing/edit.html.haml @@ -0,0 +1,20 @@ += bootstrap_form_for(current_user, url: settings_sharing_path, method: :patch, data: { turbo: false }) do |f| + .card + .card-body + = f.form_group :sharing, help: t("activerecord.help.user.sharing_enabled") do + = f.check_box :sharing_enabled + = f.form_group :sharing, help: t("activerecord.help.user.sharing_autoclose"), class: false do + = f.check_box :sharing_autoclose + + .card + .card-body + %h3= t(".advanced.title") + = t(".advanced.body_html") + = f.url_field :sharing_custom_url + + .card + .card-body + = f.primary + +- provide(:title, generate_title(t(".title"))) +- parent_layout "user/settings" diff --git a/app/views/tabs/_settings.html.haml b/app/views/tabs/_settings.html.haml index 366aafc1..f54ba115 100644 --- a/app/views/tabs/_settings.html.haml +++ b/app/views/tabs/_settings.html.haml @@ -4,7 +4,7 @@ = list_group_item t(".profile"), edit_settings_profile_path = list_group_item t(".privacy"), edit_settings_privacy_path = list_group_item t(".security"), settings_two_factor_authentication_otp_authentication_path - = list_group_item t(".sharing"), services_path + = list_group_item t(".sharing"), settings_sharing_path = list_group_item t(".mutes"), settings_muted_path = list_group_item t(".blocks"), settings_blocks_path = list_group_item t(".theme"), edit_settings_theme_path diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml index 3a0a0742..779d1227 100644 --- a/config/locales/activerecord.en.yml +++ b/config/locales/activerecord.en.yml @@ -74,6 +74,9 @@ en: privacy_allow_stranger_answers: "Allow other people to answer your questions" privacy_noindex: "Prevent search engines from indexing your profile" privacy_hide_social_graph: "Hide your social graph from others" + sharing_enabled: "Enable sharing" + sharing_autoclose: "Automatically close inbox entry after sharing once" + sharing_custom_url: "Custom Share Link" profile_picture: "Profile picture" profile_header: "Profile header" sign_in_count: "Sign in count" @@ -85,6 +88,9 @@ en: screen_name: "Alphanumerical and underscores allowed, max. 16 characters" email: "Don't forget to check your spam folder in case our email might have landed there!" current_password: "We need your current password to confirm your changes" + sharing_enabled: "Shows a sharing dialog after answering a question from your inbox" + sharing_autoclose: "Use this if you mainly post to a single service" + sharing_custom_url: "Example: https://mastodon.social/share?text=" profile: anon_display_name: "This name will be used for questions asked to you by anonymous users." motivation_header: "Shown in the header of the question box on your profile. Motivate users to ask you questions!" diff --git a/config/locales/controllers.en.yml b/config/locales/controllers.en.yml index 7c047680..4b5f49af 100644 --- a/config/locales/controllers.en.yml +++ b/config/locales/controllers.en.yml @@ -170,6 +170,10 @@ en: notice: profile_picture: " It might take a few minutes until your new profile picture is shown everywhere." profile_header: " It might take a few minutes until your new profile header is shown everywhere." + sharing: + update: + success: "Sharing settings updated successfully." + error: "Unable to update sharing settings." theme: update: success: "Theme saved successfully." diff --git a/config/locales/views.en.yml b/config/locales/views.en.yml index 295f8aab..29b241e8 100644 --- a/config/locales/views.en.yml +++ b/config/locales/views.en.yml @@ -363,9 +363,6 @@ en: heading: "Twitter connection expired" text_html: "If you would like to continue automatically sharing your answers to Twitter, head to %{settings_sharing} and re-connect your account." settings_services: "Sharing Settings" - services: - index: - title: "Service Settings" settings: account: email_confirm: "Currently awaiting confirmation for %{resource}" @@ -453,6 +450,14 @@ en: profile_picture: "Adjust your new profile picture" profile_header: "Adjust your new profile header" submit_picture: "Save pictures" + sharing: + edit: + title: "Sharing Settings" + advanced: + title: "Advanced options" + body_html: | +

If you use a service other than Twitter or Tumblr, which has support for sharing from external services, you can enter their sharing URL here so it will be listed as an option for sharing after an answer has been sent.

+

We will prepend a URL-encoded version of the answer to the end of the given link.

two_factor_authentication: otp_authentication: index: diff --git a/config/routes.rb b/config/routes.rb index fbc7aa0a..74ebc5b0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -78,6 +78,9 @@ Rails.application.routes.draw do get :privacy, to: redirect("/settings/privacy/edit") resource :privacy, controller: :privacy, only: %i[edit update] + get :sharing, to: redirect("/settings/sharing/edit") + resource :sharing, controller: :sharing, only: %i[edit update] + get :export, to: "export#index" post :export, to: "export#create" From a56db2256b933bb09c0366a77f4a018fbe5dd813 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 19:17:01 +0100 Subject: [PATCH 018/104] Return sharing options when answering a question --- app/controllers/ajax/answer_controller.rb | 20 ++++++++++++++------ app/models/user.rb | 1 + 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/app/controllers/ajax/answer_controller.rb b/app/controllers/ajax/answer_controller.rb index de9c611f..84d0607b 100644 --- a/app/controllers/ajax/answer_controller.rb +++ b/app/controllers/ajax/answer_controller.rb @@ -1,8 +1,12 @@ +require 'cgi' + class Ajax::AnswerController < AjaxController + include SocialHelper::TwitterMethods + include SocialHelper::TumblrMethods + def create params.require :id params.require :answer - params.require :share params.require :inbox inbox = (params[:inbox] == 'true') @@ -31,15 +35,19 @@ class Ajax::AnswerController < AjaxController current_user.answer question, params[:answer] end - services = JSON.parse params[:share] - services.each do |service| - ShareWorker.perform_async(current_user.id, answer.id, service) - end - @response[:status] = :okay @response[:message] = t(".success") @response[:success] = true + + if current_user.sharing_enabled + @response[:sharing] = { + twitter: twitter_share_url(answer), + tumblr: tumblr_share_url(answer), + custom: CGI.escape(prepare_tweet(answer)) + } + end + unless inbox # this assign is needed because shared/_answerbox relies on it, I think @question = 1 diff --git a/app/models/user.rb b/app/models/user.rb index df628cc5..9406aca4 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -12,6 +12,7 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength include User::PushNotificationMethods include User::ReactionMethods include User::RelationshipMethods + include User::SharingMethods include User::TimelineMethods include ActiveModel::OneTimePassword From da0a5fb98d19e002431b1f00c856a5b314081df8 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 19:17:34 +0100 Subject: [PATCH 019/104] Add inbox sharing Stimulus controller --- app/javascript/retrospring/initializers/stimulus.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/javascript/retrospring/initializers/stimulus.ts b/app/javascript/retrospring/initializers/stimulus.ts index 64ba1a50..b4c3c91b 100644 --- a/app/javascript/retrospring/initializers/stimulus.ts +++ b/app/javascript/retrospring/initializers/stimulus.ts @@ -8,6 +8,7 @@ import CollapseController from "retrospring/controllers/collapse_controller"; import ThemeController from "retrospring/controllers/theme_controller"; import CapabilitiesController from "retrospring/controllers/capabilities_controller"; import CropperController from "retrospring/controllers/cropper_controller"; +import InboxSharingController from "retrospring/controllers/inbox_sharing_controller"; /** * This module sets up Stimulus and our controllers @@ -26,5 +27,6 @@ export default function (): void { window['Stimulus'].register('collapse', CollapseController); window['Stimulus'].register('cropper', CropperController); window['Stimulus'].register('format-popup', FormatPopupController); + window['Stimulus'].register('inbox-sharing', InboxSharingController); window['Stimulus'].register('theme', ThemeController); } From 7589d66686c4b2b7080ec9477310fe6d9b3ae10e Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 19:35:59 +0100 Subject: [PATCH 020/104] Remove service reference in question generation --- app/controllers/inbox_controller.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/inbox_controller.rb b/app/controllers/inbox_controller.rb index 0d4313c4..d849dafc 100644 --- a/app/controllers/inbox_controller.rb +++ b/app/controllers/inbox_controller.rb @@ -37,11 +37,10 @@ class InboxController < ApplicationController user: current_user) inbox = Inbox.create!(user: current_user, question_id: question.id, new: true) - services = current_user.services respond_to do |format| format.turbo_stream do - render turbo_stream: turbo_stream.prepend("entries", partial: "inbox/entry", locals: { i: inbox, services: }) + render turbo_stream: turbo_stream.prepend("entries", partial: "inbox/entry", locals: { i: inbox }) inbox.update(new: false) end From a4195a158f3841c6e4ef577a9f8bb34d599ce9d0 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 19:36:28 +0100 Subject: [PATCH 021/104] Update TypeScript to support new sharing feature --- .../retrospring/features/inbox/entry/answer.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/javascript/retrospring/features/inbox/entry/answer.ts b/app/javascript/retrospring/features/inbox/entry/answer.ts index 14b6b25a..93b0cefc 100644 --- a/app/javascript/retrospring/features/inbox/entry/answer.ts +++ b/app/javascript/retrospring/features/inbox/entry/answer.ts @@ -9,16 +9,9 @@ export function answerEntryHandler(event: Event): void { element.disabled = true; - const shareTo = []; - inboxEntry.querySelectorAll('input[type=checkbox][name=ib-share]:checked') - .forEach((element: HTMLInputElement) => { - shareTo.push(element.getAttribute('data-service')); - }); - const data = { id: element.getAttribute('data-ib-id'), answer: inboxEntry.querySelector('textarea[name=ib-answer]')?.value, - share: JSON.stringify(shareTo), inbox: 'true' }; @@ -36,7 +29,14 @@ export function answerEntryHandler(event: Event): void { } updateDeleteButton(false); showNotification(data.message); - (inboxEntry as HTMLElement).remove(); + + const sharing = inboxEntry.querySelector('.inbox-entry__sharing'); + if (sharing != null) { + sharing.dataset.inboxSharingConfigValue = JSON.stringify(data.sharing); + } + else { + (inboxEntry as HTMLElement).remove(); + } }) .catch(err => { console.log(err); @@ -51,4 +51,4 @@ export function answerEntryInputHandler(event: KeyboardEvent): void { if (event.keyCode == 13 && (event.ctrlKey || event.metaKey)) { document.querySelector(`button[name="ib-answer"][data-ib-id="${inboxId}"]`).click(); } -} \ No newline at end of file +} From 83aa4ed1bcd16b7fb45de6e601bab854d1b3dc47 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 19:49:06 +0100 Subject: [PATCH 022/104] Implement new sharing layout --- .../stylesheets/components/_inbox-entry.scss | 24 +++++++++++++- app/views/inbox/_entry.html.haml | 31 ++++++++++--------- config/locales/views.en.yml | 6 ++-- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/app/assets/stylesheets/components/_inbox-entry.scss b/app/assets/stylesheets/components/_inbox-entry.scss index 3fd728a1..a56875cd 100644 --- a/app/assets/stylesheets/components/_inbox-entry.scss +++ b/app/assets/stylesheets/components/_inbox-entry.scss @@ -1,6 +1,10 @@ +@use "sass:map"; + .inbox-entry { $this: &; + position: relative; + &--new { box-shadow: 0 0.125rem 0.25rem var(--primary); @@ -22,6 +26,24 @@ } } + &__close { + position: absolute; + top: map.get($spacers, 3); + right: map.get($spacers, 3); + } + + &__sharing { + position: absolute; + display: flex; + background-color: rgba(var(--raised-bg-rgb), 0.9); + backdrop-filter: blur(3px); + border-radius: var(--card-inner-border-radius); + top: 0; + bottom: 0; + left: 0; + right: 0; + } + .format-help { opacity: .3; } @@ -30,7 +52,7 @@ &:hover .format-help { opacity: 1; } - + .card-header { position: relative; } diff --git a/app/views/inbox/_entry.html.haml b/app/views/inbox/_entry.html.haml index f630d80c..c696c29b 100644 --- a/app/views/inbox/_entry.html.haml +++ b/app/views/inbox/_entry.html.haml @@ -33,19 +33,22 @@ = t("voc.answer") %button.btn.btn-danger.me-sm-1{ name: "ib-destroy", data: { ib_id: i.id } } = t("voc.delete") - %button.btn.btn-default.px-1{ name: "ib-options", data: { ib_id: i.id, state: :hidden } } - %i.fa.fa-chevron-down - %span.pe-none= t(".options") %p.format-help.ms-auto.align-self-center.mt-2.mt-sm-0.text-center = render "shared/format_link" - .card-footer.d-none{ id: "ib-options-#{i.id}" } - %h4= t(".sharing.heading") - - if services.count.positive? - .row - - services.each do |service| - .col-md-3.col-sm-4.col-xs-6 - %label - %input{ type: "checkbox", name: "ib-share", checked: :checked, data: { ib_id: i.id, service: service.provider } } - = raw t(".sharing.post_to", service: service.provider.capitalize) - - else - %p= t(".sharing.none_html", settings: link_to(t(".sharing.settings"), services_path)) + - if current_user.sharing_enabled + .inbox-entry__sharing.text-center.p-2.justify-content-center.d-none{ data: { controller: "inbox-sharing", inbox_sharing_config_value: "{}", inbox_sharing_auto_close_value: current_user.sharing_autoclose.to_s }} + %button.btn-close.inbox-entry__close{ data: { action: "inbox-sharing#close" } } + %span.visually-hidden= t("voc.close") + %div.align-self-center + %p.fs-3.fw-bold= t(".sharing.heading") + %p + %a.btn.btn-primary{ href: "https://twitter.com/intent/tweet?text=", data: { inbox_sharing_target: "twitter" }, target: "_blank" } + %i.fab.fa-twitter.fa-fw + Twitter + %a.btn.btn-primary{ href: "#", data: { inbox_sharing_target: "tumblr" }, target: "_blank" } + %i.fab.fa-tumblr.fa-fw + Tumblr + - if current_user.sharing_custom_url + %a.btn.btn-primary{ href: current_user.sharing_custom_url, data: { inbox_sharing_target: "custom" }, target: "_blank" } + = current_user.display_sharing_custom_url + %p.text-muted= t(".sharing.hint_html", settings: link_to(t(".sharing.settings"), settings_sharing_path)) diff --git a/config/locales/views.en.yml b/config/locales/views.en.yml index 29b241e8..425c1d67 100644 --- a/config/locales/views.en.yml +++ b/config/locales/views.en.yml @@ -214,10 +214,10 @@ en: options: "Sharing Options" placeholder: "Write your answer here…" sharing: - heading: "Sharing" + heading: "Share answer on..." post_to: "Post to %{service}" - none_html: "You have not connected any services yet. Visit your %{settings} to connect one." - settings: "service settings" + hint_html: "You can customize sharing in your %{settings}" + settings: "settings" show: empty: "Nothing to see here." actions: From f0eaf9c4a22026a3b6ee42367db3adabc5bd9cfb Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 20:05:55 +0100 Subject: [PATCH 023/104] Remove `Services::Twitter` spec --- spec/models/services/twitter_spec.rb | 46 ---------------------------- 1 file changed, 46 deletions(-) delete mode 100644 spec/models/services/twitter_spec.rb diff --git a/spec/models/services/twitter_spec.rb b/spec/models/services/twitter_spec.rb deleted file mode 100644 index c12870bb..00000000 --- a/spec/models/services/twitter_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Services::Twitter do - describe "#post" do - let(:user) { FactoryBot.create(:user) } - let(:service) { Services::Twitter.create(user: user) } - let(:answer) { FactoryBot.create(:answer, user: user, - content: 'a' * 255, - question_content: 'q' * 255) } - let(:twitter_client) { instance_double(Twitter::REST::Client) } - - before do - allow(Twitter::REST::Client).to receive(:new).and_return(twitter_client) - allow(twitter_client).to receive(:update!) - stub_const("APP_CONFIG", { - 'hostname' => 'example.com', - 'https' => true, - 'items_per_page' => 5, - 'sharing' => { - 'twitter' => { - 'consumer_key' => 'AAA', - } - } - }) - end - - it "posts a shortened tweet" do - service.post(answer) - - expect(twitter_client).to have_received(:update!).with("#{'q' * 123}… — #{'a' * 124}… https://example.com/@#{user.screen_name}/a/#{answer.id}") - end - - it "posts an un-shortened tweet" do - answer.question.content = 'Why are raccoons so good?' - answer.question.save! - answer.content = 'Because they are good cunes.' - answer.save! - - service.post(answer) - - expect(twitter_client).to have_received(:update!).with("#{answer.question.content} — #{answer.content} https://example.com/@#{user.screen_name}/a/#{answer.id}") - end - end -end \ No newline at end of file From 247604f48f22f81993b4da84bdc2ba73ffb0d99a Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 20:07:17 +0100 Subject: [PATCH 024/104] Remove Twitter API key config values --- config/justask.yml.example | 8 -------- 1 file changed, 8 deletions(-) diff --git a/config/justask.yml.example b/config/justask.yml.example index 65e109c6..97616e4c 100644 --- a/config/justask.yml.example +++ b/config/justask.yml.example @@ -53,14 +53,6 @@ features: public: enabled: true -# OAuth tokens -sharing: - twitter: - enabled: false - # Get the tokens from https://apps.twitter.com - consumer_key: '' - consumer_secret: '' - # Redis redis_url: "redis://localhost:6379" From 14d841d31a82ca4d415e6714991abd1d3cab1326 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 20:10:29 +0100 Subject: [PATCH 025/104] Disable "color-function-notation" stylelint rule --- .stylelintrc.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.stylelintrc.json b/.stylelintrc.json index b8f0eb23..2aa477b8 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -9,6 +9,7 @@ "scss/at-import-partial-extension": null, "scss/at-rule-no-unknown": true, "at-rule-no-unknown": null, + "color-function-notation": null, "color-hex-length": "long", "color-hex-case": "upper", "comment-whitespace-inside": null, From 8f0ae5171d2cb853ff1803414db54a8a35a4d5a0 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 20:18:51 +0100 Subject: [PATCH 026/104] Appease the dog overlords --- app/controllers/ajax/answer_controller.rb | 27 +++++++++---------- .../controllers/inbox_sharing_controller.ts | 4 +-- app/views/inbox/_entry.html.haml | 5 ++-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/controllers/ajax/answer_controller.rb b/app/controllers/ajax/answer_controller.rb index 84d0607b..859ae794 100644 --- a/app/controllers/ajax/answer_controller.rb +++ b/app/controllers/ajax/answer_controller.rb @@ -1,4 +1,6 @@ -require 'cgi' +# frozen_string_literal: true + +require "cgi" class Ajax::AnswerController < AjaxController include SocialHelper::TwitterMethods @@ -9,7 +11,7 @@ class Ajax::AnswerController < AjaxController params.require :answer params.require :inbox - inbox = (params[:inbox] == 'true') + inbox = (params[:inbox] == "true") if inbox inbox_entry = Inbox.find(params[:id]) @@ -35,7 +37,6 @@ class Ajax::AnswerController < AjaxController current_user.answer question, params[:answer] end - @response[:status] = :okay @response[:message] = t(".success") @response[:success] = true @@ -43,16 +44,16 @@ class Ajax::AnswerController < AjaxController if current_user.sharing_enabled @response[:sharing] = { twitter: twitter_share_url(answer), - tumblr: tumblr_share_url(answer), - custom: CGI.escape(prepare_tweet(answer)) + tumblr: tumblr_share_url(answer), + custom: CGI.escape(prepare_tweet(answer)) } end - unless inbox - # this assign is needed because shared/_answerbox relies on it, I think - @question = 1 - @response[:render] = render_to_string(partial: 'answerbox', locals: { a: answer, show_question: false }) - end + return if inbox + + # this assign is needed because shared/_answerbox relies on it, I think + @question = 1 + @response[:render] = render_to_string(partial: "answerbox", locals: { a: answer, show_question: false }) end def destroy @@ -60,15 +61,13 @@ class Ajax::AnswerController < AjaxController answer = Answer.find(params[:answer]) - unless (current_user == answer.user) or (privileged? answer.user) + unless (current_user == answer.user) || (privileged? answer.user) @response[:status] = :nopriv @response[:message] = t(".nopriv") return end - if answer.user == current_user - Inbox.create!(user: answer.user, question: answer.question, new: true, returning: true) - end + Inbox.create!(user: answer.user, question: answer.question, new: true, returning: true) if answer.user == current_user answer.destroy @response[:status] = :okay diff --git a/app/javascript/retrospring/controllers/inbox_sharing_controller.ts b/app/javascript/retrospring/controllers/inbox_sharing_controller.ts index bec8dd0e..48f0a905 100644 --- a/app/javascript/retrospring/controllers/inbox_sharing_controller.ts +++ b/app/javascript/retrospring/controllers/inbox_sharing_controller.ts @@ -16,7 +16,7 @@ export default class extends Controller { declare readonly configValue: Record; declare readonly autoCloseValue: boolean; - connect() { + connect(): void { if (this.autoCloseValue) { this.twitterTarget.addEventListener('click', () => this.close()); this.tumblrTarget.addEventListener('click', () => this.close()); @@ -27,7 +27,7 @@ export default class extends Controller { } } - configValueChanged(value): void { + configValueChanged(value: Record): void { if (Object.keys(value).length === 0) { return; } diff --git a/app/views/inbox/_entry.html.haml b/app/views/inbox/_entry.html.haml index c696c29b..29fcfaa7 100644 --- a/app/views/inbox/_entry.html.haml +++ b/app/views/inbox/_entry.html.haml @@ -36,10 +36,11 @@ %p.format-help.ms-auto.align-self-center.mt-2.mt-sm-0.text-center = render "shared/format_link" - if current_user.sharing_enabled - .inbox-entry__sharing.text-center.p-2.justify-content-center.d-none{ data: { controller: "inbox-sharing", inbox_sharing_config_value: "{}", inbox_sharing_auto_close_value: current_user.sharing_autoclose.to_s }} + .inbox-entry__sharing.text-center.p-2.justify-content-center.d-none{ + data: { controller: "inbox-sharing", inbox_sharing_config_value: "{}", inbox_sharing_auto_close_value: current_user.sharing_autoclose.to_s } } %button.btn-close.inbox-entry__close{ data: { action: "inbox-sharing#close" } } %span.visually-hidden= t("voc.close") - %div.align-self-center + .align-self-center %p.fs-3.fw-bold= t(".sharing.heading") %p %a.btn.btn-primary{ href: "https://twitter.com/intent/tweet?text=", data: { inbox_sharing_target: "twitter" }, target: "_blank" } From 47d1f5ccff6f731686c1db703f7150fef88f2ee6 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 20:24:40 +0100 Subject: [PATCH 027/104] Validate format of `sharing_custom_url` --- app/models/user.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/user.rb b/app/models/user.rb index 9406aca4..c3a2bfd1 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -61,6 +61,7 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength end validates :email, fake_email: true, typoed_email: true + validates :sharing_custom_url, format: URI::DEFAULT_PARSER.make_regexp(%w[http https]) validates :screen_name, presence: true, format: { with: SCREEN_NAME_REGEX, message: I18n.t("activerecord.validation.user.screen_name.format") }, From a564bd740b86c022435ec6d0289ae2aacbd9835a Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 20:26:59 +0100 Subject: [PATCH 028/104] Apply review suggestion from @nilsding Co-authored-by: Georg Gadinger --- config/locales/views.en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/views.en.yml b/config/locales/views.en.yml index 425c1d67..9ebb0aaf 100644 --- a/config/locales/views.en.yml +++ b/config/locales/views.en.yml @@ -216,7 +216,7 @@ en: sharing: heading: "Share answer on..." post_to: "Post to %{service}" - hint_html: "You can customize sharing in your %{settings}" + hint_html: "You can customise sharing in your %{settings}" settings: "settings" show: empty: "Nothing to see here." From 490a06af27e8d31d1fc39d36f66ca2be403329f3 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 20:36:32 +0100 Subject: [PATCH 029/104] Allow `sharing_custom_url` to be empty --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index c3a2bfd1..7a57479d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -61,7 +61,7 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength end validates :email, fake_email: true, typoed_email: true - validates :sharing_custom_url, format: URI::DEFAULT_PARSER.make_regexp(%w[http https]) + validates :sharing_custom_url, format: URI::DEFAULT_PARSER.make_regexp(%w[http https]), allow_blank: true validates :screen_name, presence: true, format: { with: SCREEN_NAME_REGEX, message: I18n.t("activerecord.validation.user.screen_name.format") }, From 4e5aca9ab5e8cba05aa039537376db29e9dd0976 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 20:49:45 +0100 Subject: [PATCH 030/104] Add specs for URL validation --- spec/models/user_spec.rb | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index f1f2abdc..3263fa86 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -33,6 +33,38 @@ RSpec.describe User, type: :model do end end + describe "custom sharing url validation" do + subject do + FactoryBot.build(:user, sharing_custom_url: url).tap(&:validate).errors[:sharing_custom_url] + end + + shared_examples_for "valid url" do |example_url| + context "when url is #{example_url}" do + let(:url) { example_url } + + it "does not have validation errors" do + expect(subject).to be_empty + end + end + end + + shared_examples_for "invalid url" do |example_url| + context "when url is #{example_url}" do + let(:url) { example_url } + + it "has validation errors" do + expect(subject).not_to be_empty + end + end + end + + include_examples "valid url", "https://myfunnywebsite.com/" + include_examples "valid url", "https://desu.social/share?text=" + include_examples "valid url", "http://insecurebutvalid.business/" + include_examples "invalid url", "ftp://fileprotocols.cool/" + include_examples "invalid url", "notevenanurl" + end + describe "email validation" do subject do FactoryBot.build(:user, email: email).tap(&:validate).errors[:email] From d43e27bcd2b16720e40d4d8d99425620fb9c8062 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 21:13:06 +0100 Subject: [PATCH 031/104] Fix data export specs --- spec/lib/use_case/data_export/user_spec.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/lib/use_case/data_export/user_spec.rb b/spec/lib/use_case/data_export/user_spec.rb index f9c70ff9..1ee9cf45 100644 --- a/spec/lib/use_case/data_export/user_spec.rb +++ b/spec/lib/use_case/data_export/user_spec.rb @@ -22,6 +22,9 @@ describe UseCase::DataExport::User, :data_export do privacy_allow_public_timeline: false, privacy_allow_stranger_answers: false, privacy_show_in_search: true, + sharing_enabled: false, + sharing_autoclose: false, + sharing_custom_url: nil, screen_name: "fizzyraccoon", show_foreign_themes: true, sign_in_count: 10, @@ -85,7 +88,10 @@ describe UseCase::DataExport::User, :data_export do privacy_lock_inbox: false, privacy_require_user: false, privacy_hide_social_graph: false, - privacy_noindex: false + privacy_noindex: false, + sharing_enabled: false, + sharing_autoclose: false, + sharing_custom_url: nil, }, profile: { display_name: "Fizzy Raccoon", From 024127e62fdff78f638a3864624c9439cf56605d Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 21:13:37 +0100 Subject: [PATCH 032/104] Remove connected user transformation from TwitteredMarkdown --- app/services/twittered_markdown.rb | 12 +---------- spec/helpers/markdown_helper_spec.rb | 30 +++------------------------- 2 files changed, 4 insertions(+), 38 deletions(-) diff --git a/app/services/twittered_markdown.rb b/app/services/twittered_markdown.rb index 8787c1b4..ef71472b 100644 --- a/app/services/twittered_markdown.rb +++ b/app/services/twittered_markdown.rb @@ -7,17 +7,7 @@ class TwitteredMarkdown < Redcarpet::Render::StripDown def wrap_mentions(text) text.gsub(/(^|\s)@([a-zA-Z0-9_]{1,16})/) do - local_user = User.find_by(screen_name: $2) - if local_user.nil? - "#{$1}#{$2}" - else - service = local_user.services.where(type: "Services::Twitter").first - if service.nil? - "#{$1}#{$2}" - else - "#{$1}@#{service.nickname}" - end - end + "#{$1}#{$2}" end end end diff --git a/spec/helpers/markdown_helper_spec.rb b/spec/helpers/markdown_helper_spec.rb index b5ecf606..177b42c7 100644 --- a/spec/helpers/markdown_helper_spec.rb +++ b/spec/helpers/markdown_helper_spec.rb @@ -40,24 +40,8 @@ describe MarkdownHelper, type: :helper do end describe "#twitter_markdown" do - context "mentioned user has Twitter connected" do - let(:user) { FactoryBot.create(:user) } - let(:service) { Services::Twitter.create(user: user) } - - before do - service.nickname = "test" - service.save! - end - - it "should transform a internal mention to a Twitter mention" do - expect(twitter_markdown("@#{user.screen_name}")).to eq("@test") - end - end - - context "mentioned user doesnt have Twitter connected" do - it "should not transform the mention" do - expect(twitter_markdown("@test")).to eq("test") - end + it "should not transform the mention" do + expect(twitter_markdown("@test")).to eq("test") end it "should not strip weird hearts" do @@ -118,17 +102,9 @@ describe MarkdownHelper, type: :helper do describe "#twitter_markdown_io" do let(:user) { FactoryBot.create(:user) } - let(:service) { Services::Twitter.create(user: user) } - - before do - user.screen_name = "test" - user.save! - service.nickname = "ObamaGaming" - service.save! - end it "should return the expected text" do - expect(twitter_markdown_io("spec/fixtures/markdown/twitter.md")).to eq("@ObamaGaming") + expect(twitter_markdown_io("spec/fixtures/markdown/twitter.md")).to eq("test") end end From 2cb98fc2e057bebb25154c0315f9ea197d85dc2a Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 21:14:04 +0100 Subject: [PATCH 033/104] Hide the custom share button if the URL is blank --- app/views/inbox/_entry.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/inbox/_entry.html.haml b/app/views/inbox/_entry.html.haml index 29fcfaa7..ef3c8cbb 100644 --- a/app/views/inbox/_entry.html.haml +++ b/app/views/inbox/_entry.html.haml @@ -49,7 +49,7 @@ %a.btn.btn-primary{ href: "#", data: { inbox_sharing_target: "tumblr" }, target: "_blank" } %i.fab.fa-tumblr.fa-fw Tumblr - - if current_user.sharing_custom_url + - unless current_user.sharing_custom_url.blank? %a.btn.btn-primary{ href: current_user.sharing_custom_url, data: { inbox_sharing_target: "custom" }, target: "_blank" } = current_user.display_sharing_custom_url %p.text-muted= t(".sharing.hint_html", settings: link_to(t(".sharing.settings"), settings_sharing_path)) From b0f529424335f40899ef86e704376cd8eacb8e03 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 21:30:16 +0100 Subject: [PATCH 034/104] Appease the dog overlords --- app/views/inbox/_entry.html.haml | 2 +- spec/lib/use_case/data_export/user_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/inbox/_entry.html.haml b/app/views/inbox/_entry.html.haml index ef3c8cbb..769d4f10 100644 --- a/app/views/inbox/_entry.html.haml +++ b/app/views/inbox/_entry.html.haml @@ -49,7 +49,7 @@ %a.btn.btn-primary{ href: "#", data: { inbox_sharing_target: "tumblr" }, target: "_blank" } %i.fab.fa-tumblr.fa-fw Tumblr - - unless current_user.sharing_custom_url.blank? + - if current_user.sharing_custom_url.present? %a.btn.btn-primary{ href: current_user.sharing_custom_url, data: { inbox_sharing_target: "custom" }, target: "_blank" } = current_user.display_sharing_custom_url %p.text-muted= t(".sharing.hint_html", settings: link_to(t(".sharing.settings"), settings_sharing_path)) diff --git a/spec/lib/use_case/data_export/user_spec.rb b/spec/lib/use_case/data_export/user_spec.rb index 1ee9cf45..9f01e1ef 100644 --- a/spec/lib/use_case/data_export/user_spec.rb +++ b/spec/lib/use_case/data_export/user_spec.rb @@ -91,7 +91,7 @@ describe UseCase::DataExport::User, :data_export do privacy_noindex: false, sharing_enabled: false, sharing_autoclose: false, - sharing_custom_url: nil, + sharing_custom_url: nil }, profile: { display_name: "Fizzy Raccoon", From 2ba2367e7e335cb98bb00b7f99786b7e40a2e6fb Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 5 Feb 2023 21:32:03 +0100 Subject: [PATCH 035/104] Apply suggestions from @raccube Co-authored-by: Karina Kwiatek <6197148+raccube@users.noreply.github.com> --- config/locales/activerecord.en.yml | 2 +- config/locales/views.en.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml index 779d1227..c8066090 100644 --- a/config/locales/activerecord.en.yml +++ b/config/locales/activerecord.en.yml @@ -75,7 +75,7 @@ en: privacy_noindex: "Prevent search engines from indexing your profile" privacy_hide_social_graph: "Hide your social graph from others" sharing_enabled: "Enable sharing" - sharing_autoclose: "Automatically close inbox entry after sharing once" + sharing_autoclose: "Automatically hide inbox entry after sharing once" sharing_custom_url: "Custom Share Link" profile_picture: "Profile picture" profile_header: "Profile header" diff --git a/config/locales/views.en.yml b/config/locales/views.en.yml index 9ebb0aaf..65fcdee3 100644 --- a/config/locales/views.en.yml +++ b/config/locales/views.en.yml @@ -216,8 +216,8 @@ en: sharing: heading: "Share answer on..." post_to: "Post to %{service}" - hint_html: "You can customise sharing in your %{settings}" - settings: "settings" + hint_html: "You can customise these options in your %{settings}" + settings: "sharing settings" show: empty: "Nothing to see here." actions: From 2ba321755152b5b5d4560eb4cca7daaf1d3adb4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 09:00:54 +0000 Subject: [PATCH 036/104] Bump faker from 3.1.0 to 3.1.1 Bumps [faker](https://github.com/faker-ruby/faker) from 3.1.0 to 3.1.1. - [Release notes](https://github.com/faker-ruby/faker/releases) - [Changelog](https://github.com/faker-ruby/faker/blob/main/CHANGELOG.md) - [Commits](https://github.com/faker-ruby/faker/compare/v3.1.0...v3.1.1) --- updated-dependencies: - dependency-name: faker dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 41852740..98736c46 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -155,7 +155,7 @@ GEM fake_email_validator (1.0.11) activemodel mail - faker (3.1.0) + faker (3.1.1) i18n (>= 1.8.11, < 2) ffi (1.15.5) fog-aws (3.16.0) From bb7588e7e166c1101fd2483ce58bbfc656cf3169 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 09:01:10 +0000 Subject: [PATCH 037/104] Bump jwt from 2.6.0 to 2.7.0 Bumps [jwt](https://github.com/jwt/ruby-jwt) from 2.6.0 to 2.7.0. - [Release notes](https://github.com/jwt/ruby-jwt/releases) - [Changelog](https://github.com/jwt/ruby-jwt/blob/main/CHANGELOG.md) - [Commits](https://github.com/jwt/ruby-jwt/compare/v2.6.0...v2.7.0) --- updated-dependencies: - dependency-name: jwt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index b4a30a43..76514d2d 100644 --- a/Gemfile +++ b/Gemfile @@ -67,7 +67,7 @@ gem "fake_email_validator" # TLD validation gem "tldv", "~> 0.1.0" -gem "jwt", "~> 2.6" +gem "jwt", "~> 2.7" group :development do gem "binding_of_caller" diff --git a/Gemfile.lock b/Gemfile.lock index 41852740..b539db07 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -210,7 +210,7 @@ GEM json (2.6.3) json-schema (3.0.0) addressable (>= 2.8) - jwt (2.6.0) + jwt (2.7.0) kaminari (1.2.2) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.2) @@ -509,7 +509,7 @@ DEPENDENCIES i18n-js (= 4.0) jsbundling-rails (~> 1.1) json-schema - jwt (~> 2.6) + jwt (~> 2.7) letter_opener lograge mail (~> 2.7.1) From 166cf30084c333c07cf3a39331b150b32452bbb8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 09:01:44 +0000 Subject: [PATCH 038/104] Bump oj from 3.13.23 to 3.14.1 Bumps [oj](https://github.com/ohler55/oj) from 3.13.23 to 3.14.1. - [Release notes](https://github.com/ohler55/oj/releases) - [Changelog](https://github.com/ohler55/oj/blob/develop/CHANGELOG.md) - [Commits](https://github.com/ohler55/oj/compare/v3.13.23...v3.14.1) --- updated-dependencies: - dependency-name: oj dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 41852740..773b2073 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -270,7 +270,7 @@ GEM nokogiri (1.14.0) mini_portile2 (~> 2.8.0) racc (~> 1.4) - oj (3.13.23) + oj (3.14.1) openssl (3.1.0) orm_adapter (0.5.0) parallel (1.22.1) From 033c3490cba4bba16a883ac4c8c4b1e46dcd5638 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 09:02:14 +0000 Subject: [PATCH 039/104] Bump rolify from 6.0.0 to 6.0.1 Bumps [rolify](https://github.com/RolifyCommunity/rolify) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/RolifyCommunity/rolify/releases) - [Changelog](https://github.com/RolifyCommunity/rolify/blob/master/CHANGELOG.rdoc) - [Commits](https://github.com/RolifyCommunity/rolify/compare/v6.0.0...v6.0.1) --- updated-dependencies: - dependency-name: rolify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 41852740..2339ac73 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -339,7 +339,7 @@ GEM actionpack (>= 5.0) railties (>= 5.0) rexml (3.2.5) - rolify (6.0.0) + rolify (6.0.1) rotp (6.2.0) rpush (7.0.1) activesupport (>= 5.2) From 0bda9fa72d2c9deb0fe7b4dab22d8947822c21f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 09:02:14 +0000 Subject: [PATCH 040/104] Bump @hotwired/turbo-rails from 7.2.4 to 7.2.5 Bumps [@hotwired/turbo-rails](https://github.com/hotwired/turbo-rails) from 7.2.4 to 7.2.5. - [Release notes](https://github.com/hotwired/turbo-rails/releases) - [Commits](https://github.com/hotwired/turbo-rails/commits) --- updated-dependencies: - dependency-name: "@hotwired/turbo-rails" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 2f4499f2..82b692ff 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "@fontsource/lexend": "^4.5.15", "@fortawesome/fontawesome-free": "^6.2.1", "@hotwired/stimulus": "^3.2.1", - "@hotwired/turbo-rails": "^7.2.4", + "@hotwired/turbo-rails": "^7.2.5", "@melloware/coloris": "^0.17.1", "@popperjs/core": "^2.11", "@rails/request.js": "^0.0.8", diff --git a/yarn.lock b/yarn.lock index f38e6b8f..77535a42 100644 --- a/yarn.lock +++ b/yarn.lock @@ -189,18 +189,18 @@ resolved "https://registry.yarnpkg.com/@hotwired/stimulus/-/stimulus-3.2.1.tgz#e3de23623b0c52c247aba4cd5d530d257008676b" integrity sha512-HGlzDcf9vv/EQrMJ5ZG6VWNs8Z/xMN+1o2OhV1gKiSG6CqZt5MCBB1gRg5ILiN3U0jEAxuDTNPRfBcnZBDmupQ== -"@hotwired/turbo-rails@^7.2.4": - version "7.2.4" - resolved "https://registry.yarnpkg.com/@hotwired/turbo-rails/-/turbo-rails-7.2.4.tgz#d155533e79c4ebdac23e8fe12697d821d5c06307" - integrity sha512-givDUQqaccd19BvErz1Cf2j6MXF74m0G6I75oqFJGeXAa7vwkz9nDplefVNrALCR9Xi9j9gy32xmSI6wD0tZyA== +"@hotwired/turbo-rails@^7.2.5": + version "7.2.5" + resolved "https://registry.yarnpkg.com/@hotwired/turbo-rails/-/turbo-rails-7.2.5.tgz#74fc3395a29a76df2bb8835aa88c86885cffde4c" + integrity sha512-F8ztmARxd/XBdevRa//HoJGZ7u+Unb0J7cQUeUP+pBvt9Ta2TJJ7a2TORAOhjC8Zgxx+LKwm/1UUHqN3ojjiGw== dependencies: - "@hotwired/turbo" "^7.2.4" + "@hotwired/turbo" "^7.2.5" "@rails/actioncable" "^7.0" -"@hotwired/turbo@^7.2.4": - version "7.2.4" - resolved "https://registry.yarnpkg.com/@hotwired/turbo/-/turbo-7.2.4.tgz#0d35541be32cfae3b4f78c6ab9138f5b21f28a21" - integrity sha512-c3xlOroHp/cCZHDOuLp6uzQYEbvXBUVaal0puXoGJ9M8L/KHwZ3hQozD4dVeSN9msHWLxxtmPT1TlCN7gFhj4w== +"@hotwired/turbo@^7.2.5": + version "7.2.5" + resolved "https://registry.yarnpkg.com/@hotwired/turbo/-/turbo-7.2.5.tgz#2d9d6bde8a9549c3aea8970445ade16ffd56719a" + integrity sha512-o5PByC/mWkmTe4pWnKrixhPECJUxIT/NHtxKqjq7n9Fj6JlNza1pgxdTCJVIq+PI0j95U+7mA3N4n4A/QYZtZQ== "@humanwhocodes/config-array@^0.5.0": version "0.5.0" From 98ee37094eeec37016cedd68b018576ad64c1f48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 09:02:26 +0000 Subject: [PATCH 041/104] Bump sass from 1.57.1 to 1.58.0 Bumps [sass](https://github.com/sass/dart-sass) from 1.57.1 to 1.58.0. - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.57.1...1.58.0) --- updated-dependencies: - dependency-name: sass dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 2f4499f2..b9ba7ede 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "croppr": "^2.3.1", "i18n-js": "^4.0", "js-cookie": "2.2.1", - "sass": "^1.57.1", + "sass": "^1.58.0", "sweetalert": "1.1.3", "toastify-js": "^1.12.0", "typescript": "^4.9.4" diff --git a/yarn.lock b/yarn.lock index f38e6b8f..97e7174b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2110,10 +2110,10 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" -sass@^1.57.1: - version "1.57.1" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.57.1.tgz#dfafd46eb3ab94817145e8825208ecf7281119b5" - integrity sha512-O2+LwLS79op7GI0xZ8fqzF7X2m/m8WFfI02dHOdsK5R2ECeS5F62zrwg/relM1rjSLy7Vd/DiMNIvPrQGsA0jw== +sass@^1.58.0: + version "1.58.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.58.0.tgz#ee8aea3ad5ea5c485c26b3096e2df6087d0bb1cc" + integrity sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -2176,12 +2176,7 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -"source-map-js@>=0.6.2 <2.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf" - integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA== - -source-map-js@^1.0.2: +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== From bbfbaede08b243fef1cceb38ac8a748fae73496f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 09:02:34 +0000 Subject: [PATCH 042/104] Bump turbo-rails from 1.3.2 to 1.3.3 Bumps [turbo-rails](https://github.com/hotwired/turbo-rails) from 1.3.2 to 1.3.3. - [Release notes](https://github.com/hotwired/turbo-rails/releases) - [Commits](https://github.com/hotwired/turbo-rails/compare/v1.3.2...v1.3.3) --- updated-dependencies: - dependency-name: turbo-rails dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 41852740..9f127eed 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -267,7 +267,7 @@ GEM net-smtp (0.3.3) net-protocol nio4r (2.5.8) - nokogiri (1.14.0) + nokogiri (1.14.1) mini_portile2 (~> 2.8.0) racc (~> 1.4) oj (3.13.23) @@ -451,7 +451,7 @@ GEM tldv (0.1.0) tldv-data (~> 1.0) tldv-data (1.0.2022121701) - turbo-rails (1.3.2) + turbo-rails (1.3.3) actionpack (>= 6.0.0) activejob (>= 6.0.0) railties (>= 6.0.0) From 02df330fd45c8dcbb248cef909ed078e4fc53900 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 09:02:41 +0000 Subject: [PATCH 043/104] Bump typescript from 4.9.4 to 4.9.5 Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.9.4 to 4.9.5. - [Release notes](https://github.com/Microsoft/TypeScript/releases) - [Commits](https://github.com/Microsoft/TypeScript/compare/v4.9.4...v4.9.5) --- updated-dependencies: - dependency-name: typescript dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2f4499f2..b3fef8cf 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "sass": "^1.57.1", "sweetalert": "1.1.3", "toastify-js": "^1.12.0", - "typescript": "^4.9.4" + "typescript": "^4.9.5" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^4.11.0", diff --git a/yarn.lock b/yarn.lock index f38e6b8f..9238a75b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2512,10 +2512,10 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" -typescript@^4.9.4: - version "4.9.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" - integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== +typescript@^4.9.5: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== unbox-primitive@^1.0.1: version "1.0.1" From 9a77b89cda657ac2e51b388b01bde5ae29d17389 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Tue, 7 Feb 2023 07:38:59 +0100 Subject: [PATCH 044/104] use remote_ip 2: electric boogaloo --- app/controllers/ajax/question_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/ajax/question_controller.rb b/app/controllers/ajax/question_controller.rb index 5d10cf2f..f3bef810 100644 --- a/app/controllers/ajax/question_controller.rb +++ b/app/controllers/ajax/question_controller.rb @@ -31,7 +31,7 @@ class Ajax::QuestionController < AjaxController UseCase::Question::CreateFollowers.call( source_user_id: current_user.id, content: params[:question], - author_identifier: AnonymousBlock.get_identifier(request.ip) + author_identifier: AnonymousBlock.get_identifier(request.remote_ip) ) return end From a59bd20456cc2ca78f9dd0ec2413fcb3d91b516a Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Tue, 7 Feb 2023 18:54:19 +0100 Subject: [PATCH 045/104] Update carrierwave_backgrounder for compatibility with Sidekiq 7 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 76514d2d..8077ea4e 100644 --- a/Gemfile +++ b/Gemfile @@ -22,7 +22,7 @@ gem "active_model_otp" gem "bootsnap", require: false gem "bootstrap_form", "~> 5.0" gem "carrierwave", "~> 2.0" -gem "carrierwave_backgrounder", git: "https://github.com/mltnhm/carrierwave_backgrounder.git" +gem "carrierwave_backgrounder", git: "https://github.com/raccube/carrierwave_backgrounder.git" gem "colorize" gem "devise", "~> 4.0" gem "devise-async" diff --git a/Gemfile.lock b/Gemfile.lock index ec3cbbaa..424c92b5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT - remote: https://github.com/mltnhm/carrierwave_backgrounder.git - revision: 8fe468957f047ad7039f07679e5952a534d07b6d + remote: https://github.com/raccube/carrierwave_backgrounder.git + revision: 41b756f7514c0e410c561bc8b5ee321cd8cce1ee specs: carrierwave_backgrounder (0.4.2) carrierwave (>= 0.5, <= 2.1) From 59c0dce9b4e90732f155c99be5e433f23e8638c0 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Tue, 7 Feb 2023 23:39:13 +0100 Subject: [PATCH 046/104] Disable relationship action buttons while waiting for a response --- app/javascript/retrospring/features/user/action.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/javascript/retrospring/features/user/action.ts b/app/javascript/retrospring/features/user/action.ts index d9605c3e..91ad152e 100644 --- a/app/javascript/retrospring/features/user/action.ts +++ b/app/javascript/retrospring/features/user/action.ts @@ -7,6 +7,7 @@ export function userActionHandler(event: Event): void { const button: HTMLButtonElement = event.target as HTMLButtonElement; const target = button.dataset.target; const action = button.dataset.action; + button.disabled = true; let targetURL, relationshipType; @@ -56,6 +57,7 @@ export function userActionHandler(event: Event): void { showErrorNotification(I18n.translate('frontend.error.message')); }) .finally(() => { + button.disabled = false; if (!success) return; switch (action) { From 24f9bd4bb6c2f49260b13126e9a454486d1a269d Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Fri, 10 Feb 2023 14:24:23 +0100 Subject: [PATCH 047/104] add workflow to automatically build and publish a container image --- .github/workflows/build-image.yml | 57 +++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/build-image.yml diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml new file mode 100644 index 00000000..32d8f2d0 --- /dev/null +++ b/.github/workflows/build-image.yml @@ -0,0 +1,57 @@ +--- +name: Build container image + +on: + workflow_dispatch: + push: + branches: [ main ] + tags: [ '*' ] + pull_request: + paths: + - .github/workflows/build-image.yml + - Containerfile + +jobs: + build-image: + runs-on: ubuntu-latest + + concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + + steps: + - uses: actions/checkout@v3.3.0 + + - name: Discover build-time variables + run: | + echo "RUBY_VERSION=$(cat .ruby-version)" >> $GITHUB_ENV + echo "BUNDLER_VERSION=$(egrep -A1 "^BUNDLED WITH" Gemfile.lock | tr -d '\n' | awk '{ print $3; }')" >> $GITHUB_ENV + case "${{ github.ref_name }}" in + */merge) + # use commit id as version for pull requests + echo "RETROSPRING_VERSION=${{ github.sha }}" >> $GITHUB_ENV + ;; + *) + # use tags and branches as version otherwise + echo "RETROSPRING_VERSION=${{ github.ref_name }}" >> $GITHUB_ENV + ;; + esac + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + if: github.event_name != 'pull_request' + + - name: Build and push + uses: docker/build-push-action@v4 + with: + build-args: | + BUNDLER_VERSION=${{ env.BUNDLER_VERSION }} + RETROSPRING_VERSION=${{ env.RETROSPRING_VERSION }} + RUBY_VERSION=${{ env.RUBY_VERSION }} + context: . + file: Containerfile + push: ${{ github.event_name != 'pull_request' }} + tags: retrospring/retrospring:${{ env.RETROSPRING_VERSION }} From 92b6d4323028b558b58fe37b8b87d44360921c0c Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Fri, 10 Feb 2023 14:41:01 +0100 Subject: [PATCH 048/104] Containerfile: download archive without tag requirement --- Containerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Containerfile b/Containerfile index 637aebee..2e740498 100644 --- a/Containerfile +++ b/Containerfile @@ -57,7 +57,7 @@ WORKDIR /opt/retrospring/app USER justask:users # install the app -RUN curl -L https://github.com/Retrospring/retrospring/archive/refs/tags/${RETROSPRING_VERSION}.tar.gz | tar xz --strip-components=1 +RUN curl -L https://github.com/Retrospring/retrospring/archive/${RETROSPRING_VERSION}.tar.gz | tar xz --strip-components=1 RUN bundle config set without 'development test' \ && bundle config set path '/opt/retrospring/bundle' \ From 606629577aca6b2d72776ed29a1342cf39967d67 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Fri, 10 Feb 2023 20:48:15 +0100 Subject: [PATCH 049/104] make URI.parse part of the validation for the sharing URL the regexp alone and web browsers allows URLs to contain non-ASCII characters, which `URI.parse` does not like -- resulting in the inbox page to suddenly break. also changed the `redirect_to` in the controller to a `render :edit` so that validation errors are shown properly --- .../settings/sharing_controller.rb | 7 ++++--- app/models/user.rb | 2 +- app/validators/valid_url_validator.rb | 21 +++++++++++++++++++ config/locales/activerecord.en.yml | 3 +++ spec/models/user_spec.rb | 2 ++ 5 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 app/validators/valid_url_validator.rb diff --git a/app/controllers/settings/sharing_controller.rb b/app/controllers/settings/sharing_controller.rb index a92bd364..76a7ca76 100644 --- a/app/controllers/settings/sharing_controller.rb +++ b/app/controllers/settings/sharing_controller.rb @@ -10,10 +10,11 @@ class Settings::SharingController < ApplicationController :sharing_autoclose, :sharing_custom_url) if current_user.update(user_attributes) - flash[:success] = t(".success") + flash.now[:success] = t(".success") else - flash[:error] = t(".error") + flash.now[:error] = t(".error") end - redirect_to settings_sharing_path + + render :edit end end diff --git a/app/models/user.rb b/app/models/user.rb index 7a57479d..6370ed01 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -61,7 +61,7 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength end validates :email, fake_email: true, typoed_email: true - validates :sharing_custom_url, format: URI::DEFAULT_PARSER.make_regexp(%w[http https]), allow_blank: true + validates :sharing_custom_url, allow_blank: true, valid_url: true validates :screen_name, presence: true, format: { with: SCREEN_NAME_REGEX, message: I18n.t("activerecord.validation.user.screen_name.format") }, diff --git a/app/validators/valid_url_validator.rb b/app/validators/valid_url_validator.rb new file mode 100644 index 00000000..0d29426c --- /dev/null +++ b/app/validators/valid_url_validator.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class ValidUrlValidator < ActiveModel::EachValidator + URI_REGEXP = URI::DEFAULT_PARSER.make_regexp(%w[http https]).freeze + + def validate_each(record, attribute, value) + return if valid?(value) + + record.errors.add(attribute, :invalid_url) + end + + def valid?(value) + return false unless URI_REGEXP.match?(value) + + URI.parse(value) # raises URI::InvalidURIError + + true + rescue URI::InvalidURIError + false + end +end diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml index c8066090..d7a879ae 100644 --- a/config/locales/activerecord.en.yml +++ b/config/locales/activerecord.en.yml @@ -104,6 +104,9 @@ en: user: screen_name: format: "contains invalid characters" + errors: + messages: + invalid_url: "does not look like a valid URL" helpers: submit: user: diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 3263fa86..01d3fc47 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -63,6 +63,8 @@ RSpec.describe User, type: :model do include_examples "valid url", "http://insecurebutvalid.business/" include_examples "invalid url", "ftp://fileprotocols.cool/" include_examples "invalid url", "notevenanurl" + include_examples "invalid url", %(https://richtig oarger shice) # passes the regexp, but trips up URI.parse + include_examples "invalid url", %(https://österreich.gv.at) # needs to be ASCII end describe "email validation" do From 71be21cccc43fcad610dc8509ae531abb522212e Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Fri, 10 Feb 2023 21:16:21 +0100 Subject: [PATCH 050/104] add specs for Settings::SharingController --- .../settings/sharing_controller_spec.rb | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 spec/controllers/settings/sharing_controller_spec.rb diff --git a/spec/controllers/settings/sharing_controller_spec.rb b/spec/controllers/settings/sharing_controller_spec.rb new file mode 100644 index 00000000..6755845a --- /dev/null +++ b/spec/controllers/settings/sharing_controller_spec.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe Settings::SharingController, type: :controller do + describe "#edit" do + subject { get :edit } + + it "redirects to the sign in page when not signed in" do + expect(subject).to redirect_to new_user_session_path + end + + context "user signed in" do + let(:user) { FactoryBot.create(:user) } + + before { sign_in user } + + it "renders the edit template" do + expect(subject).to render_template(:edit) + end + end + end + + describe "#update" do + subject { patch :update, params: { user: user_params } } + let(:user_params) do + { + sharing_enabled: "1", + sharing_autoclose: "1", + sharing_custom_url: "", + } + end + + it "redirects to the sign in page when not signed in" do + expect(subject).to redirect_to new_user_session_path + end + + context "user signed in" do + let(:user) { FactoryBot.create :user } + + before { sign_in user } + + it "renders the edit template" do + expect(subject).to render_template(:edit) + end + + it "updates the user's sharing preferences" do + expect { subject } + .to change { user.reload.slice(:sharing_enabled, :sharing_autoclose, :sharing_custom_url).values } + .from([false, false, nil]) + .to([true, true, ""]) + end + + it "sets the success flash" do + subject + expect(flash[:success]).to eq(I18n.t("settings.sharing.update.success")) + end + + context "when passed a valid url" do + let(:user_params) do + super().merge( + sharing_custom_url: "https://example.com/share?text=" + ) + end + + it "renders the edit template" do + expect(subject).to render_template(:edit) + end + + it "updates the user's sharing preferences" do + expect { subject } + .to change { user.reload.slice(:sharing_enabled, :sharing_autoclose, :sharing_custom_url).values } + .from([false, false, nil]) + .to([true, true, "https://example.com/share?text="]) + end + + it "sets the success flash" do + subject + expect(flash[:success]).to eq(I18n.t("settings.sharing.update.success")) + end + end + + context "when passed an invalid url" do + let(:user_params) do + super().merge( + sharing_custom_url: "nfs://example.com/data" + ) + end + + it "renders the edit template" do + expect(subject).to render_template(:edit) + end + + it "updates the user's sharing preferences" do + expect { subject } + .not_to(change { user.reload.slice(:sharing_enabled, :sharing_autoclose, :sharing_custom_url).values }) + end + + it "sets the error flash" do + subject + expect(flash[:error]).to eq(I18n.t("settings.sharing.update.error")) + end + end + + context "when unticking boolean settings" do + let(:user_params) do + super().merge( + sharing_enabled: "0", + sharing_autoclose: "0" + ) + end + + let(:user) { FactoryBot.create :user, sharing_enabled: true, sharing_autoclose: true, sharing_custom_url: "https://example.com/share?text=" } + + it "renders the edit template" do + expect(subject).to render_template(:edit) + end + + it "updates the user's sharing preferences" do + expect { subject } + .to change { user.reload.slice(:sharing_enabled, :sharing_autoclose, :sharing_custom_url).values } + .from([true, true, "https://example.com/share?text="]) + .to([false, false, ""]) + end + + it "sets the success flash" do + subject + expect(flash[:success]).to eq(I18n.t("settings.sharing.update.success")) + end + end + end + end +end From 7dfa65f13980b64a0d1199c62963a954d727d13a Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sat, 11 Feb 2023 07:11:38 +0100 Subject: [PATCH 051/104] Add default database/redis connections to docker-compose --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 0bdc353d..ae852632 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,8 @@ services: - redis environment: - SPROCKETS_CACHE=/cache + - DATABASE_URL=postgres://postgres:justask@postgres/justask_development?pool=25 + - REDIS_URL=redis://redis:6379 volumes: - ./:/app - cache:/cache From 22013791c3cec859ee030a70601c18f0bcea998b Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 12 Feb 2023 16:41:18 +0100 Subject: [PATCH 052/104] Add dependency installation steps to Ruby-based linters --- .github/workflows/lint.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 321573da..4916381e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,6 +12,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3.3.0 + - name: Install dependencies + run: sudo apt update && sudo apt-get install -y libpq-dev libxml2-dev libxslt1-dev libmagickwand-dev imagemagick libidn11-dev - name: Set up Ruby uses: ruby/setup-ruby@v1 with: @@ -46,6 +48,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3.3.0 + - name: Install dependencies + run: sudo apt update && sudo apt-get install -y libpq-dev libxml2-dev libxslt1-dev libmagickwand-dev imagemagick libidn11-dev - name: Set up Ruby uses: ruby/setup-ruby@v1 with: From 42a78cd286bd13aef8f000e275bda916207fd827 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Sat, 11 Feb 2023 23:51:58 +0100 Subject: [PATCH 053/104] Provide a way of using Redis for Rails cache --- Gemfile | 1 + Gemfile.lock | 1 + config/environments/production.rb | 14 +++++++++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 8077ea4e..abf9e2e9 100644 --- a/Gemfile +++ b/Gemfile @@ -60,6 +60,7 @@ gem "sanitize" gem "twitter-text" +gem "connection_pool" gem "redis" gem "fake_email_validator" diff --git a/Gemfile.lock b/Gemfile.lock index 424c92b5..96a87385 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -489,6 +489,7 @@ DEPENDENCIES carrierwave (~> 2.0) carrierwave_backgrounder! colorize + connection_pool cssbundling-rails (~> 1.1) database_cleaner devise (~> 4.0) diff --git a/config/environments/production.rb b/config/environments/production.rb index 3ae984d5..6fe7f142 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -54,7 +54,19 @@ Rails.application.configure do config.lograge.enabled = true # Use a different cache store in production. - # config.cache_store = :mem_cache_store + cache_redis_url = ENV.fetch("CACHE_REDIS_URL") { nil } + if cache_redis_url.present? + config.cache_store = :redis_cache_store, { + url: cache_redis_url, + pool_size: ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i, + pool_timeout: ENV.fetch("CACHE_REDIS_TIMEOUT") { 5 }, + error_handler: -> (method:, returning:, exception:) { + # Report errors to Sentry as warnings + Sentry.capture_exception exception, level: 'warning', + tags: { method: method, returning: returning } + }, + } + end # Use a real queuing backend for Active Job (and separate queues per environment) # config.active_job.queue_adapter = :resque From 615c60d4207cbdf525569de2a38fed66b312ea13 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Mon, 6 Feb 2023 15:35:57 +0100 Subject: [PATCH 054/104] Highlight direct questions in own question lists --- app/views/moderation/questions/show.html.haml | 2 +- app/views/shared/_question.html.haml | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/views/moderation/questions/show.html.haml b/app/views/moderation/questions/show.html.haml index 8f174ba0..48617bf2 100644 --- a/app/views/moderation/questions/show.html.haml +++ b/app/views/moderation/questions/show.html.haml @@ -5,4 +5,4 @@ .container-lg.container--main - @questions.each do |q| - = render 'shared/question', q: q, type: nil + = render "shared/question", q:, type: "moderation" diff --git a/app/views/shared/_question.html.haml b/app/views/shared/_question.html.haml index 354f8a6b..a5094207 100644 --- a/app/views/shared/_question.html.haml +++ b/app/views/shared/_question.html.haml @@ -1,8 +1,17 @@ - type ||= nil .card.questionbox{ data: { id: q.id } } + - if type.nil? && q.direct + - if user_signed_in? && q.user == current_user + .card-header + %i.fa.fa-eye-slash + Only you can see this + - elsif moderation_view? + .card-header + %i.fa.fa-eye-slash + You can see this because you are in moderation view .card-body{ data: { controller: q.long? ? "collapse" : nil } } .d-flex - - if type == 'discover' + - if type == "discover" .flex-shrink-0 %a{ href: user_screen_name(q.user, link_only: true) } %img.avatar-md.me-2{ src: q.user&.profile_picture&.url(:small), loading: :lazy } @@ -13,7 +22,7 @@ · %a{ href: question_path(q.user.screen_name, q.id) } = pluralize(q.answer_count, t("voc.answer")) - .answerbox__question-text{ class: q.long? ? 'collapsed' : '', data: { collapse_target: "content" } } + .answerbox__question-text{ class: q.long? ? "collapsed" : "", data: { collapse_target: "content" } } = question_markdown q.content - if q.long? = render "shared/collapse", type: "question" From dd3f86988acb1c01b0af90a53bad81f3b213b562 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Mon, 6 Feb 2023 18:54:34 +0100 Subject: [PATCH 055/104] Localise strings for question visibility --- app/views/shared/_question.html.haml | 4 ++-- config/locales/views.en.yml | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/views/shared/_question.html.haml b/app/views/shared/_question.html.haml index a5094207..e66fddc8 100644 --- a/app/views/shared/_question.html.haml +++ b/app/views/shared/_question.html.haml @@ -4,11 +4,11 @@ - if user_signed_in? && q.user == current_user .card-header %i.fa.fa-eye-slash - Only you can see this + = t(".visible_to_you") - elsif moderation_view? .card-header %i.fa.fa-eye-slash - You can see this because you are in moderation view + = t(".visible_mod_mode") .card-body{ data: { controller: q.long? ? "collapse" : nil } } .d-flex - if type == "discover" diff --git a/config/locales/views.en.yml b/config/locales/views.en.yml index 65fcdee3..008f5f61 100644 --- a/config/locales/views.en.yml +++ b/config/locales/views.en.yml @@ -578,6 +578,9 @@ en: anonymous_block: deleted_question: "Deleted question" blocked: "blocked %{time} ago" + question: + visible_to_you: "Only visible to you" + visible_mod_mode: "You can see this because you are in moderation view" tabs: admin: announcements: "Announcements" From 0c3e54de6bb424fac8e217b167386b23ad038779 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Sat, 11 Feb 2023 21:23:07 +0100 Subject: [PATCH 056/104] Use icons with tooltips instead --- app/views/shared/_question.html.haml | 14 +++++--------- config/locales/views.en.yml | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/views/shared/_question.html.haml b/app/views/shared/_question.html.haml index e66fddc8..e1a4df80 100644 --- a/app/views/shared/_question.html.haml +++ b/app/views/shared/_question.html.haml @@ -1,14 +1,5 @@ - type ||= nil .card.questionbox{ data: { id: q.id } } - - if type.nil? && q.direct - - if user_signed_in? && q.user == current_user - .card-header - %i.fa.fa-eye-slash - = t(".visible_to_you") - - elsif moderation_view? - .card-header - %i.fa.fa-eye-slash - = t(".visible_mod_mode") .card-body{ data: { controller: q.long? ? "collapse" : nil } } .d-flex - if type == "discover" @@ -17,6 +8,11 @@ %img.avatar-md.me-2{ src: q.user&.profile_picture&.url(:small), loading: :lazy } .flex-grow-1 %h6.text-muted.answerbox__question-user + - if type.nil? && q.direct + - if user_signed_in? && q.user == current_user + %i.fa.fa-eye-slash{ data: { bs_toggle: "tooltip", bs_title: t(".visible_to_you") } } + - elsif moderation_view? + %i.fa.fa-eye-slash{ data: { bs_toggle: "tooltip", bs_title: t(".visible_mod_mode") } } = t("answerbox.header.asked_html", user: user_screen_name(q.user), time: time_tooltip(q)) - if q.answer_count > 1 · diff --git a/config/locales/views.en.yml b/config/locales/views.en.yml index 008f5f61..ea82b1dc 100644 --- a/config/locales/views.en.yml +++ b/config/locales/views.en.yml @@ -579,7 +579,7 @@ en: deleted_question: "Deleted question" blocked: "blocked %{time} ago" question: - visible_to_you: "Only visible to you" + visible_to_you: "Only visible to you as it was asked directly" visible_mod_mode: "You can see this because you are in moderation view" tabs: admin: From 484badb555a715e2a44bdcbc9050a367ebf6ae67 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 12 Feb 2023 17:36:25 +0100 Subject: [PATCH 057/104] Remove services reference from inbox Turbo Stream view --- app/views/inbox/show.turbo_stream.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/inbox/show.turbo_stream.haml b/app/views/inbox/show.turbo_stream.haml index ce09c7da..fffc48fc 100644 --- a/app/views/inbox/show.turbo_stream.haml +++ b/app/views/inbox/show.turbo_stream.haml @@ -1,6 +1,6 @@ = turbo_stream.append "entries" do - @inbox.each do |i| - = render "inbox/entry", services:, i: + = render "inbox/entry", i: = turbo_stream.update "paginator" do - if @more_data_available From 065d35c288ae53185f42953f7432e0bf4b1d99b9 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Sun, 12 Feb 2023 19:04:01 +0100 Subject: [PATCH 058/104] well_known/node_info: remove twitter as outbound service --- .../well_known/node_info_controller.rb | 18 ++++---- .../well_known/node_info_controller_spec.rb | 42 ++----------------- 2 files changed, 10 insertions(+), 50 deletions(-) diff --git a/app/controllers/well_known/node_info_controller.rb b/app/controllers/well_known/node_info_controller.rb index 6633b137..c4031c69 100644 --- a/app/controllers/well_known/node_info_controller.rb +++ b/app/controllers/well_known/node_info_controller.rb @@ -8,7 +8,7 @@ class WellKnown::NodeInfoController < ApplicationController links: [ rel: "http://nodeinfo.diaspora.software/ns/schema/2.1", href: node_info_url - ] + ], } end @@ -21,12 +21,12 @@ class WellKnown::NodeInfoController < ApplicationController protocols: %i[], services: { inbound: inbound_services, - outbound: outbound_services + outbound: outbound_services, }, usage: usage_stats, # We don't implement this so we can always return true for now openRegistrations: true, - metadata: {} + metadata: {}, } end @@ -36,23 +36,19 @@ class WellKnown::NodeInfoController < ApplicationController { name: "Retrospring", version: Retrospring::Version.to_s, - repository: "https://github.com/Retrospring/retrospring" + repository: "https://github.com/Retrospring/retrospring", } end def usage_stats { users: { - total: User.count - } + total: User.count, + }, } end def inbound_services = [] - def outbound_services - { - "twitter" => APP_CONFIG.dig("sharing", "twitter", "enabled") - }.select { |_service, available| available }.keys - end + def outbound_services = [] end diff --git a/spec/controllers/well_known/node_info_controller_spec.rb b/spec/controllers/well_known/node_info_controller_spec.rb index ad6a8dce..54d871aa 100644 --- a/spec/controllers/well_known/node_info_controller_spec.rb +++ b/spec/controllers/well_known/node_info_controller_spec.rb @@ -13,9 +13,9 @@ describe WellKnown::NodeInfoController do "links" => [ { "rel" => "http://nodeinfo.diaspora.software/ns/schema/2.1", - "href" => "http://test.host/nodeinfo/2.1" + "href" => "http://test.host/nodeinfo/2.1", } - ] + ], }) end end @@ -44,47 +44,11 @@ describe WellKnown::NodeInfoController do expect(parsed["software"]).to eq({ "name" => "Retrospring", "version" => "2023.0102.1", - "repository" => "https://github.com/Retrospring/retrospring" + "repository" => "https://github.com/Retrospring/retrospring", }) end end - context "Twitter integration enabled" do - before do - stub_const("APP_CONFIG", { - "sharing" => { - "twitter" => { - "enabled" => true - } - } - }) - end - - it "includes Twitter in outbound services" do - subject - parsed = JSON.parse(response.body) - expect(parsed.dig("services", "outbound")).to include("twitter") - end - end - - context "Twitter integration disabled" do - before do - stub_const("APP_CONFIG", { - "sharing" => { - "twitter" => { - "enabled" => false - } - } - }) - end - - it "includes Twitter in outbound services" do - subject - parsed = JSON.parse(response.body) - expect(parsed.dig("services", "outbound")).to_not include("twitter") - end - end - context "site has users" do let(:num_users) { rand(10..50) } From e1aee89be00d035c3630e2b1d55ed2ed34760126 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Sun, 12 Feb 2023 19:18:17 +0100 Subject: [PATCH 059/104] remove expired service connection notifications --- app/models/notification/service_token_expired.rb | 4 ---- app/models/notification/twitter_token_expired.rb | 4 ---- app/models/user/expired_service_connection.rb | 5 ----- app/models/user/expired_twitter_service_connection.rb | 4 ---- .../type/_expiredtwitterserviceconnection.html.haml | 8 -------- config/locales/views.en.yml | 4 ---- ...044_remove_expired_service_connection_notifications.rb | 7 +++++++ db/schema.rb | 2 +- 8 files changed, 8 insertions(+), 30 deletions(-) delete mode 100644 app/models/notification/service_token_expired.rb delete mode 100644 app/models/notification/twitter_token_expired.rb delete mode 100644 app/models/user/expired_service_connection.rb delete mode 100644 app/models/user/expired_twitter_service_connection.rb delete mode 100644 app/views/notifications/type/_expiredtwitterserviceconnection.html.haml create mode 100644 db/migrate/20230212181044_remove_expired_service_connection_notifications.rb diff --git a/app/models/notification/service_token_expired.rb b/app/models/notification/service_token_expired.rb deleted file mode 100644 index bc61a820..00000000 --- a/app/models/notification/service_token_expired.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true - -class Notification::ServiceTokenExpired < Notification -end diff --git a/app/models/notification/twitter_token_expired.rb b/app/models/notification/twitter_token_expired.rb deleted file mode 100644 index 9ddb8447..00000000 --- a/app/models/notification/twitter_token_expired.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true - -class Notification::TwitterTokenExpired < Notification::ServiceTokenExpired -end diff --git a/app/models/user/expired_service_connection.rb b/app/models/user/expired_service_connection.rb deleted file mode 100644 index 110dd896..00000000 --- a/app/models/user/expired_service_connection.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -# stub model for notifying about expired service connections -class User::ExpiredServiceConnection < User -end diff --git a/app/models/user/expired_twitter_service_connection.rb b/app/models/user/expired_twitter_service_connection.rb deleted file mode 100644 index bb39250e..00000000 --- a/app/models/user/expired_twitter_service_connection.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true - -class User::ExpiredTwitterServiceConnection < User::ExpiredServiceConnection -end diff --git a/app/views/notifications/type/_expiredtwitterserviceconnection.html.haml b/app/views/notifications/type/_expiredtwitterserviceconnection.html.haml deleted file mode 100644 index fd941e2f..00000000 --- a/app/views/notifications/type/_expiredtwitterserviceconnection.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -.d-flex.notification - .flex-shrink-0.notification__icon - %i.fa.fa-2x.fa-fw.fa-twitter - .flex-grow-1 - %h6.notification__user - = t(".heading") - .notification__text - = t(".text_html", settings_sharing: link_to(t(".settings_services"), services_path)) diff --git a/config/locales/views.en.yml b/config/locales/views.en.yml index ea82b1dc..6dfef2d3 100644 --- a/config/locales/views.en.yml +++ b/config/locales/views.en.yml @@ -359,10 +359,6 @@ en: heading: "Push notifications are failing to send to one of your devices." text_html: "Please check the %{settings_push} if you still want to be notified." settings_push: "push notification settings" - expiredtwitterserviceconnection: - heading: "Twitter connection expired" - text_html: "If you would like to continue automatically sharing your answers to Twitter, head to %{settings_sharing} and re-connect your account." - settings_services: "Sharing Settings" settings: account: email_confirm: "Currently awaiting confirmation for %{resource}" diff --git a/db/migrate/20230212181044_remove_expired_service_connection_notifications.rb b/db/migrate/20230212181044_remove_expired_service_connection_notifications.rb new file mode 100644 index 00000000..3f306f39 --- /dev/null +++ b/db/migrate/20230212181044_remove_expired_service_connection_notifications.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class RemoveExpiredServiceConnectionNotifications < ActiveRecord::Migration[6.1] + def up = Notification.where(type: "Notification::ServiceTokenExpired").delete_all + + def down = raise ActiveRecord::IrreversibleMigration +end diff --git a/db/schema.rb b/db/schema.rb index 79160ab2..0d70a9eb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_02_05_162800) do +ActiveRecord::Schema.define(version: 2023_02_12_181044) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" From 5799a6f4d48d02c8d8c854ed78316daf069ebbf5 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Sun, 12 Feb 2023 19:23:28 +0100 Subject: [PATCH 060/104] locales/views: reword "share your answers" bit on the landingpage --- config/locales/views.en.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/config/locales/views.en.yml b/config/locales/views.en.yml index 6dfef2d3..c944b72c 100644 --- a/config/locales/views.en.yml +++ b/config/locales/views.en.yml @@ -27,10 +27,11 @@ en: header: "Share your answers" body_html: |

Want your followers on another platform to see your %{app_name} answers? - You can configure automatic sharing to your favourite platforms easily.

+ You can easily share them to your favourite platforms.

-

Not sure if it's a favourite, but at the moment only - Twitter is supported.

+

We support Tumblr, Twitter, + and many other services including Mastodon and + Misskey.

customize: header: "Customise your experience" body_html: | From b38a048e92ae33482c14f205ffa2160a865f2a6f Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sat, 11 Feb 2023 06:15:50 +0100 Subject: [PATCH 061/104] Added TurboStreamable concern --- app/controllers/concerns/.keep | 0 app/controllers/concerns/turbo_streamable.rb | 39 ++++++++++++++++++++ app/views/layouts/base.html.haml | 1 + app/views/shared/_toast.html.haml | 1 + 4 files changed, 41 insertions(+) delete mode 100644 app/controllers/concerns/.keep create mode 100644 app/controllers/concerns/turbo_streamable.rb create mode 100644 app/views/shared/_toast.html.haml diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/app/controllers/concerns/turbo_streamable.rb b/app/controllers/concerns/turbo_streamable.rb new file mode 100644 index 00000000..694b4a7c --- /dev/null +++ b/app/controllers/concerns/turbo_streamable.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module TurboStreamable + extend ActiveSupport::Concern + + included do |controller| + around_action :handle_error, only: controller.respond_to?(:turbo_stream_actions) ? controller.turbo_stream_actions : [] + end + + def render_toast(message, success = true) + turbo_stream.append("toasts", partial: "shared/toast", locals: { message:, success: }) + end + + class_methods do + def render_toast = render_toast + end + + private + + def handle_error + yield + rescue Errors::Base => e + render_error I18n.t(e.locale_tag) + rescue KeyError, ActionController::ParameterMissing => e + render_error t("errors.parameter_error", parameter: e.is_a?(KeyError) ? e.key : e.param.capitalize) + rescue Dry::Types::CoercionError, Dry::Types::ConstraintError + render_error t("errors.invalid_parameter") + rescue ActiveRecord::RecordNotFound + render_error t("errors.record_not_found") + end + + def render_error(message) + respond_to do |format| + format.turbo_stream do + render turbo_stream: render_toast(message, false) + end + end + end +end diff --git a/app/views/layouts/base.html.haml b/app/views/layouts/base.html.haml index d1d7d54a..edbd026e 100644 --- a/app/views/layouts/base.html.haml +++ b/app/views/layouts/base.html.haml @@ -31,6 +31,7 @@ = render 'shared/announcements' = yield = render "shared/formatting" + #toasts.d-none - if Rails.env.development? #debug %hr diff --git a/app/views/shared/_toast.html.haml b/app/views/shared/_toast.html.haml new file mode 100644 index 00000000..de241ebd --- /dev/null +++ b/app/views/shared/_toast.html.haml @@ -0,0 +1 @@ +.d-none{ data: { controller: "toast", toast_message_value: message, toast_success_value: success } } From 4c0948c6d14e455d9a9b9f663af1e0dff6658a00 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sat, 11 Feb 2023 06:16:42 +0100 Subject: [PATCH 062/104] Add Stimulus toast controller --- .../retrospring/controllers/toast_controller.ts | 17 +++++++++++++++++ .../retrospring/initializers/stimulus.ts | 2 ++ 2 files changed, 19 insertions(+) create mode 100644 app/javascript/retrospring/controllers/toast_controller.ts diff --git a/app/javascript/retrospring/controllers/toast_controller.ts b/app/javascript/retrospring/controllers/toast_controller.ts new file mode 100644 index 00000000..76b91eed --- /dev/null +++ b/app/javascript/retrospring/controllers/toast_controller.ts @@ -0,0 +1,17 @@ +import { Controller } from '@hotwired/stimulus'; +import { showNotification } from "utilities/notifications"; + +export default class extends Controller { + static values = { + message: String, + success: Boolean + }; + + declare readonly messageValue: string; + declare readonly successValue: boolean; + + connect(): void { + showNotification(this.messageValue, this.successValue); + this.element.remove(); + } +} diff --git a/app/javascript/retrospring/initializers/stimulus.ts b/app/javascript/retrospring/initializers/stimulus.ts index b4c3c91b..6239a1f9 100644 --- a/app/javascript/retrospring/initializers/stimulus.ts +++ b/app/javascript/retrospring/initializers/stimulus.ts @@ -9,6 +9,7 @@ import ThemeController from "retrospring/controllers/theme_controller"; import CapabilitiesController from "retrospring/controllers/capabilities_controller"; import CropperController from "retrospring/controllers/cropper_controller"; import InboxSharingController from "retrospring/controllers/inbox_sharing_controller"; +import ToastController from "retrospring/controllers/toast_controller"; /** * This module sets up Stimulus and our controllers @@ -29,4 +30,5 @@ export default function (): void { window['Stimulus'].register('format-popup', FormatPopupController); window['Stimulus'].register('inbox-sharing', InboxSharingController); window['Stimulus'].register('theme', ThemeController); + window['Stimulus'].register('toast', ToastController); } From 5cb96a7907593b53ab4cf9605fee93450b28a21b Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sat, 11 Feb 2023 06:18:51 +0100 Subject: [PATCH 063/104] Fix anonymous blocks causing exceptions without inboxes --- app/controllers/anonymous_block_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/anonymous_block_controller.rb b/app/controllers/anonymous_block_controller.rb index 1daf9d8c..01e09a41 100644 --- a/app/controllers/anonymous_block_controller.rb +++ b/app/controllers/anonymous_block_controller.rb @@ -16,12 +16,13 @@ class AnonymousBlockController < ApplicationController target_user: question.user ) - inbox_id = question.inboxes.first.id + inbox_id = question.inboxes.first&.id question.inboxes.first&.destroy respond_to do |format| format.turbo_stream do render turbo_stream: turbo_stream.remove("inbox_#{inbox_id}") + inbox_id ? turbo_stream.remove("inbox_#{inbox_id}") : nil, end format.html { redirect_back(fallback_location: inbox_path) } From f2024a990e264b54a1e4c7971a408edff1209fd8 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sat, 11 Feb 2023 06:19:31 +0100 Subject: [PATCH 064/104] Add TurboStreamable concern to AnonymousBlock controller --- app/controllers/anonymous_block_controller.rb | 13 +++++++++++-- config/locales/controllers.en.yml | 5 +++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/app/controllers/anonymous_block_controller.rb b/app/controllers/anonymous_block_controller.rb index 01e09a41..c71ade6c 100644 --- a/app/controllers/anonymous_block_controller.rb +++ b/app/controllers/anonymous_block_controller.rb @@ -3,6 +3,10 @@ class AnonymousBlockController < ApplicationController before_action :authenticate_user! + def turbo_stream_actions = %i[create destroy] + + include TurboStreamable + def create params.require :question @@ -21,8 +25,10 @@ class AnonymousBlockController < ApplicationController respond_to do |format| format.turbo_stream do - render turbo_stream: turbo_stream.remove("inbox_#{inbox_id}") + render turbo_stream: [ inbox_id ? turbo_stream.remove("inbox_#{inbox_id}") : nil, + render_toast(t(".success")) + ].compact end format.html { redirect_back(fallback_location: inbox_path) } @@ -39,7 +45,10 @@ class AnonymousBlockController < ApplicationController respond_to do |format| format.turbo_stream do - render turbo_stream: turbo_stream.remove("block_#{params[:id]}") + render turbo_stream: [ + turbo_stream.remove("block_#{params[:id]}"), + render_toast(t(".success")) + ] end format.html { redirect_back(fallback_location: settings_blocks_path) } diff --git a/config/locales/controllers.en.yml b/config/locales/controllers.en.yml index 4b5f49af..d5f14595 100644 --- a/config/locales/controllers.en.yml +++ b/config/locales/controllers.en.yml @@ -144,6 +144,11 @@ en: zero: "You are not currently subscribed to push notifications on any devices." one: "You are currently receiving push notifications on one device." other: "You are currently receiving push notifications on %{count} devices." + anonymous_block: + create: + success: "Successfully blocked user." + destroy: + success: "Successfully unblocked user." inbox: author: info: "No questions from @%{author} found, showing entries from all users instead!" From c880fade4ecc81dc9b789d54fe7c8f07cbdb982b Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sat, 11 Feb 2023 06:19:57 +0100 Subject: [PATCH 065/104] Add TurboStreamable concern to Settings::MutesController --- app/controllers/settings/mutes_controller.rb | 12 ++++++++++-- config/locales/controllers.en.yml | 5 +++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/controllers/settings/mutes_controller.rb b/app/controllers/settings/mutes_controller.rb index 6d312ee6..8ad0ab72 100644 --- a/app/controllers/settings/mutes_controller.rb +++ b/app/controllers/settings/mutes_controller.rb @@ -3,6 +3,10 @@ class Settings::MutesController < ApplicationController before_action :authenticate_user! + def turbo_stream_actions = %i[create destroy] + + include TurboStreamable + def index @users = current_user.muted_users @rules = MuteRule.where(user: current_user) @@ -15,7 +19,8 @@ class Settings::MutesController < ApplicationController format.turbo_stream do render turbo_stream: [ turbo_stream.replace("form", partial: "settings/mutes/form"), - turbo_stream.append("rules", partial: "settings/mutes/rule", locals: { rule: result[:resource] }) + turbo_stream.append("rules", partial: "settings/mutes/rule", locals: { rule: result[:resource] }), + render_toast(t(".success")) ] end @@ -32,7 +37,10 @@ class Settings::MutesController < ApplicationController respond_to do |format| format.turbo_stream do - render turbo_stream: turbo_stream.remove("rule_#{params[:id]}") + render turbo_stream: [ + turbo_stream.remove("rule_#{params[:id]}"), + render_toast(t(".success")) + ] end format.html { redirect_to settings_muted_path } diff --git a/config/locales/controllers.en.yml b/config/locales/controllers.en.yml index d5f14595..d94570f8 100644 --- a/config/locales/controllers.en.yml +++ b/config/locales/controllers.en.yml @@ -160,6 +160,11 @@ en: create: success: "Your account is currently being exported. This will take a little while." error: "Exporting is currently not possible." + mutes: + create: + success: "Sucessfully created mute rule" + destroy: + success: "Successfully removed mute rule" privacy: update: success: :settings.profile.update.success From c3fa2701ba7a3d514de68a11ee6f826a8af2a6f6 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sat, 11 Feb 2023 06:43:11 +0100 Subject: [PATCH 066/104] Appease the dog overlords --- app/views/layouts/base.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/base.html.haml b/app/views/layouts/base.html.haml index edbd026e..f8464ac6 100644 --- a/app/views/layouts/base.html.haml +++ b/app/views/layouts/base.html.haml @@ -31,7 +31,7 @@ = render 'shared/announcements' = yield = render "shared/formatting" - #toasts.d-none + .d-none#toasts - if Rails.env.development? #debug %hr From 5a192a3598d125e5760d4622b25cf481f784b16c Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sat, 11 Feb 2023 14:41:36 +0100 Subject: [PATCH 067/104] Turn `turbo_stream_actions` into a class method Co-Authored-By: Georg Gadinger --- app/controllers/anonymous_block_controller.rb | 6 +++--- app/controllers/concerns/turbo_streamable.rb | 10 ++++------ app/controllers/settings/mutes_controller.rb | 6 +++--- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/app/controllers/anonymous_block_controller.rb b/app/controllers/anonymous_block_controller.rb index c71ade6c..a8c9a32f 100644 --- a/app/controllers/anonymous_block_controller.rb +++ b/app/controllers/anonymous_block_controller.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true class AnonymousBlockController < ApplicationController + include TurboStreamable + before_action :authenticate_user! - def turbo_stream_actions = %i[create destroy] - - include TurboStreamable + turbo_stream_actions :create, :destroy def create params.require :question diff --git a/app/controllers/concerns/turbo_streamable.rb b/app/controllers/concerns/turbo_streamable.rb index 694b4a7c..fd1d8f86 100644 --- a/app/controllers/concerns/turbo_streamable.rb +++ b/app/controllers/concerns/turbo_streamable.rb @@ -3,18 +3,16 @@ module TurboStreamable extend ActiveSupport::Concern - included do |controller| - around_action :handle_error, only: controller.respond_to?(:turbo_stream_actions) ? controller.turbo_stream_actions : [] + class_methods do + def turbo_stream_actions(*actions) + around_action :handle_error, only: actions + end end def render_toast(message, success = true) turbo_stream.append("toasts", partial: "shared/toast", locals: { message:, success: }) end - class_methods do - def render_toast = render_toast - end - private def handle_error diff --git a/app/controllers/settings/mutes_controller.rb b/app/controllers/settings/mutes_controller.rb index 8ad0ab72..1000b862 100644 --- a/app/controllers/settings/mutes_controller.rb +++ b/app/controllers/settings/mutes_controller.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true class Settings::MutesController < ApplicationController + include TurboStreamable + before_action :authenticate_user! - def turbo_stream_actions = %i[create destroy] - - include TurboStreamable + turbo_stream_actions :create, :destroy def index @users = current_user.muted_users From ed20ad9237804301d795d65f4ec49c2d06245dad Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sat, 11 Feb 2023 15:41:17 +0100 Subject: [PATCH 068/104] Fix class check for parameter errors --- app/controllers/concerns/turbo_streamable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/concerns/turbo_streamable.rb b/app/controllers/concerns/turbo_streamable.rb index fd1d8f86..5066bc05 100644 --- a/app/controllers/concerns/turbo_streamable.rb +++ b/app/controllers/concerns/turbo_streamable.rb @@ -20,7 +20,7 @@ module TurboStreamable rescue Errors::Base => e render_error I18n.t(e.locale_tag) rescue KeyError, ActionController::ParameterMissing => e - render_error t("errors.parameter_error", parameter: e.is_a?(KeyError) ? e.key : e.param.capitalize) + render_error t("errors.parameter_error", parameter: e.instance_of?(KeyError) ? e.key : e.param.capitalize) rescue Dry::Types::CoercionError, Dry::Types::ConstraintError render_error t("errors.invalid_parameter") rescue ActiveRecord::RecordNotFound From 72ad8f66999dd24a67e8fcc48fef119259916356 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sat, 11 Feb 2023 15:41:35 +0100 Subject: [PATCH 069/104] Add tests for TurboStreamable concern --- .../concerns/turbo_streamable_spec.rb | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 spec/controllers/concerns/turbo_streamable_spec.rb diff --git a/spec/controllers/concerns/turbo_streamable_spec.rb b/spec/controllers/concerns/turbo_streamable_spec.rb new file mode 100644 index 00000000..fc285d30 --- /dev/null +++ b/spec/controllers/concerns/turbo_streamable_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require "rails_helper" + +class TurboStreamableTestController < ApplicationController + include TurboStreamable + + turbo_stream_actions :create, :blocked, :not_found + + def create + params.require :message + + respond_to do |format| + format.turbo_stream do + render turbo_stream: render_toast("success!") + end + end + end + + def blocked + raise Errors::Blocked + end + + def not_found + raise ActiveRecord::RecordNotFound + end +end + +describe TurboStreamableTestController, type: :controller do + render_views + + before do + routes.disable_clear_and_finalize = true + routes.draw do + get "turbo_streamable" => "turbo_streamable_test#create" + get "turbo_streamable_blocked" => "turbo_streamable_test#blocked" + get "turbo_streamable_not_found" => "turbo_streamable_test#not_found" + end + routes.finalize! + end + + shared_examples_for "it returns a toast as Turbo Stream response" do |action, message| + subject { get action, format: :turbo_stream } + + it "returns a toast as Turbo Stream response" do + subject + + expect(response.header["Content-Type"]).to include "text/vnd.turbo-stream.html" + expect(response.body).to include message + end + end + + describe "#create" do + context "gets called with the proper parameters" do + subject { get :create, format: :turbo_stream, params: { message: "test" } } + + it "returns a toast as Turbo Stream response" do + subject + + expect(response.header["Content-Type"]).to include "text/vnd.turbo-stream.html" + expect(response.body).to include "success!" + end + end + + context "gets called with the wrong parameters" do + it_behaves_like "it returns a toast as Turbo Stream response", :create, "Message is required" + end + end + + it_behaves_like "it returns a toast as Turbo Stream response", :create, "Message is required" + it_behaves_like "it returns a toast as Turbo Stream response", :blocked, "You have been blocked from performing this request" + it_behaves_like "it returns a toast as Turbo Stream response", :not_found, "Record not found" +end From 0aac4caf45a413fce98fd34f81864f9aaa9799ac Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sat, 11 Feb 2023 16:38:47 +0100 Subject: [PATCH 070/104] Register test routes in routes.rb --- config/routes.rb | 6 ++++++ spec/controllers/concerns/turbo_streamable_spec.rb | 10 ---------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 74ebc5b0..d331abbd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -171,5 +171,11 @@ Rails.application.routes.draw do get "/nodeinfo/2.1", to: "well_known/node_info#nodeinfo", as: :node_info + if Rails.env.test? + get "turbo_streamable" => "turbo_streamable_test#create" + get "turbo_streamable_blocked" => "turbo_streamable_test#blocked" + get "turbo_streamable_not_found" => "turbo_streamable_test#not_found" + end + puts "processing time of routes.rb: #{"#{(Time.zone.now - start).round(3).to_s.ljust(5, '0')}s".light_green}" end diff --git a/spec/controllers/concerns/turbo_streamable_spec.rb b/spec/controllers/concerns/turbo_streamable_spec.rb index fc285d30..6c1facdf 100644 --- a/spec/controllers/concerns/turbo_streamable_spec.rb +++ b/spec/controllers/concerns/turbo_streamable_spec.rb @@ -29,16 +29,6 @@ end describe TurboStreamableTestController, type: :controller do render_views - before do - routes.disable_clear_and_finalize = true - routes.draw do - get "turbo_streamable" => "turbo_streamable_test#create" - get "turbo_streamable_blocked" => "turbo_streamable_test#blocked" - get "turbo_streamable_not_found" => "turbo_streamable_test#not_found" - end - routes.finalize! - end - shared_examples_for "it returns a toast as Turbo Stream response" do |action, message| subject { get action, format: :turbo_stream } From 4dc88fe1efcd07ff023616389bc8cab4433e8480 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 12 Feb 2023 01:12:52 +0100 Subject: [PATCH 071/104] Refactor TurboStreamable spec to use an anonymous controller --- config/routes.rb | 6 --- .../concerns/turbo_streamable_spec.rb | 40 +++++++++++-------- spec/rails_helper.rb | 4 ++ 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index d331abbd..74ebc5b0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -171,11 +171,5 @@ Rails.application.routes.draw do get "/nodeinfo/2.1", to: "well_known/node_info#nodeinfo", as: :node_info - if Rails.env.test? - get "turbo_streamable" => "turbo_streamable_test#create" - get "turbo_streamable_blocked" => "turbo_streamable_test#blocked" - get "turbo_streamable_not_found" => "turbo_streamable_test#not_found" - end - puts "processing time of routes.rb: #{"#{(Time.zone.now - start).round(3).to_s.ljust(5, '0')}s".light_green}" end diff --git a/spec/controllers/concerns/turbo_streamable_spec.rb b/spec/controllers/concerns/turbo_streamable_spec.rb index 6c1facdf..6cf3e496 100644 --- a/spec/controllers/concerns/turbo_streamable_spec.rb +++ b/spec/controllers/concerns/turbo_streamable_spec.rb @@ -2,31 +2,39 @@ require "rails_helper" -class TurboStreamableTestController < ApplicationController - include TurboStreamable +describe ApplicationController, type: :controller do + controller do + include TurboStreamable - turbo_stream_actions :create, :blocked, :not_found + turbo_stream_actions :create, :blocked, :not_found - def create - params.require :message + def create + params.require :message - respond_to do |format| - format.turbo_stream do - render turbo_stream: render_toast("success!") + respond_to do |format| + format.turbo_stream do + render turbo_stream: render_toast("success!") + end end end + + def blocked + raise Errors::Blocked + end + + def not_found + raise ActiveRecord::RecordNotFound + end end - def blocked - raise Errors::Blocked + before do + routes.draw do + get "create" => "anonymous#create" + get "blocked" => "anonymous#blocked" + get "not_found" => "anonymous#not_found" + end end - def not_found - raise ActiveRecord::RecordNotFound - end -end - -describe TurboStreamableTestController, type: :controller do render_views shared_examples_for "it returns a toast as Turbo Stream response" do |action, message| diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 90ade2fe..43662e83 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -60,6 +60,10 @@ RSpec.configure do |config| # arbitrary gems may also be filtered via: # config.filter_gems_from_backtrace("gem name") + # anonymous controllers will infer ApplicationController instead of the + # described class + config.infer_base_class_for_anonymous_controllers = false + config.include Devise::Test::ControllerHelpers, type: :controller config.include Devise::Test::ControllerHelpers, type: :helper end From bc52eb8cb5a0273e9974a4198ef667f6093f67d2 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 12 Feb 2023 02:23:43 +0100 Subject: [PATCH 072/104] Add specs for Turbo Stream responses --- .../anonymous_block_controller_spec.rb | 25 ++++++++++++++++++- .../settings/mutes_controller_spec.rb | 16 ++++++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/spec/controllers/anonymous_block_controller_spec.rb b/spec/controllers/anonymous_block_controller_spec.rb index 15dfef01..a1619a51 100644 --- a/spec/controllers/anonymous_block_controller_spec.rb +++ b/spec/controllers/anonymous_block_controller_spec.rb @@ -4,7 +4,7 @@ require "rails_helper" describe AnonymousBlockController, type: :controller do describe "#create" do - subject { post(:create, params:) } + subject { post(:create, params:, format: :turbo_stream) } context "user signed in" do let(:user) { FactoryBot.create(:user) } @@ -23,6 +23,29 @@ describe AnonymousBlockController, type: :controller do it "creates an anonymous block" do expect { subject }.to(change { AnonymousBlock.count }.by(1)) end + + it "contains the inbox entry removal turbo stream action" do + subject + + expect(response.body).to include "turbo-stream action=\"remove\" target=\"inbox_#{inbox.id}" + end + end + + context "when all required parameters are given, but no inbox entry exists" do + let(:question) { FactoryBot.create(:question, author_identifier: "someidentifier") } + let(:params) do + { question: question.id } + end + + it "creates an anonymous block" do + expect { subject }.to(change { AnonymousBlock.count }.by(1)) + end + + it "doesn't contain the inbox entry removal turbo stream action" do + subject + + expect(response.body).not_to include "turbo-stream action=\"remove\" target=\"inbox_" + end end context "when blocking a user globally" do diff --git a/spec/controllers/settings/mutes_controller_spec.rb b/spec/controllers/settings/mutes_controller_spec.rb index 95a25b78..3b7579c5 100644 --- a/spec/controllers/settings/mutes_controller_spec.rb +++ b/spec/controllers/settings/mutes_controller_spec.rb @@ -19,7 +19,7 @@ describe Settings::MutesController, type: :controller do end describe "#create" do - subject { post :create, params: { muted_phrase: "foo" } } + subject { post :create, params: { muted_phrase: "foo" }, format: :turbo_stream } context "user signed in" do let(:user) { FactoryBot.create(:user) } @@ -29,11 +29,17 @@ describe Settings::MutesController, type: :controller do it "creates a mute rule" do expect { subject }.to(change { MuteRule.count }.by(1)) end + + it "contains a turbo stream append tag" do + subject + + expect(response.body).to include "turbo-stream action=\"append\" target=\"rules\"" + end end end describe "#destroy" do - subject { delete :destroy, params: } + subject { delete :destroy, params:, format: :turbo_stream } context "user signed in" do let(:user) { FactoryBot.create(:user) } @@ -47,6 +53,12 @@ describe Settings::MutesController, type: :controller do expect(MuteRule.exists?(rule.id)).to eq(false) end + + it "contains a turbo stream remove tag" do + subject + + expect(response.body).to include "turbo-stream action=\"remove\" target=\"rule_#{rule.id}\"" + end end end end From 5579489a927bcd2645acdf8b5901d2f9d55190b0 Mon Sep 17 00:00:00 2001 From: Andreas Nedbal Date: Sun, 12 Feb 2023 16:21:39 +0100 Subject: [PATCH 073/104] Apply review suggestion from @nilsding Co-authored-by: Georg Gadinger --- spec/controllers/concerns/turbo_streamable_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/controllers/concerns/turbo_streamable_spec.rb b/spec/controllers/concerns/turbo_streamable_spec.rb index 6cf3e496..3a2c9c60 100644 --- a/spec/controllers/concerns/turbo_streamable_spec.rb +++ b/spec/controllers/concerns/turbo_streamable_spec.rb @@ -2,7 +2,7 @@ require "rails_helper" -describe ApplicationController, type: :controller do +describe TurboStreamable, type: :controller do controller do include TurboStreamable From c2baa86c09a56a267b166a004a6dca7d332a99d9 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Sun, 29 Jan 2023 00:56:47 +0100 Subject: [PATCH 074/104] Add `pinned_at` to answers --- app/models/answer.rb | 4 ++++ db/migrate/20230128233136_add_pinned_at_to_answers.rb | 8 ++++++++ db/schema.rb | 2 ++ 3 files changed, 14 insertions(+) create mode 100644 db/migrate/20230128233136_add_pinned_at_to_answers.rb diff --git a/app/models/answer.rb b/app/models/answer.rb index 91a6c956..d1e3e645 100644 --- a/app/models/answer.rb +++ b/app/models/answer.rb @@ -14,6 +14,8 @@ class Answer < ApplicationRecord validates :question_id, uniqueness: { scope: :user_id } # rubocop:enable Rails/UniqueValidationWithoutIndex + scope :pinned, -> { where.not(pinned_at: nil) } + SHORT_ANSWER_MAX_LENGTH = 640 # rubocop:disable Rails/SkipsModelValidations @@ -56,4 +58,6 @@ class Answer < ApplicationRecord end def long? = content.length > SHORT_ANSWER_MAX_LENGTH + + def pinned? = pinned_at.present? end diff --git a/db/migrate/20230128233136_add_pinned_at_to_answers.rb b/db/migrate/20230128233136_add_pinned_at_to_answers.rb new file mode 100644 index 00000000..8501ba1e --- /dev/null +++ b/db/migrate/20230128233136_add_pinned_at_to_answers.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class AddPinnedAtToAnswers < ActiveRecord::Migration[6.1] + def change + add_column :answers, :pinned_at, :timestamp + add_index :answers, %i[user_id pinned_at] + end +end diff --git a/db/schema.rb b/db/schema.rb index 0d70a9eb..33418a33 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -48,8 +48,10 @@ ActiveRecord::Schema.define(version: 2023_02_12_181044) do t.datetime "created_at" t.datetime "updated_at" t.integer "smile_count", default: 0, null: false + t.datetime "pinned_at" t.index ["question_id"], name: "index_answers_on_question_id" t.index ["user_id", "created_at"], name: "index_answers_on_user_id_and_created_at" + t.index ["user_id", "pinned_at"], name: "index_answers_on_user_id_and_pinned_at" end create_table "appendables", force: :cascade do |t| From ed4ec98455e024a3d11d09934770a20c6ebfb59e Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Sun, 29 Jan 2023 21:00:47 +0100 Subject: [PATCH 075/104] Add use case for pinning answers --- lib/use_case/answer/pin.rb | 28 ++++++++++++++++++++++++ spec/lib/use_case/answer/pin_spec.rb | 32 ++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 lib/use_case/answer/pin.rb create mode 100644 spec/lib/use_case/answer/pin_spec.rb diff --git a/lib/use_case/answer/pin.rb b/lib/use_case/answer/pin.rb new file mode 100644 index 00000000..dd574e71 --- /dev/null +++ b/lib/use_case/answer/pin.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module UseCase + module Answer + class Pin < UseCase::Base + option :user, type: Types.Instance(::User) + option :answer, type: Types.Instance(::Answer) + + def call + check_ownership! + + answer.pinned_at = Time.now.utc + answer.save! + + { + status: 200, + resource: answer + } + end + + private + + def check_ownership! + raise ::Errors::NotAuthorized unless answer.user == user + end + end + end +end diff --git a/spec/lib/use_case/answer/pin_spec.rb b/spec/lib/use_case/answer/pin_spec.rb new file mode 100644 index 00000000..920d8f02 --- /dev/null +++ b/spec/lib/use_case/answer/pin_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe UseCase::Answer::Pin do + include ActiveSupport::Testing::TimeHelpers + + subject { UseCase::Answer::Pin.call(user:, answer:) } + + context "answer exists" do + let(:answer) { FactoryBot.create(:answer, user: FactoryBot.create(:user)) } + + context "as answer owner" do + let(:user) { answer.user } + + it "pins the answer" do + travel_to(Time.at(1603290950).utc) do + expect { subject }.to change { answer.pinned_at }.from(nil).to(Time.at(1603290950).utc) + end + end + end + + context "as other user" do + let(:user) { FactoryBot.create(:user) } + + it "does not pin the answer" do + expect { subject }.to raise_error(Errors::NotAuthorized) + expect(answer.reload.pinned_at).to eq(nil) + end + end + end +end From 3451ae1fb04c2502bead1e4a6025a146adc1a01e Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Sun, 29 Jan 2023 21:01:03 +0100 Subject: [PATCH 076/104] Display pinned answers on profiles --- app/views/actions/_answer.html.haml | 9 +++++++++ app/views/user/show.html.haml | 6 +++++- config/locales/controllers.en.yml | 2 ++ config/locales/views.en.yml | 2 ++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/views/actions/_answer.html.haml b/app/views/actions/_answer.html.haml index 1fcfdca8..9f778bbe 100644 --- a/app/views/actions/_answer.html.haml +++ b/app/views/actions/_answer.html.haml @@ -16,6 +16,15 @@ %a.dropdown-item{ href: "#", data: { a_id: answer.id, action: "ab-report" } } %i.fa.fa-fw.fa-exclamation-triangle = t("voc.report") + - else + - if answer.pinned? + %a.dropdown-item{ href: "#", data: { a_id: answer.id, action: "ab-unpin" } } + %i.fa.fa-fw.fa-thumbtack + = t(".unpin") + - else + %a.dropdown-item{ href: "#", data: { a_id: answer.id, action: "ab-pin" } } + %i.fa.fa-fw.fa-thumbtack + = t(".pin") - if current_user.admin? %a.dropdown-item{ href: rails_admin_path_for_resource(answer), target: "_blank" } %i.fa.fa-fw.fa-gears diff --git a/app/views/user/show.html.haml b/app/views/user/show.html.haml index ac6452a0..6796f5c0 100644 --- a/app/views/user/show.html.haml +++ b/app/views/user/show.html.haml @@ -1,7 +1,11 @@ - unless @user.banned? + #pinned_answers + - @user.answers.pinned.each do |a| + = render 'answerbox', a: + #answers - @answers.each do |a| - = render 'answerbox', a: a + = render 'answerbox', a: - if @more_data_available .d-flex.justify-content-center.justify-content-sm-start#paginator diff --git a/config/locales/controllers.en.yml b/config/locales/controllers.en.yml index d94570f8..ebd15114 100644 --- a/config/locales/controllers.en.yml +++ b/config/locales/controllers.en.yml @@ -26,6 +26,8 @@ en: destroy: nopriv: "You cannot delete other people's answers." success: "Successfully deleted answer." + pin: + success: "Successfully pinned answer." comment: create: invalid: "Your comment is too long." diff --git a/config/locales/views.en.yml b/config/locales/views.en.yml index c944b72c..4b06bb01 100644 --- a/config/locales/views.en.yml +++ b/config/locales/views.en.yml @@ -73,6 +73,8 @@ en: actions: answer: return: "Return to Inbox" + pin: "Pin to Profile" + unpin: "Unpin from Profile" comment: view_smiles: "View comment smiles" share: From 5f50a08f03fe0160f01f60a7d33dbadc26110ae8 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Mon, 30 Jan 2023 17:39:34 +0100 Subject: [PATCH 077/104] Adjust answer export test to include pinned_at field --- spec/lib/use_case/data_export/answers_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/lib/use_case/data_export/answers_spec.rb b/spec/lib/use_case/data_export/answers_spec.rb index 3fa24f8e..8298fbf1 100644 --- a/spec/lib/use_case/data_export/answers_spec.rb +++ b/spec/lib/use_case/data_export/answers_spec.rb @@ -32,7 +32,8 @@ describe UseCase::DataExport::Answers, :data_export do user_id: user.id, created_at: "2022-12-10T13:37:42.000Z", updated_at: "2022-12-10T13:37:42.000Z", - smile_count: 0 + smile_count: 0, + pinned_at: nil } ] } From 5b1340b793968f4650a0e7f395b4b3c91ecb5d92 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Mon, 30 Jan 2023 17:54:10 +0100 Subject: [PATCH 078/104] Appease the dog overlords --- app/views/user/show.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/user/show.html.haml b/app/views/user/show.html.haml index 6796f5c0..4456aa90 100644 --- a/app/views/user/show.html.haml +++ b/app/views/user/show.html.haml @@ -1,11 +1,11 @@ - unless @user.banned? - #pinned_answers + #pinned-answers - @user.answers.pinned.each do |a| - = render 'answerbox', a: + = render "answerbox", a: #answers - @answers.each do |a| - = render 'answerbox', a: + = render "answerbox", a: - if @more_data_available .d-flex.justify-content-center.justify-content-sm-start#paginator From b196909b796794c9a20792dbddae89d6e0e45eb1 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Tue, 7 Feb 2023 08:32:25 +0100 Subject: [PATCH 079/104] Create frontend endpoint for pinning answers --- app/controllers/answer_controller.rb | 12 ++++++++++++ app/views/actions/_answer.html.haml | 9 +-------- app/views/actions/_pin.html.haml | 14 ++++++++++++++ app/views/answer/pin.turbo_stream.haml | 2 ++ config/locales/views.en.yml | 5 +++-- config/routes.rb | 1 + 6 files changed, 33 insertions(+), 10 deletions(-) create mode 100644 app/views/actions/_pin.html.haml create mode 100644 app/views/answer/pin.turbo_stream.haml diff --git a/app/controllers/answer_controller.rb b/app/controllers/answer_controller.rb index b63be49e..33057ceb 100644 --- a/app/controllers/answer_controller.rb +++ b/app/controllers/answer_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class AnswerController < ApplicationController + before_action :authenticate_user!, only: :pin + def show @answer = Answer.includes(comments: %i[user smiles], question: [:user], smiles: [:user]).find(params[:id]) @display_all = true @@ -16,4 +18,14 @@ class AnswerController < ApplicationController notif.update_all(new: false) unless notif.empty? end end + + def pin + answer = Answer.includes(:user).find(params[:id]) + UseCase::Answer::Pin.call(user: current_user, answer:) + + respond_to do |format| + format.html { redirect_to(user_path(username: current_user.screen_name)) } + format.turbo_stream { render "pin", locals: { answer: } } + end + end end diff --git a/app/views/actions/_answer.html.haml b/app/views/actions/_answer.html.haml index 9f778bbe..de81c031 100644 --- a/app/views/actions/_answer.html.haml +++ b/app/views/actions/_answer.html.haml @@ -17,14 +17,7 @@ %i.fa.fa-fw.fa-exclamation-triangle = t("voc.report") - else - - if answer.pinned? - %a.dropdown-item{ href: "#", data: { a_id: answer.id, action: "ab-unpin" } } - %i.fa.fa-fw.fa-thumbtack - = t(".unpin") - - else - %a.dropdown-item{ href: "#", data: { a_id: answer.id, action: "ab-pin" } } - %i.fa.fa-fw.fa-thumbtack - = t(".pin") + = render "actions/pin", answer: - if current_user.admin? %a.dropdown-item{ href: rails_admin_path_for_resource(answer), target: "_blank" } %i.fa.fa-fw.fa-gears diff --git a/app/views/actions/_pin.html.haml b/app/views/actions/_pin.html.haml new file mode 100644 index 00000000..aae54317 --- /dev/null +++ b/app/views/actions/_pin.html.haml @@ -0,0 +1,14 @@ +- if answer.pinned? + = button_to pin_answer_path(id: answer.id), + class: "dropdown-item", + method: :delete, + form: { id: "ab-pin-#{answer.id}", data: { turbo_stream: true } } do + %i.fa.fa-fw.fa-thumbtack + = t(".unpin") +- else + = button_to pin_answer_path(id: answer.id), + class: "dropdown-item", + method: :post, + form: { id: "ab-pin-#{answer.id}", data: { turbo_stream: true } } do + %i.fa.fa-fw.fa-thumbtack + = t(".pin") diff --git a/app/views/answer/pin.turbo_stream.haml b/app/views/answer/pin.turbo_stream.haml new file mode 100644 index 00000000..bc2c107a --- /dev/null +++ b/app/views/answer/pin.turbo_stream.haml @@ -0,0 +1,2 @@ += turbo_stream.update("ab-pin-#{answer.id}") do + = render "actions/pin", answer: diff --git a/config/locales/views.en.yml b/config/locales/views.en.yml index 4b06bb01..41652ab5 100644 --- a/config/locales/views.en.yml +++ b/config/locales/views.en.yml @@ -73,10 +73,11 @@ en: actions: answer: return: "Return to Inbox" - pin: "Pin to Profile" - unpin: "Unpin from Profile" comment: view_smiles: "View comment smiles" + pin: + pin: "Pin to Profile" + unpin: "Unpin from Profile" share: twitter: "Share on Twitter" tumblr: "Share on Tumblr" diff --git a/config/routes.rb b/config/routes.rb index 74ebc5b0..c8a12365 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -146,6 +146,7 @@ Rails.application.routes.draw do get "/user/:username", to: "user#show" get "/@:username", to: "user#show", as: :user get "/@:username/a/:id", to: "answer#show", as: :answer + post "/@:username/a/:id/pin", to: "answer#pin", as: :pin_answer get "/@:username/q/:id", to: "question#show", as: :question get "/@:username/followers", to: "user#followers", as: :show_user_followers get "/@:username/followings", to: "user#followings", as: :show_user_followings From 410d9b5d8ee5e2587d62ada37533479a0569bfe9 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Tue, 7 Feb 2023 08:36:29 +0100 Subject: [PATCH 080/104] Implement unpinning answers --- app/controllers/answer_controller.rb | 10 +++++++++ app/views/actions/_pin.html.haml | 2 +- config/routes.rb | 1 + lib/use_case/answer/unpin.rb | 33 ++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 lib/use_case/answer/unpin.rb diff --git a/app/controllers/answer_controller.rb b/app/controllers/answer_controller.rb index 33057ceb..aaa5f07b 100644 --- a/app/controllers/answer_controller.rb +++ b/app/controllers/answer_controller.rb @@ -28,4 +28,14 @@ class AnswerController < ApplicationController format.turbo_stream { render "pin", locals: { answer: } } end end + + def unpin + answer = Answer.includes(:user).find(params[:id]) + UseCase::Answer::Unpin.call(user: current_user, answer:) + + respond_to do |format| + format.html { redirect_to(user_path(username: current_user.screen_name)) } + format.turbo_stream { render "pin", locals: { answer: } } + end + end end diff --git a/app/views/actions/_pin.html.haml b/app/views/actions/_pin.html.haml index aae54317..c6d6c4bc 100644 --- a/app/views/actions/_pin.html.haml +++ b/app/views/actions/_pin.html.haml @@ -1,5 +1,5 @@ - if answer.pinned? - = button_to pin_answer_path(id: answer.id), + = button_to unpin_answer_path(id: answer.id), class: "dropdown-item", method: :delete, form: { id: "ab-pin-#{answer.id}", data: { turbo_stream: true } } do diff --git a/config/routes.rb b/config/routes.rb index c8a12365..744a8d83 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -147,6 +147,7 @@ Rails.application.routes.draw do get "/@:username", to: "user#show", as: :user get "/@:username/a/:id", to: "answer#show", as: :answer post "/@:username/a/:id/pin", to: "answer#pin", as: :pin_answer + delete "/@:username/a/:id/pin", to: "answer#unpin", as: :unpin_answer get "/@:username/q/:id", to: "question#show", as: :question get "/@:username/followers", to: "user#followers", as: :show_user_followers get "/@:username/followings", to: "user#followings", as: :show_user_followings diff --git a/lib/use_case/answer/unpin.rb b/lib/use_case/answer/unpin.rb new file mode 100644 index 00000000..ac945e63 --- /dev/null +++ b/lib/use_case/answer/unpin.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module UseCase + module Answer + class Unpin < UseCase::Base + option :user, type: Types.Instance(::User) + option :answer, type: Types.Instance(::Answer) + + def call + check_ownership! + check_pinned! + + answer.pinned_at = nil + answer.save! + + { + status: 200, + resource: nil + } + end + + private + + def check_ownership! + raise ::Errors::NotAuthorized unless answer.user == user + end + + def check_pinned! + raise ::Errors::BadRequest if answer.pinned_at.nil? + end + end + end +end From 438884e13ad2781d5c0ea65874649588ee0e64fb Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Tue, 7 Feb 2023 22:06:35 +0100 Subject: [PATCH 081/104] Add trailing commas (lint) --- lib/use_case/answer/pin.rb | 2 +- lib/use_case/answer/unpin.rb | 2 +- spec/lib/use_case/data_export/answers_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/use_case/answer/pin.rb b/lib/use_case/answer/pin.rb index dd574e71..a14e8674 100644 --- a/lib/use_case/answer/pin.rb +++ b/lib/use_case/answer/pin.rb @@ -14,7 +14,7 @@ module UseCase { status: 200, - resource: answer + resource: answer, } end diff --git a/lib/use_case/answer/unpin.rb b/lib/use_case/answer/unpin.rb index ac945e63..f9553b8d 100644 --- a/lib/use_case/answer/unpin.rb +++ b/lib/use_case/answer/unpin.rb @@ -15,7 +15,7 @@ module UseCase { status: 200, - resource: nil + resource: nil, } end diff --git a/spec/lib/use_case/data_export/answers_spec.rb b/spec/lib/use_case/data_export/answers_spec.rb index 8298fbf1..99335a91 100644 --- a/spec/lib/use_case/data_export/answers_spec.rb +++ b/spec/lib/use_case/data_export/answers_spec.rb @@ -33,7 +33,7 @@ describe UseCase::DataExport::Answers, :data_export do created_at: "2022-12-10T13:37:42.000Z", updated_at: "2022-12-10T13:37:42.000Z", smile_count: 0, - pinned_at: nil + pinned_at: nil, } ] } From 664bf5eab295e9f60308b784919904fe2b9e811a Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Tue, 7 Feb 2023 22:11:36 +0100 Subject: [PATCH 082/104] Add test for unpin use case --- spec/lib/use_case/answer/unpin_spec.rb | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 spec/lib/use_case/answer/unpin_spec.rb diff --git a/spec/lib/use_case/answer/unpin_spec.rb b/spec/lib/use_case/answer/unpin_spec.rb new file mode 100644 index 00000000..b6f5ea9c --- /dev/null +++ b/spec/lib/use_case/answer/unpin_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe UseCase::Answer::Unpin do + include ActiveSupport::Testing::TimeHelpers + + subject { UseCase::Answer::Unpin.call(user:, answer:) } + + context "answer exists" do + let(:pinned_at) { Time.at(1603290950).utc } + let(:answer) { FactoryBot.create(:answer, user: FactoryBot.create(:user), pinned_at:) } + + context "as answer owner" do + let(:user) { answer.user } + + it "unpins the answer" do + expect { subject }.to change { answer.pinned_at }.from(pinned_at).to(nil) + end + end + + context "as other user" do + let(:user) { FactoryBot.create(:user) } + + it "does not unpin the answer" do + expect { subject }.to raise_error(Errors::NotAuthorized) + expect(answer.reload.pinned_at).to eq(pinned_at) + end + end + end +end From 04303c667e98da34c18ad90bc166befd9226ab53 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Tue, 7 Feb 2023 22:52:36 +0100 Subject: [PATCH 083/104] Add tests for pin/unpin endpoints --- spec/controllers/answer_controller_spec.rb | 62 +++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/spec/controllers/answer_controller_spec.rb b/spec/controllers/answer_controller_spec.rb index b2c39fd5..86514985 100644 --- a/spec/controllers/answer_controller_spec.rb +++ b/spec/controllers/answer_controller_spec.rb @@ -2,7 +2,9 @@ require "rails_helper" -describe AnswerController do +describe AnswerController, type: :controller do + include ActiveSupport::Testing::TimeHelpers + let(:user) do FactoryBot.create :user, otp_module: :disabled, @@ -39,4 +41,62 @@ describe AnswerController do end end end + + describe "#pin" do + subject { post :pin, params: { username: user.screen_name, id: answer.id } } + + context "user signed in" do + before(:each) { sign_in user } + + it "pins the answer" do + travel_to(Time.at(1603290950).utc) do + expect { subject }.to change { answer.reload.pinned_at }.from(nil).to(Time.at(1603290950).utc) + expect(response).to redirect_to(user_path(user)) + end + end + end + + context "other user signed in" do + let(:other_user) { FactoryBot.create(:user) } + + before(:each) { sign_in other_user } + + it "does not pin the answer do" do + travel_to(Time.at(1603290950).utc) do + expect { subject }.to raise_error(Errors::NotAuthorized) + expect(answer.reload.pinned_at).to eq(nil) + end + end + end + end + + describe "#unpin" do + subject { delete :unpin, params: { username: user.screen_name, id: answer.id } } + + context "user signed in" do + before(:each) do + sign_in user + answer.update!(pinned_at: Time.at(1603290950).utc) + end + + it "pins the answer" do + expect { subject }.to change { answer.reload.pinned_at }.from(Time.at(1603290950).utc).to(nil) + expect(response).to redirect_to(user_path(user)) + end + end + + context "other user signed in" do + let(:other_user) { FactoryBot.create(:user) } + + before(:each) do + sign_in other_user + answer.update!(pinned_at: Time.at(1603290950).utc) + end + + it "does not pin the answer do" do + expect { subject }.to raise_error(Errors::NotAuthorized) + expect(answer.reload.pinned_at).to eq(Time.at(1603290950).utc) + end + end + end end From baea94297514c2691f90238fce7f8e85de42ce37 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Tue, 7 Feb 2023 22:55:39 +0100 Subject: [PATCH 084/104] Add check for pinning when the answer is already pinned --- lib/use_case/answer/pin.rb | 5 +++++ spec/lib/use_case/answer/pin_spec.rb | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/use_case/answer/pin.rb b/lib/use_case/answer/pin.rb index a14e8674..0e0d76d4 100644 --- a/lib/use_case/answer/pin.rb +++ b/lib/use_case/answer/pin.rb @@ -8,6 +8,7 @@ module UseCase def call check_ownership! + check_unpinned! answer.pinned_at = Time.now.utc answer.save! @@ -23,6 +24,10 @@ module UseCase def check_ownership! raise ::Errors::NotAuthorized unless answer.user == user end + + def check_unpinned! + raise ::Errors::BadRequest if answer.pinned_at.present? + end end end end diff --git a/spec/lib/use_case/answer/pin_spec.rb b/spec/lib/use_case/answer/pin_spec.rb index 920d8f02..26fa29d4 100644 --- a/spec/lib/use_case/answer/pin_spec.rb +++ b/spec/lib/use_case/answer/pin_spec.rb @@ -18,6 +18,17 @@ describe UseCase::Answer::Pin do expect { subject }.to change { answer.pinned_at }.from(nil).to(Time.at(1603290950).utc) end end + + context "answer is already pinned" do + before do + answer.update!(pinned_at: Time.at(1603290950).utc) + end + + it "raises an error" do + expect { subject }.to raise_error(Errors::BadRequest) + expect(answer.reload.pinned_at).to eq(Time.at(1603290950).utc) + end + end end context "as other user" do From dd8f51160fd191bcca171f98de22e349bbc8c8b8 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Tue, 7 Feb 2023 22:56:41 +0100 Subject: [PATCH 085/104] Add test for unpinning when the answer is not pinned --- spec/lib/use_case/answer/unpin_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/lib/use_case/answer/unpin_spec.rb b/spec/lib/use_case/answer/unpin_spec.rb index b6f5ea9c..f4d7a3cb 100644 --- a/spec/lib/use_case/answer/unpin_spec.rb +++ b/spec/lib/use_case/answer/unpin_spec.rb @@ -17,6 +17,15 @@ describe UseCase::Answer::Unpin do it "unpins the answer" do expect { subject }.to change { answer.pinned_at }.from(pinned_at).to(nil) end + + context "answer is already unpinned" do + let(:pinned_at) { nil } + + it "raises an error" do + expect { subject }.to raise_error(Errors::BadRequest) + expect(answer.reload.pinned_at).to eq(nil) + end + end end context "as other user" do From 6cbce2c1579f83710ba3b65194a8cdfdd08b695c Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Tue, 7 Feb 2023 22:59:48 +0100 Subject: [PATCH 086/104] Require authentication on unpin endpoint --- app/controllers/answer_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/answer_controller.rb b/app/controllers/answer_controller.rb index aaa5f07b..6b045f7a 100644 --- a/app/controllers/answer_controller.rb +++ b/app/controllers/answer_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class AnswerController < ApplicationController - before_action :authenticate_user!, only: :pin + before_action :authenticate_user!, only: %i[pin unpin] def show @answer = Answer.includes(comments: %i[user smiles], question: [:user], smiles: [:user]).find(params[:id]) From de73532befdf22ab56ef114befc6f419a72760a9 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Tue, 7 Feb 2023 23:12:13 +0100 Subject: [PATCH 087/104] Show indicator for pinned answers --- app/assets/stylesheets/components/_answerbox.scss | 12 ++++++++++++ app/views/application/_answerbox.html.haml | 5 +++++ config/locales/views.en.yml | 1 + 3 files changed, 18 insertions(+) diff --git a/app/assets/stylesheets/components/_answerbox.scss b/app/assets/stylesheets/components/_answerbox.scss index 2eae714c..15909d38 100644 --- a/app/assets/stylesheets/components/_answerbox.scss +++ b/app/assets/stylesheets/components/_answerbox.scss @@ -82,11 +82,23 @@ } } + &__pinned { + display: none; + } + .card-body { padding-bottom: .6rem; } } +#pinned-answers { + .answerbox { + &__pinned { + display: inline; + } + } +} + body:not(.cap-web-share) { [name="ab-share"] { display: none; diff --git a/app/views/application/_answerbox.html.haml b/app/views/application/_answerbox.html.haml index fe27f953..e6b9504b 100644 --- a/app/views/application/_answerbox.html.haml +++ b/app/views/application/_answerbox.html.haml @@ -27,6 +27,11 @@ .col-md-6.text-start.text-muted %i.fa.fa-clock-o = link_to(raw(t("time.distance_ago", time: time_tooltip(a))), answer_path(a.user.screen_name, a.id), class: "answerbox__permalink") + - if instance_variable_defined?(:@user) && @user == a.user && a.pinned_at.present? + %span.answerbox__pinned + · + %i.fa.fa-thumbtack + = t(".pinned") .col-md-6.d-md-flex.answerbox__actions = render "answerbox/actions", a: a, display_all: display_all .card-footer{ id: "ab-comments-section-#{a.id}", class: display_all.nil? ? "d-none" : nil } diff --git a/config/locales/views.en.yml b/config/locales/views.en.yml index 41652ab5..351e2c03 100644 --- a/config/locales/views.en.yml +++ b/config/locales/views.en.yml @@ -126,6 +126,7 @@ en: read: "Read the entire answer" answered: "%{hide} %{user}" # resolves into "Answered by %{user}" hide: "Answered by" + pinned: "Pinned" questionbox: title: "Ask something!" placeholder: "Type your question here…" From fa68ab27d764a36106d6eae3b5c9d4dbd1c58d60 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Tue, 7 Feb 2023 23:14:33 +0100 Subject: [PATCH 088/104] Limit to 10 pinned answers --- app/controllers/user_controller.rb | 1 + app/views/user/show.html.haml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb index 17926429..58060d22 100644 --- a/app/controllers/user_controller.rb +++ b/app/controllers/user_controller.rb @@ -7,6 +7,7 @@ class UserController < ApplicationController def show @answers = @user.cursored_answers(last_id: params[:last_id]) + @pinned_answers = @user.answers.pinned.limit(10) @answers_last_id = @answers.map(&:id).min @more_data_available = !@user.cursored_answers(last_id: @answers_last_id, size: 1).count.zero? diff --git a/app/views/user/show.html.haml b/app/views/user/show.html.haml index 4456aa90..25240a33 100644 --- a/app/views/user/show.html.haml +++ b/app/views/user/show.html.haml @@ -1,6 +1,6 @@ - unless @user.banned? #pinned-answers - - @user.answers.pinned.each do |a| + - @pinned_answers.each do |a| = render "answerbox", a: #answers From 2ee25d264f327a5ae5473d0edf9da98a08b95b57 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Tue, 7 Feb 2023 23:20:47 +0100 Subject: [PATCH 089/104] Simplify pinned check in answerbox This is hidden by CSS in the prior case anyway --- app/views/application/_answerbox.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/application/_answerbox.html.haml b/app/views/application/_answerbox.html.haml index e6b9504b..20b88963 100644 --- a/app/views/application/_answerbox.html.haml +++ b/app/views/application/_answerbox.html.haml @@ -27,7 +27,7 @@ .col-md-6.text-start.text-muted %i.fa.fa-clock-o = link_to(raw(t("time.distance_ago", time: time_tooltip(a))), answer_path(a.user.screen_name, a.id), class: "answerbox__permalink") - - if instance_variable_defined?(:@user) && @user == a.user && a.pinned_at.present? + - if a.pinned_at.present? %span.answerbox__pinned · %i.fa.fa-thumbtack From 2d6ff76461f0bfdcaffb89ca0e32baf670d6fbdb Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Tue, 7 Feb 2023 23:23:49 +0100 Subject: [PATCH 090/104] Appease the dog overlords --- app/assets/stylesheets/components/_answerbox.scss | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/components/_answerbox.scss b/app/assets/stylesheets/components/_answerbox.scss index 15909d38..adb1b732 100644 --- a/app/assets/stylesheets/components/_answerbox.scss +++ b/app/assets/stylesheets/components/_answerbox.scss @@ -92,10 +92,9 @@ } #pinned-answers { - .answerbox { - &__pinned { - display: inline; - } + + .answerbox__pinned { + display: inline; } } From 736ca4d6b08e2c6de64596770a7b83c4f332f45f Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Fri, 10 Feb 2023 11:34:55 +0100 Subject: [PATCH 091/104] Use a policy for pinning/unpinning --- app/policies/answer_policy.rb | 14 ++++++++++++++ lib/use_case/answer/pin.rb | 6 +----- lib/use_case/answer/unpin.rb | 6 +----- lib/use_case/base.rb | 6 ++++++ 4 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 app/policies/answer_policy.rb diff --git a/app/policies/answer_policy.rb b/app/policies/answer_policy.rb new file mode 100644 index 00000000..c012c920 --- /dev/null +++ b/app/policies/answer_policy.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class AnswerPolicy + attr_reader :user, :answer + + def initialize(user, answer) + @user = user + @answer = answer + end + + def pin? = answer.user == user + + def unpin? = answer.user == user +end diff --git a/lib/use_case/answer/pin.rb b/lib/use_case/answer/pin.rb index 0e0d76d4..c790663c 100644 --- a/lib/use_case/answer/pin.rb +++ b/lib/use_case/answer/pin.rb @@ -7,7 +7,7 @@ module UseCase option :answer, type: Types.Instance(::Answer) def call - check_ownership! + authorize!(:pin, user, answer) check_unpinned! answer.pinned_at = Time.now.utc @@ -21,10 +21,6 @@ module UseCase private - def check_ownership! - raise ::Errors::NotAuthorized unless answer.user == user - end - def check_unpinned! raise ::Errors::BadRequest if answer.pinned_at.present? end diff --git a/lib/use_case/answer/unpin.rb b/lib/use_case/answer/unpin.rb index f9553b8d..8f12742e 100644 --- a/lib/use_case/answer/unpin.rb +++ b/lib/use_case/answer/unpin.rb @@ -7,7 +7,7 @@ module UseCase option :answer, type: Types.Instance(::Answer) def call - check_ownership! + authorize!(:unpin, user, answer) check_pinned! answer.pinned_at = nil @@ -21,10 +21,6 @@ module UseCase private - def check_ownership! - raise ::Errors::NotAuthorized unless answer.user == user - end - def check_pinned! raise ::Errors::BadRequest if answer.pinned_at.nil? end diff --git a/lib/use_case/base.rb b/lib/use_case/base.rb index e35585c2..7ff23bbf 100644 --- a/lib/use_case/base.rb +++ b/lib/use_case/base.rb @@ -9,5 +9,11 @@ module UseCase def self.call(...) = new(...).call def call = raise NotImplementedError + + private + + def authorize!(verb, user, record, error_class: Errors::NotAuthorized) + raise error_class unless Pundit.policy!(user, record).public_send("#{verb}?") + end end end From 854cf2662edeabb4f4ddb8ee2822b674d215f2c6 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Fri, 10 Feb 2023 22:18:49 +0100 Subject: [PATCH 092/104] Specify username param for pin/unpin path --- app/views/actions/_pin.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/actions/_pin.html.haml b/app/views/actions/_pin.html.haml index c6d6c4bc..41a1fa59 100644 --- a/app/views/actions/_pin.html.haml +++ b/app/views/actions/_pin.html.haml @@ -1,12 +1,12 @@ - if answer.pinned? - = button_to unpin_answer_path(id: answer.id), + = button_to unpin_answer_path(username: current_user.screen_name, id: answer.id), class: "dropdown-item", method: :delete, form: { id: "ab-pin-#{answer.id}", data: { turbo_stream: true } } do %i.fa.fa-fw.fa-thumbtack = t(".unpin") - else - = button_to pin_answer_path(id: answer.id), + = button_to pin_answer_path(username: current_user.screen_name, id: answer.id), class: "dropdown-item", method: :post, form: { id: "ab-pin-#{answer.id}", data: { turbo_stream: true } } do From 6724aef105baff5f2e824b7c0eac458b8f907753 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Fri, 10 Feb 2023 22:23:34 +0100 Subject: [PATCH 093/104] Order pinned answers by when they were pinned --- app/controllers/user_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb index 58060d22..1963b496 100644 --- a/app/controllers/user_controller.rb +++ b/app/controllers/user_controller.rb @@ -7,7 +7,7 @@ class UserController < ApplicationController def show @answers = @user.cursored_answers(last_id: params[:last_id]) - @pinned_answers = @user.answers.pinned.limit(10) + @pinned_answers = @user.answers.pinned.order(pinned_at: :desc).limit(10) @answers_last_id = @answers.map(&:id).min @more_data_available = !@user.cursored_answers(last_id: @answers_last_id, size: 1).count.zero? From dcad9073a8be220c0b3d9deae56a020ef23c0282 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Fri, 10 Feb 2023 22:33:17 +0100 Subject: [PATCH 094/104] Fix typos in pinning tests --- spec/controllers/answer_controller_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/controllers/answer_controller_spec.rb b/spec/controllers/answer_controller_spec.rb index 86514985..2c967c40 100644 --- a/spec/controllers/answer_controller_spec.rb +++ b/spec/controllers/answer_controller_spec.rb @@ -61,7 +61,7 @@ describe AnswerController, type: :controller do before(:each) { sign_in other_user } - it "does not pin the answer do" do + it "does not pin the answer" do travel_to(Time.at(1603290950).utc) do expect { subject }.to raise_error(Errors::NotAuthorized) expect(answer.reload.pinned_at).to eq(nil) @@ -79,7 +79,7 @@ describe AnswerController, type: :controller do answer.update!(pinned_at: Time.at(1603290950).utc) end - it "pins the answer" do + it "unpins the answer" do expect { subject }.to change { answer.reload.pinned_at }.from(Time.at(1603290950).utc).to(nil) expect(response).to redirect_to(user_path(user)) end @@ -93,7 +93,7 @@ describe AnswerController, type: :controller do answer.update!(pinned_at: Time.at(1603290950).utc) end - it "does not pin the answer do" do + it "does not unpin the answer" do expect { subject }.to raise_error(Errors::NotAuthorized) expect(answer.reload.pinned_at).to eq(Time.at(1603290950).utc) end From 520f7eb9ef7501b906a1175b252a46da8f8213f6 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Sun, 12 Feb 2023 20:29:36 +0100 Subject: [PATCH 095/104] Show toasts on pin/unpin --- app/controllers/answer_controller.rb | 18 ++++++++++++++++-- config/locales/controllers.en.yml | 5 +++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/controllers/answer_controller.rb b/app/controllers/answer_controller.rb index 6b045f7a..d5e70881 100644 --- a/app/controllers/answer_controller.rb +++ b/app/controllers/answer_controller.rb @@ -3,6 +3,10 @@ class AnswerController < ApplicationController before_action :authenticate_user!, only: %i[pin unpin] + include TurboStreamable + + turbo_stream_actions :pin, :unpin + def show @answer = Answer.includes(comments: %i[user smiles], question: [:user], smiles: [:user]).find(params[:id]) @display_all = true @@ -25,7 +29,12 @@ class AnswerController < ApplicationController respond_to do |format| format.html { redirect_to(user_path(username: current_user.screen_name)) } - format.turbo_stream { render "pin", locals: { answer: } } + format.turbo_stream do + render turbo_stream: [ + turbo_stream.update("ab-pin-#{answer.id}", partial: "actions/pin", locals: { answer: }), + render_toast(t(".success")) + ] + end end end @@ -35,7 +44,12 @@ class AnswerController < ApplicationController respond_to do |format| format.html { redirect_to(user_path(username: current_user.screen_name)) } - format.turbo_stream { render "pin", locals: { answer: } } + format.turbo_stream do + render turbo_stream: [ + turbo_stream.update("ab-pin-#{answer.id}", partial: "actions/pin", locals: { answer: }), + render_toast(t(".success")) + ] + end end end end diff --git a/config/locales/controllers.en.yml b/config/locales/controllers.en.yml index ebd15114..ff41c0c6 100644 --- a/config/locales/controllers.en.yml +++ b/config/locales/controllers.en.yml @@ -212,3 +212,8 @@ en: timeline: public: title: "Public Timeline" + answer: + pin: + success: "This answer will now appear at the top of your profile." + unpin: + success: "This answer will no longer be pinned to the top of your profile." From 793fec7da173a33554b5883f643c5ad74a518b9b Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Sun, 12 Feb 2023 21:04:35 +0100 Subject: [PATCH 096/104] Update pinning tests to match new Turbo Stream behaviour --- spec/controllers/answer_controller_spec.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/spec/controllers/answer_controller_spec.rb b/spec/controllers/answer_controller_spec.rb index 2c967c40..108b3969 100644 --- a/spec/controllers/answer_controller_spec.rb +++ b/spec/controllers/answer_controller_spec.rb @@ -43,7 +43,7 @@ describe AnswerController, type: :controller do end describe "#pin" do - subject { post :pin, params: { username: user.screen_name, id: answer.id } } + subject { post :pin, params: { username: user.screen_name, id: answer.id }, format: :turbo_stream } context "user signed in" do before(:each) { sign_in user } @@ -51,7 +51,7 @@ describe AnswerController, type: :controller do it "pins the answer" do travel_to(Time.at(1603290950).utc) do expect { subject }.to change { answer.reload.pinned_at }.from(nil).to(Time.at(1603290950).utc) - expect(response).to redirect_to(user_path(user)) + expect(response.body).to include("turbo-stream action=\"update\" target=\"ab-pin-#{answer.id}\"") end end end @@ -63,15 +63,14 @@ describe AnswerController, type: :controller do it "does not pin the answer" do travel_to(Time.at(1603290950).utc) do - expect { subject }.to raise_error(Errors::NotAuthorized) - expect(answer.reload.pinned_at).to eq(nil) + expect { subject }.not_to(change { answer.reload.pinned_at }) end end end end describe "#unpin" do - subject { delete :unpin, params: { username: user.screen_name, id: answer.id } } + subject { delete :unpin, params: { username: user.screen_name, id: answer.id }, format: :turbo_stream } context "user signed in" do before(:each) do @@ -81,7 +80,7 @@ describe AnswerController, type: :controller do it "unpins the answer" do expect { subject }.to change { answer.reload.pinned_at }.from(Time.at(1603290950).utc).to(nil) - expect(response).to redirect_to(user_path(user)) + expect(response.body).to include("turbo-stream action=\"update\" target=\"ab-pin-#{answer.id}\"") end end @@ -94,8 +93,7 @@ describe AnswerController, type: :controller do end it "does not unpin the answer" do - expect { subject }.to raise_error(Errors::NotAuthorized) - expect(answer.reload.pinned_at).to eq(Time.at(1603290950).utc) + expect { subject }.not_to(change { answer.reload.pinned_at }) end end end From 39ecdc24fb60dc84baf67671acfed5998a958aed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Feb 2023 09:56:58 +0000 Subject: [PATCH 097/104] Bump sentry-sidekiq from 5.7.0 to 5.8.0 Bumps [sentry-sidekiq](https://github.com/getsentry/sentry-ruby) from 5.7.0 to 5.8.0. - [Release notes](https://github.com/getsentry/sentry-ruby/releases) - [Changelog](https://github.com/getsentry/sentry-ruby/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-ruby/compare/5.7.0...5.8.0) --- updated-dependencies: - dependency-name: sentry-sidekiq dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 96a87385..8e1a9463 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -409,13 +409,13 @@ GEM sprockets (> 3.0) sprockets-rails tilt - sentry-rails (5.7.0) + sentry-rails (5.8.0) railties (>= 5.0) - sentry-ruby (~> 5.7.0) - sentry-ruby (5.7.0) + sentry-ruby (~> 5.8.0) + sentry-ruby (5.8.0) concurrent-ruby (~> 1.0, >= 1.0.2) - sentry-sidekiq (5.7.0) - sentry-ruby (~> 5.7.0) + sentry-sidekiq (5.8.0) + sentry-ruby (~> 5.8.0) sidekiq (>= 3.0) shoulda-matchers (5.3.0) activesupport (>= 5.2.0) From fc49dfc89a5fe1953a3b7658e0c8bc5c21787344 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Feb 2023 09:58:03 +0000 Subject: [PATCH 098/104] Bump puma from 6.0.2 to 6.1.0 Bumps [puma](https://github.com/puma/puma) from 6.0.2 to 6.1.0. - [Release notes](https://github.com/puma/puma/releases) - [Changelog](https://github.com/puma/puma/blob/master/History.md) - [Commits](https://github.com/puma/puma/compare/v6.0.2...v6.1.0) --- updated-dependencies: - dependency-name: puma dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 96a87385..fcfef973 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -280,7 +280,7 @@ GEM pghero (3.1.0) activerecord (>= 6) public_suffix (4.0.7) - puma (6.0.2) + puma (6.1.0) nio4r (~> 2.0) pundit (2.3.0) activesupport (>= 3.0.0) From 378173813d68a5467c694758918f596ae9ec96f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Feb 2023 09:58:27 +0000 Subject: [PATCH 099/104] Bump @fortawesome/fontawesome-free from 6.2.1 to 6.3.0 Bumps [@fortawesome/fontawesome-free](https://github.com/FortAwesome/Font-Awesome) from 6.2.1 to 6.3.0. - [Release notes](https://github.com/FortAwesome/Font-Awesome/releases) - [Changelog](https://github.com/FortAwesome/Font-Awesome/blob/6.x/CHANGELOG.md) - [Commits](https://github.com/FortAwesome/Font-Awesome/compare/6.2.1...6.3.0) --- updated-dependencies: - dependency-name: "@fortawesome/fontawesome-free" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3ab43c53..a411a05e 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "dependencies": { "@fontsource/lexend": "^4.5.15", - "@fortawesome/fontawesome-free": "^6.2.1", + "@fortawesome/fontawesome-free": "^6.3.0", "@hotwired/stimulus": "^3.2.1", "@hotwired/turbo-rails": "^7.2.5", "@melloware/coloris": "^0.17.1", diff --git a/yarn.lock b/yarn.lock index 5d081111..5f6eed25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -179,10 +179,10 @@ resolved "https://registry.yarnpkg.com/@fontsource/lexend/-/lexend-4.5.15.tgz#ee033b850224d05b665d6661bf8d32c950f166bb" integrity sha512-6edLmDmte8pJWtQ1NIahcJq0iWlf6b0/JLwkd7WbDQ3C5tZWnxjvbP4RSklMv4MPzGjpuYuYnc+QANbjUws1oA== -"@fortawesome/fontawesome-free@^6.2.1": - version "6.2.1" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.2.1.tgz#344baf6ff9eaad7a73cff067d8c56bfc11ae5304" - integrity sha512-viouXhegu/TjkvYQoiRZK3aax69dGXxgEjpvZW81wIJdxm5Fnvp3VVIP4VHKqX4SvFw6qpmkILkD4RJWAdrt7A== +"@fortawesome/fontawesome-free@^6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.3.0.tgz#b5877182692a6f7a39d1108837bec24247ba4bd7" + integrity sha512-qVtd5i1Cc7cdrqnTWqTObKQHjPWAiRwjUPaXObaeNPcy7+WKxJumGBx66rfSFgK6LNpIasVKkEgW8oyf0tmPLA== "@hotwired/stimulus@^3.2.1": version "3.2.1" From 8e56408c366762b2f146d9286a040542ad3074a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Feb 2023 09:58:48 +0000 Subject: [PATCH 100/104] Bump stylelint-scss from 4.3.0 to 4.4.0 Bumps [stylelint-scss](https://github.com/stylelint-scss/stylelint-scss) from 4.3.0 to 4.4.0. - [Release notes](https://github.com/stylelint-scss/stylelint-scss/releases) - [Changelog](https://github.com/stylelint-scss/stylelint-scss/blob/master/CHANGELOG.md) - [Commits](https://github.com/stylelint-scss/stylelint-scss/compare/v4.3.0...v4.4.0) --- updated-dependencies: - dependency-name: stylelint-scss dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3ab43c53..d8a5e68d 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,6 @@ "eslint-plugin-import": "^2.27.5", "stylelint": "^14.16.1", "stylelint-config-standard-scss": "^6.1.0", - "stylelint-scss": "^4.3.0" + "stylelint-scss": "^4.4.0" } } diff --git a/yarn.lock b/yarn.lock index 5d081111..ec4921bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2313,10 +2313,10 @@ stylelint-config-standard@^29.0.0: dependencies: stylelint-config-recommended "^9.0.0" -stylelint-scss@^4.0.0, stylelint-scss@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-4.3.0.tgz#638800faf823db11fff60d537c81051fe74c90fa" - integrity sha512-GvSaKCA3tipzZHoz+nNO7S02ZqOsdBzMiCx9poSmLlb3tdJlGddEX/8QzCOD8O7GQan9bjsvLMsO5xiw6IhhIQ== +stylelint-scss@^4.0.0, stylelint-scss@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-4.4.0.tgz#87ce9d049eff1ce67cce788780fbfda63099017e" + integrity sha512-Qy66a+/30aylFhPmUArHhVsHOun1qrO93LGT15uzLuLjWS7hKDfpFm34mYo1ndR4MCo8W4bEZM1+AlJRJORaaw== dependencies: lodash "^4.17.21" postcss-media-query-parser "^0.2.3" From e0c4e896bab1bc666f27b1398c4f24ca5dc7d77a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Feb 2023 09:59:31 +0000 Subject: [PATCH 101/104] Bump fog-aws from 3.16.0 to 3.17.0 Bumps [fog-aws](https://github.com/fog/fog-aws) from 3.16.0 to 3.17.0. - [Release notes](https://github.com/fog/fog-aws/releases) - [Changelog](https://github.com/fog/fog-aws/blob/master/CHANGELOG.md) - [Commits](https://github.com/fog/fog-aws/compare/v3.16.0...v3.17.0) --- updated-dependencies: - dependency-name: fog-aws dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 96a87385..5280e179 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -146,7 +146,7 @@ GEM dry-logic (>= 1.4, < 2) zeitwerk (~> 2.6) erubi (1.12.0) - excon (0.98.0) + excon (0.99.0) factory_bot (6.2.0) activesupport (>= 5.0.0) factory_bot_rails (6.2.0) @@ -158,7 +158,7 @@ GEM faker (3.1.1) i18n (>= 1.8.11, < 2) ffi (1.15.5) - fog-aws (3.16.0) + fog-aws (3.17.0) fog-core (~> 2.1) fog-json (~> 1.1) fog-xml (~> 0.1) From cbf4c8099472848051916667046ea5c37d4d016f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Feb 2023 10:00:10 +0000 Subject: [PATCH 102/104] Bump rubocop from 1.44.1 to 1.45.1 Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.44.1 to 1.45.1. - [Release notes](https://github.com/rubocop/rubocop/releases) - [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md) - [Commits](https://github.com/rubocop/rubocop/compare/v1.44.1...v1.45.1) --- updated-dependencies: - dependency-name: rubocop dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Gemfile b/Gemfile index abf9e2e9..1b14f588 100644 --- a/Gemfile +++ b/Gemfile @@ -92,7 +92,7 @@ group :development, :test do gem "rspec-mocks" gem "rspec-rails", "~> 6.0" gem "rspec-sidekiq", "~> 3.0", require: false - gem "rubocop", "~> 1.44" + gem "rubocop", "~> 1.45" gem "rubocop-rails", "~> 2.17" gem "shoulda-matchers", "~> 5.3" gem "simplecov", require: false diff --git a/Gemfile.lock b/Gemfile.lock index 96a87385..b2143e8c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -274,7 +274,7 @@ GEM openssl (3.1.0) orm_adapter (0.5.0) parallel (1.22.1) - parser (3.2.0.0) + parser (3.2.1.0) ast (~> 2.4.1) pg (1.4.5) pghero (3.1.0) @@ -332,7 +332,7 @@ GEM rake (13.0.6) redcarpet (3.6.0) redis (4.8.0) - regexp_parser (2.6.2) + regexp_parser (2.7.0) request_store (1.5.1) rack (>= 1.4) responders (3.0.1) @@ -378,7 +378,7 @@ GEM rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) rspec-support (3.12.0) - rubocop (1.44.1) + rubocop (1.45.1) json (~> 2.3) parallel (~> 1.10) parser (>= 3.2.0.0) @@ -388,8 +388,8 @@ GEM rubocop-ast (>= 1.24.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.24.1) - parser (>= 3.1.1.0) + rubocop-ast (1.26.0) + parser (>= 3.2.1.0) rubocop-rails (2.17.4) activesupport (>= 4.2.0) rack (>= 1.1) @@ -539,7 +539,7 @@ DEPENDENCIES rspec-mocks rspec-rails (~> 6.0) rspec-sidekiq (~> 3.0) - rubocop (~> 1.44) + rubocop (~> 1.45) rubocop-rails (~> 2.17) ruby-progressbar rubyzip (~> 2.3) From e1b538ecc4fd1944fff5bdd2c09964f7f08ed0a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Feb 2023 10:00:47 +0000 Subject: [PATCH 103/104] Bump oj from 3.14.1 to 3.14.2 Bumps [oj](https://github.com/ohler55/oj) from 3.14.1 to 3.14.2. - [Release notes](https://github.com/ohler55/oj/releases) - [Changelog](https://github.com/ohler55/oj/blob/develop/CHANGELOG.md) - [Commits](https://github.com/ohler55/oj/compare/v3.14.1...v3.14.2) --- updated-dependencies: - dependency-name: oj dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 96a87385..ed77a7c7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -270,7 +270,7 @@ GEM nokogiri (1.14.1) mini_portile2 (~> 2.8.0) racc (~> 1.4) - oj (3.14.1) + oj (3.14.2) openssl (3.1.0) orm_adapter (0.5.0) parallel (1.22.1) From 06e05b4869ce4c47566530e5f2fc1ed32228a5c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Feb 2023 10:01:18 +0000 Subject: [PATCH 104/104] Bump esbuild from 0.17.5 to 0.17.8 Bumps [esbuild](https://github.com/evanw/esbuild) from 0.17.5 to 0.17.8. - [Release notes](https://github.com/evanw/esbuild/releases) - [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md) - [Commits](https://github.com/evanw/esbuild/compare/v0.17.5...v0.17.8) --- updated-dependencies: - dependency-name: esbuild dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 228 +++++++++++++++++++++++++-------------------------- 2 files changed, 115 insertions(+), 115 deletions(-) diff --git a/package.json b/package.json index 3ab43c53..b47015ec 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "^4.11.0", "@typescript-eslint/parser": "^4.11.0", - "esbuild": "^0.17.5", + "esbuild": "^0.17.8", "eslint": "^7.16.0", "eslint-plugin-import": "^2.27.5", "stylelint": "^14.16.1", diff --git a/yarn.lock b/yarn.lock index 5d081111..815c9c14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -49,115 +49,115 @@ resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36" integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg== -"@esbuild/android-arm64@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.5.tgz#a145f43018e639bed94ed637369e2dcdd6bf9ea2" - integrity sha512-KHWkDqYAMmKZjY4RAN1PR96q6UOtfkWlTS8uEwWxdLtkRt/0F/csUhXIrVfaSIFxnscIBMPynGfhsMwQDRIBQw== +"@esbuild/android-arm64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.8.tgz#b3d5b65a3b2e073a6c7ee36b1f3c30c8f000315b" + integrity sha512-oa/N5j6v1svZQs7EIRPqR8f+Bf8g6HBDjD/xHC02radE/NjKHK7oQmtmLxPs1iVwYyvE+Kolo6lbpfEQ9xnhxQ== -"@esbuild/android-arm@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.5.tgz#9fa2deff7fc5d180bb4ecff70beea3a95ac44251" - integrity sha512-crmPUzgCmF+qZXfl1YkiFoUta2XAfixR1tEnr/gXIixE+WL8Z0BGqfydP5oox0EUOgQMMRgtATtakyAcClQVqQ== +"@esbuild/android-arm@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.8.tgz#c41e496af541e175369d48164d0cf01a5f656cf6" + integrity sha512-0/rb91GYKhrtbeglJXOhAv9RuYimgI8h623TplY2X+vA4EXnk3Zj1fXZreJ0J3OJJu1bwmb0W7g+2cT/d8/l/w== -"@esbuild/android-x64@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.5.tgz#145fc61f810400e65a56b275280d1422a102c2ef" - integrity sha512-8fI/AnIdmWz/+1iza2WrCw8kwXK9wZp/yZY/iS8ioC+U37yJCeppi9EHY05ewJKN64ASoBIseufZROtcFnX5GA== +"@esbuild/android-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.8.tgz#080fa67c29be77f5a3ca5ee4cc78d5bf927e3a3b" + integrity sha512-bTliMLqD7pTOoPg4zZkXqCDuzIUguEWLpeqkNfC41ODBHwoUgZ2w5JBeYimv4oP6TDVocoYmEhZrCLQTrH89bg== -"@esbuild/darwin-arm64@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.5.tgz#61fb0546aa4bae0850817d6e0d008b1cb3f64b49" - integrity sha512-EAvaoyIySV6Iif3NQCglUNpnMfHSUgC5ugt2efl3+QDntucJe5spn0udNZjTgNi6tKVqSceOw9tQ32liNZc1Xw== +"@esbuild/darwin-arm64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.8.tgz#053622bf9a82f43d5c075b7818e02618f7b4a397" + integrity sha512-ghAbV3ia2zybEefXRRm7+lx8J/rnupZT0gp9CaGy/3iolEXkJ6LYRq4IpQVI9zR97ID80KJVoUlo3LSeA/sMAg== -"@esbuild/darwin-x64@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.5.tgz#54b770f0c49f524ae9ba24c85d6dea8b521f610d" - integrity sha512-ha7QCJh1fuSwwCgoegfdaljowwWozwTDjBgjD3++WAy/qwee5uUi1gvOg2WENJC6EUyHBOkcd3YmLDYSZ2TPPA== +"@esbuild/darwin-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.8.tgz#8a1aadb358d537d8efad817bb1a5bff91b84734b" + integrity sha512-n5WOpyvZ9TIdv2V1K3/iIkkJeKmUpKaCTdun9buhGRWfH//osmUjlv4Z5mmWdPWind/VGcVxTHtLfLCOohsOXw== -"@esbuild/freebsd-arm64@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.5.tgz#be1dd18b7b9411f10bdc362ba8bff16386175367" - integrity sha512-VbdXJkn2aI2pQ/wxNEjEcnEDwPpxt3CWWMFYmO7CcdFBoOsABRy2W8F3kjbF9F/pecEUDcI3b5i2w+By4VQFPg== +"@esbuild/freebsd-arm64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.8.tgz#e6738d0081ba0721a5c6c674e84c6e7fcea61989" + integrity sha512-a/SATTaOhPIPFWvHZDoZYgxaZRVHn0/LX1fHLGfZ6C13JqFUZ3K6SMD6/HCtwOQ8HnsNaEeokdiDSFLuizqv5A== -"@esbuild/freebsd-x64@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.5.tgz#c9c1960fa3e1eada4e5d4be2a11a2f04ce14198f" - integrity sha512-olgGYND1/XnnWxwhjtY3/ryjOG/M4WfcA6XH8dBTH1cxMeBemMODXSFhkw71Kf4TeZFFTN25YOomaNh0vq2iXg== +"@esbuild/freebsd-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.8.tgz#1855e562f2b730f4483f6e94086e9e2597feb4c3" + integrity sha512-xpFJb08dfXr5+rZc4E+ooZmayBW6R3q59daCpKZ/cDU96/kvDM+vkYzNeTJCGd8rtO6fHWMq5Rcv/1cY6p6/0Q== -"@esbuild/linux-arm64@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.5.tgz#34d96d11c6899017ecae42fb97de8e0c3282902f" - integrity sha512-8a0bqSwu3OlLCfu2FBbDNgQyBYdPJh1B9PvNX7jMaKGC9/KopgHs37t+pQqeMLzcyRqG6z55IGNQAMSlCpBuqg== +"@esbuild/linux-arm64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.8.tgz#481da38952721a3fdb77c17a36ceaacc4270b5c5" + integrity sha512-v3iwDQuDljLTxpsqQDl3fl/yihjPAyOguxuloON9kFHYwopeJEf1BkDXODzYyXEI19gisEsQlG1bM65YqKSIww== -"@esbuild/linux-arm@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.5.tgz#86332e6293fd713a54ab299a5e2ed7c60c9e1c07" - integrity sha512-YBdCyQwA3OQupi6W2/WO4FnI+NWFWe79cZEtlbqSESOHEg7a73htBIRiE6uHPQe7Yp5E4aALv+JxkRLGEUL7tw== +"@esbuild/linux-arm@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.8.tgz#18127072b270bb6321c6d11be20bfd30e0d6ad17" + integrity sha512-6Ij8gfuGszcEwZpi5jQIJCVIACLS8Tz2chnEBfYjlmMzVsfqBP1iGmHQPp7JSnZg5xxK9tjCc+pJ2WtAmPRFVA== -"@esbuild/linux-ia32@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.5.tgz#7bd9185c844e7dfce6a01dfdec584e115602a8c4" - integrity sha512-uCwm1r/+NdP7vndctgq3PoZrnmhmnecWAr114GWMRwg2QMFFX+kIWnp7IO220/JLgnXK/jP7VKAFBGmeOYBQYQ== +"@esbuild/linux-ia32@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.8.tgz#ee400af7b3bc69e8ca2e593ca35156ffb9abd54f" + integrity sha512-8svILYKhE5XetuFk/B6raFYIyIqydQi+GngEXJgdPdI7OMKUbSd7uzR02wSY4kb53xBrClLkhH4Xs8P61Q2BaA== -"@esbuild/linux-loong64@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.5.tgz#2907d4120c7b3642b96be6014f77e7624c378eea" - integrity sha512-3YxhSBl5Sb6TtBjJu+HP93poBruFzgXmf3PVfIe4xOXMj1XpxboYZyw3W8BhoX/uwxzZz4K1I99jTE/5cgDT1g== +"@esbuild/linux-loong64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.8.tgz#8c509d8a454693d39824b83b3f66c400872fce82" + integrity sha512-B6FyMeRJeV0NpyEOYlm5qtQfxbdlgmiGdD+QsipzKfFky0K5HW5Td6dyK3L3ypu1eY4kOmo7wW0o94SBqlqBSA== -"@esbuild/linux-mips64el@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.5.tgz#fc98be741e8080ecd13b404d5fca5302d3835bf4" - integrity sha512-Hy5Z0YVWyYHdtQ5mfmfp8LdhVwGbwVuq8mHzLqrG16BaMgEmit2xKO+iDakHs+OetEx0EN/2mUzDdfdktI+Nmg== +"@esbuild/linux-mips64el@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.8.tgz#f2b0d36e63fb26bc3f95b203b6a80638292101ca" + integrity sha512-CCb67RKahNobjm/eeEqeD/oJfJlrWyw29fgiyB6vcgyq97YAf3gCOuP6qMShYSPXgnlZe/i4a8WFHBw6N8bYAA== -"@esbuild/linux-ppc64@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.5.tgz#ea12e8f6b290a613ac4903c9e00835c69ced065c" - integrity sha512-5dbQvBLbU/Y3Q4ABc9gi23hww1mQcM7KZ9KBqabB7qhJswYMf8WrDDOSw3gdf3p+ffmijMd28mfVMvFucuECyg== +"@esbuild/linux-ppc64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.8.tgz#1e628be003e036e90423716028cc884fe5ba25bd" + integrity sha512-bytLJOi55y55+mGSdgwZ5qBm0K9WOCh0rx+vavVPx+gqLLhxtSFU0XbeYy/dsAAD6xECGEv4IQeFILaSS2auXw== -"@esbuild/linux-riscv64@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.5.tgz#ce47b15fd4227eeb0590826e41bdc430c5bfd06c" - integrity sha512-fp/KUB/ZPzEWGTEUgz9wIAKCqu7CjH1GqXUO2WJdik1UNBQ7Xzw7myIajpxztE4Csb9504ERiFMxZg5KZ6HlZQ== +"@esbuild/linux-riscv64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.8.tgz#419a815cb4c3fb9f1b78ef5295f5b48b8bf6427a" + integrity sha512-2YpRyQJmKVBEHSBLa8kBAtbhucaclb6ex4wchfY0Tj3Kg39kpjeJ9vhRU7x4mUpq8ISLXRXH1L0dBYjAeqzZAw== -"@esbuild/linux-s390x@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.5.tgz#962fa540d7498967270eb1d4b9ac6c4a4f339735" - integrity sha512-kRV3yw19YDqHTp8SfHXfObUFXlaiiw4o2lvT1XjsPZ++22GqZwSsYWJLjMi1Sl7j9qDlDUduWDze/nQx0d6Lzw== +"@esbuild/linux-s390x@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.8.tgz#291c49ae5c3d11d226352755c0835911fe1a9e5c" + integrity sha512-QgbNY/V3IFXvNf11SS6exkpVcX0LJcob+0RWCgV9OiDAmVElnxciHIisoSix9uzYzScPmS6dJFbZULdSAEkQVw== -"@esbuild/linux-x64@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.5.tgz#9fa52884c3d876593a522aa1d4df43b717907050" - integrity sha512-vnxuhh9e4pbtABNLbT2ANW4uwQ/zvcHRCm1JxaYkzSehugoFd5iXyC4ci1nhXU13mxEwCnrnTIiiSGwa/uAF1g== +"@esbuild/linux-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.8.tgz#03199d91c76faf80bd54104f5cbf0a489bc39f6a" + integrity sha512-mM/9S0SbAFDBc4OPoyP6SEOo5324LpUxdpeIUUSrSTOfhHU9hEfqRngmKgqILqwx/0DVJBzeNW7HmLEWp9vcOA== -"@esbuild/netbsd-x64@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.5.tgz#47bb187b86aad9622051cb80c27e439b7d9e3a9a" - integrity sha512-cigBpdiSx/vPy7doUyImsQQBnBjV5f1M99ZUlaJckDAJjgXWl6y9W17FIfJTy8TxosEF6MXq+fpLsitMGts2nA== +"@esbuild/netbsd-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.8.tgz#b436d767e1b21852f9ed212e2bb57f77203b0ae2" + integrity sha512-eKUYcWaWTaYr9zbj8GertdVtlt1DTS1gNBWov+iQfWuWyuu59YN6gSEJvFzC5ESJ4kMcKR0uqWThKUn5o8We6Q== -"@esbuild/openbsd-x64@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.5.tgz#abc55c35a1ed2bc3c5ede2ef50a3b2f87395009a" - integrity sha512-VdqRqPVIjjZfkf40LrqOaVuhw9EQiAZ/GNCSM2UplDkaIzYVsSnycxcFfAnHdWI8Gyt6dO15KHikbpxwx+xHbw== +"@esbuild/openbsd-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.8.tgz#d1481d8539e21d4729cd04a0450a26c2c8789e89" + integrity sha512-Vc9J4dXOboDyMXKD0eCeW0SIeEzr8K9oTHJU+Ci1mZc5njPfhKAqkRt3B/fUNU7dP+mRyralPu8QUkiaQn7iIg== -"@esbuild/sunos-x64@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.5.tgz#b83c080a2147662599a5d18b2ff47f07c93e03a0" - integrity sha512-ItxPaJ3MBLtI4nK+mALLEoUs6amxsx+J1ibnfcYMkqaCqHST1AkF4aENpBehty3czqw64r/XqL+W9WqU6kc2Qw== +"@esbuild/sunos-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.8.tgz#2cfb8126e079b2c00fd1bf095541e9f5c47877e4" + integrity sha512-0xvOTNuPXI7ft1LYUgiaXtpCEjp90RuBBYovdd2lqAFxje4sEucurg30M1WIm03+3jxByd3mfo+VUmPtRSVuOw== -"@esbuild/win32-arm64@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.5.tgz#2a4c41f427d9cf25b75f9d61493711a482106850" - integrity sha512-4u2Q6qsJTYNFdS9zHoAi80spzf78C16m2wla4eJPh4kSbRv+BpXIfl6TmBSWupD8e47B1NrTfrOlEuco7mYQtg== +"@esbuild/win32-arm64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.8.tgz#7c6ecfd097ca23b82119753bf7072bbaefe51e3a" + integrity sha512-G0JQwUI5WdEFEnYNKzklxtBheCPkuDdu1YrtRrjuQv30WsYbkkoixKxLLv8qhJmNI+ATEWquZe/N0d0rpr55Mg== -"@esbuild/win32-ia32@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.5.tgz#7c14e3250725d0e2c21f89c98eb6abb520cba0e0" - integrity sha512-KYlm+Xu9TXsfTWAcocLuISRtqxKp/Y9ZBVg6CEEj0O5J9mn7YvBKzAszo2j1ndyzUPk+op+Tie2PJeN+BnXGqQ== +"@esbuild/win32-ia32@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.8.tgz#cffec63c3cb0ef8563a04df4e09fa71056171d00" + integrity sha512-Fqy63515xl20OHGFykjJsMnoIWS+38fqfg88ClvPXyDbLtgXal2DTlhb1TfTX34qWi3u4I7Cq563QcHpqgLx8w== -"@esbuild/win32-x64@0.17.5": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.5.tgz#a8f3d26d8afc5186eccda265ceb1820b8e8830be" - integrity sha512-XgA9qWRqby7xdYXuF6KALsn37QGBMHsdhmnpjfZtYxKxbTOwfnDM6MYi2WuUku5poNaX2n9XGVr20zgT/2QwCw== +"@esbuild/win32-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.8.tgz#200a0965cf654ac28b971358ecdca9cc5b44c335" + integrity sha512-1iuezdyDNngPnz8rLRDO2C/ZZ/emJLb72OsZeqQ6gL6Avko/XCXZw+NuxBSNhBAP13Hie418V7VMt9et1FMvpg== "@eslint/eslintrc@^0.4.3": version "0.4.3" @@ -835,33 +835,33 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -esbuild@^0.17.5: - version "0.17.5" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.5.tgz#cd76d75700d49ac050ad9eedfbed777bd6a9d930" - integrity sha512-Bu6WLCc9NMsNoMJUjGl3yBzTjVLXdysMltxQWiLAypP+/vQrf+3L1Xe8fCXzxaECus2cEJ9M7pk4yKatEwQMqQ== +esbuild@^0.17.8: + version "0.17.8" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.8.tgz#f7f799abc7cdce3f0f2e3e0c01f120d4d55193b4" + integrity sha512-g24ybC3fWhZddZK6R3uD2iF/RIPnRpwJAqLov6ouX3hMbY4+tKolP0VMF3zuIYCaXun+yHwS5IPQ91N2BT191g== optionalDependencies: - "@esbuild/android-arm" "0.17.5" - "@esbuild/android-arm64" "0.17.5" - "@esbuild/android-x64" "0.17.5" - "@esbuild/darwin-arm64" "0.17.5" - "@esbuild/darwin-x64" "0.17.5" - "@esbuild/freebsd-arm64" "0.17.5" - "@esbuild/freebsd-x64" "0.17.5" - "@esbuild/linux-arm" "0.17.5" - "@esbuild/linux-arm64" "0.17.5" - "@esbuild/linux-ia32" "0.17.5" - "@esbuild/linux-loong64" "0.17.5" - "@esbuild/linux-mips64el" "0.17.5" - "@esbuild/linux-ppc64" "0.17.5" - "@esbuild/linux-riscv64" "0.17.5" - "@esbuild/linux-s390x" "0.17.5" - "@esbuild/linux-x64" "0.17.5" - "@esbuild/netbsd-x64" "0.17.5" - "@esbuild/openbsd-x64" "0.17.5" - "@esbuild/sunos-x64" "0.17.5" - "@esbuild/win32-arm64" "0.17.5" - "@esbuild/win32-ia32" "0.17.5" - "@esbuild/win32-x64" "0.17.5" + "@esbuild/android-arm" "0.17.8" + "@esbuild/android-arm64" "0.17.8" + "@esbuild/android-x64" "0.17.8" + "@esbuild/darwin-arm64" "0.17.8" + "@esbuild/darwin-x64" "0.17.8" + "@esbuild/freebsd-arm64" "0.17.8" + "@esbuild/freebsd-x64" "0.17.8" + "@esbuild/linux-arm" "0.17.8" + "@esbuild/linux-arm64" "0.17.8" + "@esbuild/linux-ia32" "0.17.8" + "@esbuild/linux-loong64" "0.17.8" + "@esbuild/linux-mips64el" "0.17.8" + "@esbuild/linux-ppc64" "0.17.8" + "@esbuild/linux-riscv64" "0.17.8" + "@esbuild/linux-s390x" "0.17.8" + "@esbuild/linux-x64" "0.17.8" + "@esbuild/netbsd-x64" "0.17.8" + "@esbuild/openbsd-x64" "0.17.8" + "@esbuild/sunos-x64" "0.17.8" + "@esbuild/win32-arm64" "0.17.8" + "@esbuild/win32-ia32" "0.17.8" + "@esbuild/win32-x64" "0.17.8" escape-string-regexp@^1.0.5: version "1.0.5"