diff --git a/Gemfile b/Gemfile index 2f56fa81d..61515a304 100644 --- a/Gemfile +++ b/Gemfile @@ -10,7 +10,7 @@ gem "stimulus-rails" gem "turbo-rails" # Deployment and drivers -gem "active_record-tenanted", bc: "active_record-tenanted" +gem "active_record-tenanted", bc: "active_record-tenanted", branch: "flavorjones/path-based-tenanting" gem "bootsnap", require: false gem "kamal", bc: "kamal", ref: "344e2d79", require: false gem "puma", ">= 5.0" diff --git a/Gemfile.lock b/Gemfile.lock index bdf346d74..d2d32a4b8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,7 +7,8 @@ GIT GIT remote: https://github.com/basecamp/active_record-tenanted - revision: 81a4d02a363fa749de517c90e19588365f934913 + revision: 8e33088cdbd69ab7b4cf4100d4202a54aa97e4c0 + branch: flavorjones/path-based-tenanting specs: active_record-tenanted (0.1.0) activerecord (>= 8.1.alpha) @@ -17,9 +18,9 @@ GIT GIT remote: https://github.com/basecamp/file_repository - revision: 17e90fc35ccb9f0a0dae1ed8623f01386696002d + revision: 8c8d1594f3ec7e8c0938e6a7f7def521692344e4 specs: - file_repository (1.4.5) + file_repository (1.4.6) GIT remote: https://github.com/basecamp/kamal @@ -40,7 +41,7 @@ GIT GIT remote: https://github.com/basecamp/portfolio - revision: 26dd0ccbe4aad5a5c539f90c00e82b6175443636 + revision: 34b48dd3c6dc78abd162ef26481971a228084d04 specs: portfolio (5.2.0) activesupport @@ -81,7 +82,7 @@ GIT GIT remote: https://github.com/basecamp/signal_id - revision: 8908b2550ff55325b04c108146e00b4408a64e93 + revision: 32758d0bff9f17e938905ff5bec53fff359e21b0 branch: rails4 specs: signal_id (4.3.2) @@ -111,7 +112,7 @@ GIT GIT remote: https://github.com/rails/rails.git - revision: 53a9a3181a59f4e6dff73ba9453734edd7f7e076 + revision: 58a2187f7b2ac391eba841e9ec6bb1a6d4e486dd branch: main specs: actioncable (8.1.0.alpha) @@ -226,18 +227,18 @@ GEM public_suffix (>= 2.0.2, < 7.0) ast (2.4.3) aws-eventstream (1.4.0) - aws-partitions (1.1119.0) - aws-sdk-core (3.226.0) + aws-partitions (1.1124.0) + aws-sdk-core (3.226.2) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) base64 jmespath (~> 1, >= 1.6.1) logger - aws-sdk-kms (1.105.0) + aws-sdk-kms (1.106.0) aws-sdk-core (~> 3, >= 3.225.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.190.0) + aws-sdk-s3 (1.192.0) aws-sdk-core (~> 3, >= 3.225.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -290,11 +291,11 @@ GEM faraday-net_http (>= 2.0, < 3.5) json logger - faraday-multipart (1.1.0) + faraday-multipart (1.1.1) multipart-post (~> 2.0) - faraday-net_http (3.4.0) + faraday-net_http (3.4.1) net-http (>= 0.5.0) - faraday-retry (2.3.1) + faraday-retry (2.3.2) faraday (~> 2.0) ffi (1.17.2) ffi (1.17.2-arm64-darwin) @@ -330,7 +331,7 @@ GEM activesupport (>= 5.0.0) jmespath (1.6.2) json (2.12.2) - jwt (2.10.1) + jwt (3.1.2) base64 language_server-protocol (3.17.0.5) lint_roller (1.1.0) @@ -344,7 +345,7 @@ GEM net-pop net-smtp marcel (1.0.4) - matrix (0.4.2) + matrix (0.4.3) mini_magick (5.2.0) benchmark logger @@ -393,7 +394,7 @@ GEM racc (~> 1.4) nokogiri (1.18.8-x86_64-linux-gnu) racc (~> 1.4) - ostruct (0.6.1) + ostruct (0.6.2) parallel (1.27.0) parser (3.3.8.0) ast (~> 2.4.1) @@ -456,7 +457,7 @@ GEM chunky_png (~> 1.0) rqrcode_core (~> 2.0) rqrcode_core (2.0.0) - rubocop (1.75.8) + rubocop (1.77.0) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -464,10 +465,10 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.44.0, < 2.0) + rubocop-ast (>= 1.45.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.44.1) + rubocop-ast (1.45.1) parser (>= 3.3.7.2) prism (~> 1.4) rubocop-performance (1.25.0) @@ -485,25 +486,25 @@ GEM rubocop-performance (>= 1.24) rubocop-rails (>= 2.30) ruby-progressbar (1.13.0) - ruby-vips (2.2.3) + ruby-vips (2.2.4) ffi (~> 1.12) logger ruby2_keywords (0.0.5) rubyzip (2.4.1) securerandom (0.4.1) - selenium-webdriver (4.33.0) + selenium-webdriver (4.34.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - sentry-rails (5.25.0) + sentry-rails (5.26.0) railties (>= 5.0) - sentry-ruby (~> 5.25.0) - sentry-ruby (5.25.0) + sentry-ruby (~> 5.26.0) + sentry-ruby (5.26.0) bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) - solid_cable (3.0.8) + solid_cable (3.0.11) actioncable (>= 7.2) activejob (>= 7.2) activerecord (>= 7.2) @@ -519,11 +520,11 @@ GEM fugit (~> 1.11.0) railties (>= 7.1) thor (~> 1.3.1) - sqlite3 (2.7.0) + sqlite3 (2.7.1) mini_portile2 (~> 2.8.0) - sqlite3 (2.7.0-arm64-darwin) - sqlite3 (2.7.0-x86_64-darwin) - sqlite3 (2.7.0-x86_64-linux-gnu) + sqlite3 (2.7.1-arm64-darwin) + sqlite3 (2.7.1-x86_64-darwin) + sqlite3 (2.7.1-x86_64-linux-gnu) sshkit (1.24.0) base64 logger diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index bdbad0bcc..4e028663f 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -85,7 +85,7 @@ .card__background { .card:has(& img:not([src=""])) { - .card__body, + .card__content, .card__meta, .workflow-stage:not(.workflow-stage--current) { opacity: 0; @@ -108,6 +108,10 @@ } } } + + .card__steps { + display: flex; + } } /* Column elements @@ -197,7 +201,7 @@ .card__header { color: color-mix(in srgb, var(--card-color) 40%, var(--color-ink)); - margin-inline-start: calc(-0.5 * var(--card-padding-inline)); + margin-inline: calc(-0.5 * var(--card-padding-inline)) calc(-0.25 * var(--card-padding-inline)); } .card__people-label { diff --git a/app/assets/stylesheets/cards.css b/app/assets/stylesheets/cards.css index 9595b98ee..4462dfb6e 100644 --- a/app/assets/stylesheets/cards.css +++ b/app/assets/stylesheets/cards.css @@ -119,8 +119,8 @@ flex-wrap: nowrap; gap: var(--inline-space); margin-block-start: calc(-1 * var(--card-padding-block)); - margin-inline-start: calc(-1 * var(--card-padding-inline)); - max-inline-size: 100%; + margin-inline: calc(-1 * var(--card-padding-inline)) calc(-0.5 * var(--card-padding-inline)); + max-inline-size: unset; min-inline-size: 0; .workflow-stage { diff --git a/app/assets/stylesheets/comments.css b/app/assets/stylesheets/comments.css index bba7f8a34..a8b8da16b 100644 --- a/app/assets/stylesheets/comments.css +++ b/app/assets/stylesheets/comments.css @@ -37,8 +37,14 @@ } .comment__author { - .comment--system & strong { - display: none; + .comment--system & { + h3 { + margin-inline: auto; + } + + strong { + display: none; + } } .btn { diff --git a/app/assets/stylesheets/icons.css b/app/assets/stylesheets/icons.css index fd12630d9..2870088c2 100644 --- a/app/assets/stylesheets/icons.css +++ b/app/assets/stylesheets/icons.css @@ -30,6 +30,7 @@ .icon--camera { --svg: url("camera.svg "); } .icon--caret-down { --svg: url("caret-down.svg "); } .icon--check { --svg: url("check.svg "); } + .icon--check-all { --svg: url("check-all.svg "); } .icon--clipboard { --svg: url("clipboard.svg "); } .icon--close { --svg: url("close.svg "); } .icon--collection { --svg: url("collection.svg "); } diff --git a/app/assets/stylesheets/inputs.css b/app/assets/stylesheets/inputs.css index 3acda5855..ed4a51072 100644 --- a/app/assets/stylesheets/inputs.css +++ b/app/assets/stylesheets/inputs.css @@ -37,7 +37,6 @@ } .input--file { - border: 0; cursor: pointer; display: grid; inline-size: auto; @@ -52,6 +51,7 @@ } input[type="file"] { + --hover-size: 0; --input-border-color: transparent; --input-border-radius: 8px; @@ -69,9 +69,9 @@ &:focus, &:focus-visible { - --input-border-color: var(--color-selected-dark); - --input-border-radius: 8px; - --input-border-size: 0.15rem; + .input--file:has(&) { + outline: 0.15rem solid var(--color-selected-dark); + } } } diff --git a/app/assets/stylesheets/steps.css b/app/assets/stylesheets/steps.css new file mode 100644 index 000000000..3e271137f --- /dev/null +++ b/app/assets/stylesheets/steps.css @@ -0,0 +1,108 @@ +@layer components { + .step { + display: grid; + grid-template-columns: 1em auto auto; + gap: calc(var(--inline-space) * 2/3); + inline-size: auto; + } + + .step__checkbox { + --hover-color: var(--card-color); + + appearance: none; + background-color: var(--color-canvas); + block-size: 1.1em; + border: 1px solid currentColor; + border-radius: 0.15em; + color: currentColor; + display: grid; + font: inherit; + inline-size: 1.1em; + margin: 0; + place-content: center; + transform: translateY(0.1em); + + &::before { + background-color: CanvasText; + block-size: 0.65em; + box-shadow: inset 1em 1em currentColor; + clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); + content: ""; + inline-size: 0.65em; + transform: scale(0); + transform-origin: center; + transition: 150ms transform ease-in-out; + } + + &:checked::before { + transform: scale(1) rotate(10deg); + } + + &:where([disabled]):not(:hover):not(:active) { + filter: none; + opacity: 0.5; + } + } + + .step__content { + --input-border-radius: 0; + --input-border-size: 0; + --input-padding: 0; + + border-bottom: 1px solid transparent; + color: currentColor; + font-weight: 500; + margin-block-end: calc(var(--block-space) * 1/3); + + &:is(a, input[type=text]) { + --hover-size: 0; + } + + .step:has(:checked) & { + opacity: 0.7; + text-decoration: line-through; + } + + &::placeholder { + color: var(--card-color); + } + + &:is(input) { + max-inline-size: 70ch; + min-inline-size: 30ch; + + @supports (field-sizing: content) { + field-sizing: content; + min-inline-size: 15ch; + } + } + } + + .step__content--edit { + border-bottom-color: currentColor; + } + + .steps { + contain: inline-size; + display: grid; + list-style: none; + margin: 0; + max-inline-size: 100%; + padding: 0; + } + + .steps__icon { + background-color: var(--card-color); + block-size: 1.3em; + border-radius: 50%; + display: grid; + inline-size: 1.3em; + place-content: center; + + .icon { + background-color: var(--color-ink-inverted); + block-size: 0.8em; + inline-size: 0.8em; + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/trays.css b/app/assets/stylesheets/trays.css index d90c56ea8..48ae50b31 100644 --- a/app/assets/stylesheets/trays.css +++ b/app/assets/stylesheets/trays.css @@ -315,6 +315,7 @@ .card__meta .btn, .card__meta-item:not(.card__meta-item--updated), .card__stages, + .card__steps, .card__closed { display: none; } diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb index b39e7a216..0f936d05b 100644 --- a/app/channels/application_cable/connection.rb +++ b/app/channels/application_cable/connection.rb @@ -2,8 +2,9 @@ module ApplicationCable class Connection < ActionCable::Connection::Base identified_by :current_user - tenanted_connection do - set_current_user || reject_unauthorized_connection + def connect + super + ApplicationRecord.with_tenant(current_tenant) { set_current_user || reject_unauthorized_connection } end private diff --git a/app/controllers/cards/steps_controller.rb b/app/controllers/cards/steps_controller.rb new file mode 100644 index 000000000..4f3434632 --- /dev/null +++ b/app/controllers/cards/steps_controller.rb @@ -0,0 +1,35 @@ +class Cards::StepsController < ApplicationController + include CardScoped + + before_action :set_step, only: %i[ show edit update destroy ] + + def create + @step = @card.steps.create!(step_params) + render_card_replacement + end + + def show + end + + def edit + end + + def update + @step.update!(step_params) + render_card_replacement + end + + def destroy + @step.destroy! + render_card_replacement + end + + private + def set_step + @step = @card.steps.find(params[:id]) + end + + def step_params + params.expect(step: [ :content, :completed ]) + end +end diff --git a/app/controllers/collections/entropy_configurations_controller.rb b/app/controllers/collections/entropy_configurations_controller.rb new file mode 100644 index 000000000..98a2b1b0f --- /dev/null +++ b/app/controllers/collections/entropy_configurations_controller.rb @@ -0,0 +1,14 @@ +class Collections::EntropyConfigurationsController < ApplicationController + include CollectionScoped + + def update + @collection.entropy_configuration.update!(entropy_configuration_params) + + render turbo_stream: turbo_stream.replace([ @collection, :entropy_configuration ], partial: "collections/edit/auto_close", locals: { collection: @collection }) + end + + private + def entropy_configuration_params + params.expect(collection: [ :auto_close_period, :auto_reconsider_period ]) + end +end diff --git a/app/controllers/collections/publications_controller.rb b/app/controllers/collections/publications_controller.rb index feb5ae4b7..2ded7eda2 100644 --- a/app/controllers/collections/publications_controller.rb +++ b/app/controllers/collections/publications_controller.rb @@ -3,11 +3,12 @@ class Collections::PublicationsController < ApplicationController def create @collection.publish - redirect_to edit_collection_path(@collection) + render turbo_stream: turbo_stream.replace([ @collection, :publication ], partial: "collections/edit/publication", locals: { collection: @collection }) end def destroy @collection.unpublish - redirect_to edit_collection_path(@collection) + @collection.reload + render turbo_stream: turbo_stream.replace([ @collection, :publication ], partial: "collections/edit/publication", locals: { collection: @collection }) end end diff --git a/app/controllers/collections/workflows_controller.rb b/app/controllers/collections/workflows_controller.rb index 28b691f11..fd503473c 100644 --- a/app/controllers/collections/workflows_controller.rb +++ b/app/controllers/collections/workflows_controller.rb @@ -5,7 +5,7 @@ class Collections::WorkflowsController < ApplicationController def update @collection.update! workflow: @workflow - redirect_to cards_path(collection_ids: [ @collection ]) + render turbo_stream: turbo_stream.replace([ @collection, :workflows ], partial: "collections/edit/workflows", locals: { collection: @collection }) end private diff --git a/app/controllers/commands_controller.rb b/app/controllers/commands_controller.rb index adae98eb1..a5f6b90f0 100644 --- a/app/controllers/commands_controller.rb +++ b/app/controllers/commands_controller.rb @@ -29,7 +29,7 @@ class CommandsController < ApplicationController end def parsing_context - @parsing_context ||= Command::Parser::Context.new(Current.user, url: request.referrer) + @parsing_context ||= Command::Parser::Context.new(Current.user, url: request.referrer, script_name: request.script_name) end def confirmed?(command) diff --git a/app/controllers/concerns/authentication.rb b/app/controllers/concerns/authentication.rb index dc3ee7bb7..db21f5c7e 100644 --- a/app/controllers/concerns/authentication.rb +++ b/app/controllers/concerns/authentication.rb @@ -2,6 +2,9 @@ module Authentication extend ActiveSupport::Concern included do + # Checking for tenant must happen first so we redirect before trying to access the db. + before_action :require_tenant + before_action :set_current_account before_action :require_authentication helper_method :authenticated? @@ -19,6 +22,7 @@ module Authentication end def require_untenanted_access(**options) + skip_before_action :require_tenant, **options skip_before_action :set_current_account, **options skip_before_action :require_authentication, **options before_action :redirect_tenanted_request, **options @@ -30,6 +34,10 @@ module Authentication Current.session.present? end + def require_tenant + ApplicationRecord.current_tenant.present? || request_authentication + end + def require_authentication resume_session || request_authentication end @@ -48,11 +56,7 @@ module Authentication def request_authentication session[:return_to_after_authenticating] = request.url - if Rails.application.config.x.local_authentication - redirect_to new_session_path - else - redirect_to Launchpad.login_url(account: Current.account), allow_other_host: true - end + redirect_to Launchpad.login_url(product: true, account: Current.account), allow_other_host: true end def after_authentication_url diff --git a/app/controllers/sessions/launchpad_controller.rb b/app/controllers/sessions/launchpad_controller.rb index 0e2ad16a7..8a9a082dd 100644 --- a/app/controllers/sessions/launchpad_controller.rb +++ b/app/controllers/sessions/launchpad_controller.rb @@ -1,8 +1,7 @@ class Sessions::LaunchpadController < ApplicationController require_unauthenticated_access - before_action :store_sig, only: :show - before_action :restore_and_clear_sig, only: :update + before_action :require_sig def show end @@ -17,11 +16,7 @@ class Sessions::LaunchpadController < ApplicationController end private - def store_sig - cookies[:_fizzy_launchpad_sig] = params.expect(:sig) - end - - def restore_and_clear_sig - @sig = cookies.delete :_fizzy_launchpad_sig + def require_sig + @sig = params.expect(:sig) end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 428c5a7f4..fa7d98974 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,28 +1,6 @@ class SessionsController < ApplicationController - require_unauthenticated_access only: %i[ new create ] - rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_path, alert: "Try again later." } - - before_action :require_first_run_completion - - def new - end - - def create - if user = User.active.authenticate_by(params.permit(:email_address, :password)) - start_new_session_for user - redirect_to after_authentication_url - else - redirect_to new_session_path, alert: "Try another email address or password." - end - end - def destroy terminate_session - redirect_to new_session_path + redirect_to Launchpad.login_url, allow_other_host: true end - - private - def require_first_run_completion - redirect_to first_run_path if Account.none? - end end diff --git a/app/controllers/signup/base_controller.rb b/app/controllers/signup/base_controller.rb index 40ae83e92..088937b04 100644 --- a/app/controllers/signup/base_controller.rb +++ b/app/controllers/signup/base_controller.rb @@ -1,6 +1,5 @@ class Signup::BaseController < ApplicationController require_untenanted_access - before_action :redirect_if_local_auth # TODO: Remove this auth before launch. http_basic_authenticate_with( @@ -34,8 +33,4 @@ class Signup::BaseController < ApplicationController redirect_to account.signal_account.owner.remote_login_url(proceed_to: root_path), allow_other_host: true end - - def redirect_if_local_auth - render plain: "Unauthorized", status: :unauthorized if Rails.application.config.x.local_authentication - end end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 6b4ffd47d..51e69e94b 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -37,7 +37,7 @@ module EventsHelper def event_cluster_tag(hour, col, &) row = 25 - hour - tag.ul class: "events__time-block", style: "grid-area: #{row}/#{col}", & + tag.div class: "events__time-block", style: "grid-area: #{row}/#{col}", & end def event_next_page_link(next_day) diff --git a/app/helpers/tenanting_helper.rb b/app/helpers/tenanting_helper.rb new file mode 100644 index 000000000..b5c50e80d --- /dev/null +++ b/app/helpers/tenanting_helper.rb @@ -0,0 +1,7 @@ +module TenantingHelper + def tenanted_action_cable_meta_tag + tag "meta", + name: "action-cable-url", + content: "#{request.script_name}#{ActionCable.server.config.mount_path}" + end +end diff --git a/app/javascript/controllers/dialog_controller.js b/app/javascript/controllers/dialog_controller.js index fd4d122e0..123c2384e 100644 --- a/app/javascript/controllers/dialog_controller.js +++ b/app/javascript/controllers/dialog_controller.js @@ -6,6 +6,10 @@ export default class extends Controller { modal: { type: Boolean, default: false } } + connect() { + this.dialogTarget.setAttribute('aria-hidden', 'true') + } + open() { const modal = this.modalValue @@ -14,6 +18,7 @@ export default class extends Controller { } else { this.dialogTarget.show() } + this.dialogTarget.setAttribute('aria-hidden', 'false') } toggle() { @@ -26,6 +31,7 @@ export default class extends Controller { close() { this.dialogTarget.close() + this.dialogTarget.setAttribute('aria-hidden', 'true') this.dialogTarget.blur() } diff --git a/app/jobs/sqlite_backups_job.rb b/app/jobs/sqlite_backups_job.rb index 6e7559d80..10b0efe37 100644 --- a/app/jobs/sqlite_backups_job.rb +++ b/app/jobs/sqlite_backups_job.rb @@ -92,7 +92,7 @@ class SQLiteBackupsJob < ApplicationJob end def db_path(tenant) - db_config.database + db_config.database_path end def db_config diff --git a/app/models/account/signal_account.rb b/app/models/account/signal_account.rb index 610a4ed9c..a01af7ad2 100644 --- a/app/models/account/signal_account.rb +++ b/app/models/account/signal_account.rb @@ -2,10 +2,7 @@ module Account::SignalAccount extend ActiveSupport::Concern included do - unless Rails.application.config.x.local_authentication - # TODO: remove the "optional: true" once we've populated the accounts properly - belongs_to :signal_account, class_name: "SignalId::Account", primary_key: :queenbee_id, foreign_key: :queenbee_id, optional: true - end + belongs_to :signal_account, class_name: "SignalId::Account", primary_key: :queenbee_id, foreign_key: :queenbee_id end class_methods do diff --git a/app/models/card.rb b/app/models/card.rb index 94ee043d2..c83c1325b 100644 --- a/app/models/card.rb +++ b/app/models/card.rb @@ -1,6 +1,6 @@ class Card < ApplicationRecord include Assignable, Colored, Engageable, Entropic, Eventable, - Golden, Mentions, Pinnable, Closeable, Readable, Searchable, + Golden, Mentions, Multistep, Pinnable, Closeable, Readable, Searchable, Staged, Stallable, Statuses, Taggable, Watchable belongs_to :collection, touch: true diff --git a/app/models/card/multistep.rb b/app/models/card/multistep.rb new file mode 100644 index 000000000..e69b07e6f --- /dev/null +++ b/app/models/card/multistep.rb @@ -0,0 +1,7 @@ +module Card::Multistep + extend ActiveSupport::Concern + + included do + has_many :steps, dependent: :destroy + end +end diff --git a/app/models/card/statuses.rb b/app/models/card/statuses.rb index 636d9127d..73d3f122a 100644 --- a/app/models/card/statuses.rb +++ b/app/models/card/statuses.rb @@ -4,6 +4,8 @@ module Card::Statuses included do enum :status, %w[ creating drafted published ].index_by(&:itself) + after_create -> { track_event :published }, if: :published? + scope :published_or_drafted_by, ->(user) { where(status: :published).or(where(status: :drafted, creator: user)) } end diff --git a/app/models/command/ai/parser.rb b/app/models/command/ai/parser.rb index b1dece523..ab973ca02 100644 --- a/app/models/command/ai/parser.rb +++ b/app/models/command/ai/parser.rb @@ -7,6 +7,7 @@ class Command::Ai::Parser def initialize(context) @context = context + self.default_url_options[:script_name] = context.script_name end def parse(query) @@ -71,7 +72,7 @@ class Command::Ai::Parser def context_from_query(query_json) if context_properties = query_json[:context].presence url = cards_path(**context_properties) - Command::Parser::Context.new(user, url: url) + Command::Parser::Context.new(user, url: url, script_name: context.script_name) end end end diff --git a/app/models/command/parser.rb b/app/models/command/parser.rb index a7aace31f..297d307d9 100644 --- a/app/models/command/parser.rb +++ b/app/models/command/parser.rb @@ -1,7 +1,7 @@ class Command::Parser attr_reader :context - delegate :user, :cards, :filter, to: :context + delegate :user, :cards, :filter, :script_name, to: :context def initialize(context) @context = context @@ -12,6 +12,7 @@ class Command::Parser command.user = user command.line ||= as_plain_text(string) command.context ||= context + command.default_url_options[:script_name] = script_name end end diff --git a/app/models/command/parser/context.rb b/app/models/command/parser/context.rb index c5148bd0c..0c4441ba7 100644 --- a/app/models/command/parser/context.rb +++ b/app/models/command/parser/context.rb @@ -1,9 +1,10 @@ class Command::Parser::Context - attr_reader :user, :url + attr_reader :user, :url, :script_name - def initialize(user, url:) + def initialize(user, url:, script_name: "") @user = user @url = url + @script_name = script_name extract_url_components end @@ -81,7 +82,8 @@ class Command::Parser::Context def extract_url_components uri = URI.parse(url || "") - route = Rails.application.routes.recognize_path(uri.path) + path = uri.path.delete_prefix(script_name) + route = Rails.application.routes.recognize_path(path) @controller = route[:controller] @action = route[:action] @params = ActionController::Parameters.new(Rack::Utils.parse_nested_query(uri.query).merge(route.except(:controller, :action))) diff --git a/app/models/command/visit_url.rb b/app/models/command/visit_url.rb index 6066ea0ed..dabd9f52d 100644 --- a/app/models/command/visit_url.rb +++ b/app/models/command/visit_url.rb @@ -6,6 +6,20 @@ class Command::VisitUrl < Command end def execute - redirect_to url + redirect_to real_url end + + private + def real_url + case url + when String + if url.start_with?(context.script_name) + url + else + [ context&.script_name, url ].compact.join + end + else + url + end + end end diff --git a/app/models/step.rb b/app/models/step.rb new file mode 100644 index 000000000..c4e35f33e --- /dev/null +++ b/app/models/step.rb @@ -0,0 +1,9 @@ +class Step < ApplicationRecord + belongs_to :card, touch: true + + scope :completed, -> { where(completed: true) } + + def completed? + completed + end +end diff --git a/app/models/user/signal_user.rb b/app/models/user/signal_user.rb index 924cfe87d..16bee49bc 100644 --- a/app/models/user/signal_user.rb +++ b/app/models/user/signal_user.rb @@ -2,8 +2,6 @@ module User::SignalUser extend ActiveSupport::Concern included do - unless Rails.application.config.x.local_authentication - belongs_to :signal_user, dependent: :destroy, class_name: "SignalId::User", optional: true - end + belongs_to :signal_user, dependent: :destroy, class_name: "SignalId::User", optional: true end end diff --git a/app/views/accounts/_user.html.erb b/app/views/accounts/_user.html.erb index cdf3c9beb..f176236c3 100644 --- a/app/views/accounts/_user.html.erb +++ b/app/views/accounts/_user.html.erb @@ -1,5 +1,5 @@