diff --git a/.github/workflows/ci-main.yml b/.github/workflows/ci-main.yml new file mode 100644 index 0000000000..7e79ee0e70 --- /dev/null +++ b/.github/workflows/ci-main.yml @@ -0,0 +1,12 @@ +name: CI (Main) + +on: + push: + +jobs: + call-ci: + uses: ./.github/workflows/ci.yml + with: + saas: true + secrets: + GH_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml new file mode 100644 index 0000000000..c6712ad938 --- /dev/null +++ b/.github/workflows/ci-pr.yml @@ -0,0 +1,10 @@ +name: CI (PR) + +on: + pull_request: + +jobs: + call-ci: + uses: ./.github/workflows/ci.yml + with: + saas: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..7984fca661 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,106 @@ +name: Common CI + +on: + workflow_call: + inputs: + saas: + type: boolean + required: true + secrets: + GH_TOKEN: + required: false + +jobs: + security: + name: Security + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Gem audit + run: bin/bundler-audit check --update + + - name: Importmap audit + run: bin/importmap audit + + - name: Brakeman audit + run: bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error + + + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Lint code for consistent style + run: bin/rubocop + + + test: + name: Tests (${{ matrix.mode }}) + runs-on: ubuntu-latest + + strategy: + matrix: + include: + - mode: SQLite + db_adapter: sqlite + - mode: MySQL + db_adapter: mysql + - mode: SaaS + db_adapter: mysql + saas: ${{ inputs.saas }} + + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: fizzy_test + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + env: + RAILS_ENV: test + DATABASE_ADAPTER: ${{ matrix.db_adapter }} + ${{ inputs.saas && 'SAAS' || 'SAAS_DISABLED' }}: ${{ inputs.saas && '1' || '' }} + BUNDLE_GEMFILE: ${{ inputs.saas && 'Gemfile.saas' || 'Gemfile' }} + MYSQL_HOST: 127.0.0.1 + MYSQL_PORT: 3306 + MYSQL_USER: root + FIZZY_DB_HOST: 127.0.0.1 + FIZZY_DB_PORT: 3306 + BUNDLE_GITHUB__COM: ${{ inputs.saas && format('x-access-token:{0}', secrets.GH_TOKEN) || '' }} + + steps: + - name: Install system packages + run: sudo apt-get update && sudo apt-get install --no-install-recommends -y libsqlite3-0 libvips curl ffmpeg + + - uses: actions/checkout@v4 + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Run tests + run: bin/rails db:setup test + + - name: Run system tests + run: bin/rails test:system diff --git a/.kamal/hooks/post-deploy b/.kamal/hooks/post-deploy deleted file mode 100755 index 8715060788..0000000000 --- a/.kamal/hooks/post-deploy +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -MESSAGE="$KAMAL_PERFORMER deployed $KAMAL_SERVICE_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds" -CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) - -bin/notify_dash_of_deployment "$MESSAGE" $KAMAL_VERSION $KAMAL_PERFORMER $CURRENT_BRANCH $KAMAL_DESTINATION $KAMAL_RUNTIME - -if [[ $CURRENT_BRANCH == "main" && $KAMAL_DESTINATION == "production" ]]; then - gh release create $KAMAL_SERVICE_VERSION --target $KAMAL_VERSION --generate-notes 2> /dev/null || true - - RELEASE_URL=$(gh release view $KAMAL_SERVICE_VERSION --json url,body --jq .url) - RELEASE_BODY=$(gh release view $KAMAL_SERVICE_VERSION --json url,body --jq .body) - - bin/broadcast_to_bc "$MESSAGE "$'\n'"$RELEASE_URL "$'\n'"$RELEASE_BODY" -else - bin/broadcast_to_bc "$MESSAGE" -fi diff --git a/.kamal/hooks/pre-build b/.kamal/hooks/pre-build deleted file mode 100755 index 99de8a8248..0000000000 --- a/.kamal/hooks/pre-build +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env ruby - -def exit_with_error(message) - $stderr.puts message - exit 1 -end - -def check_branch - if ENV["KAMAL_DESTINATION"] == "production" - current_branch = `git branch --show-current`.strip - - if current_branch != "main" - exit_with_error "Only the `main` branch should be deployed to production, current branch is #{current_branch}. If this is expected, try again with `SKIP_GIT_CHECKS=1` prepended to the command" - end - end -end - -def check_for_uncommitted_changes - if `git status --porcelain`.strip.length != 0 - exit_with_error "You have uncommitted changes, aborting" - end -end - -def check_local_and_remote_heads_match - remote_head = `git ls-remote origin --tags $(git branch --show-current) | cut -f1 | head -1`.strip - local_head = `git rev-parse HEAD`.strip - - if local_head != remote_head - exit_with_error "Remote HEAD #{remote_head}, differs from local HEAD #{local_head}, aborting" - end -end - -unless ENV["SKIP_GIT_CHECKS"] - check_branch - check_for_uncommitted_changes - check_local_and_remote_heads_match -end diff --git a/.kamal/hooks/pre-connect b/.kamal/hooks/pre-connect deleted file mode 100755 index 5f42f2cf2b..0000000000 --- a/.kamal/hooks/pre-connect +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env bash - -# Validate hostnames are FQDNs ending in -int.37signals.com -if command -v yq >/dev/null 2>&1; then - declare -A SUGGESTIONS - while IFS= read -r host; do - if [[ ! $host =~ -int\.37signals\.com$ ]]; then - if [[ $host =~ -4[0-9]{2}$ ]]; then - SUGGESTIONS["$host"]="$host.df-ams-int.37signals.com" - elif [[ $host =~ -1[0-9]{2}$ ]]; then - SUGGESTIONS["$host"]="$host.df-iad-int.37signals.com" - else - SUGGESTIONS["$host"]="$host.sc-chi-int.37signals.com" - fi - fi - done < <(bin/kamal config -d "${KAMAL_DESTINATION:-production}" 2>/dev/null | yq -r '.":hosts"[]') - - if [ ${#SUGGESTIONS[@]} -gt 0 ]; then - echo "Unqualified hostnames found in config/deploy.${KAMAL_DESTINATION:-production}.yml:" >&2 - echo "" >&2 - echo "Update to use fully-qualified hostnames:" >&2 - for host in "${!SUGGESTIONS[@]}"; do - echo " $host → ${SUGGESTIONS[$host]}" >&2 - done - exit 1 - fi -fi - -# Verify Tailscale connection and SSH authentication before deploying. -tailscale_cmd() { - if command -v tailscale >/dev/null 2>&1; then - tailscale "$@" - elif [ -f "/Applications/Tailscale.app/Contents/MacOS/Tailscale" ]; then - env TAILSCALE_BE_CLI=1 /Applications/Tailscale.app/Contents/MacOS/Tailscale "$@" - else - return 1 - fi -} - -on_tailscale() { - tailscale_cmd status --json 2>/dev/null | jq -e '.Self.Online' >/dev/null 2>&1 -} - -# Check Tailscale connection -if ! on_tailscale; then - echo "" >&2 - echo "You must be connected to Tailscale to deploy." >&2 - echo "" >&2 - echo "→ Connect to Tailscale and try again" >&2 - echo "" >&2 - exit 1 -fi - -# Verify SSH access -echo "Deploying via Tailscale. Verifying SSH access…" >&2 - -TEST_HOST="fizzy-app-101" - -SSH_OUTPUT=$(ssh -o ConnectTimeout=5 "app@$TEST_HOST" true 2>&1) -SSH_EXIT=$? - -echo "$SSH_OUTPUT" >&2 - -if echo "$SSH_OUTPUT" | grep -q "Permission denied"; then - GITHUB_USER=$(gh api user 2>/dev/null | jq -r '.login // "unknown"') - GITHUB_KEYS_URL="https://github.com/${GITHUB_USER}.keys" - - echo "" >&2 - echo "ERROR: SSH authentication failed" >&2 - echo "" >&2 - echo "You must deploy with an SSH key that's on your GitHub account." >&2 - echo "" >&2 - echo "→ Verify your public key is at $GITHUB_KEYS_URL" >&2 - echo " Add it at https://github.com/settings/keys if not" >&2 - echo "" >&2 - echo "Note that SSH keys are pulled from GitHub every 5 minutes, so if you've" >&2 - echo "just added a new key to GitHub, try again in five." >&2 - echo "" >&2 - exit 1 -fi - -exit $SSH_EXIT diff --git a/Dockerfile b/Dockerfile index 6341f3fc19..3cd25c7f19 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,77 +1,76 @@ +# syntax=docker/dockerfile:1 +# check=error=true + +# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand: +# docker build -t fizzy . +# docker run -d -p 80:80 -e RAILS_MASTER_KEY= --name fizzy fizzy + +# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html + # Make sure RUBY_VERSION matches the Ruby version in .ruby-version ARG RUBY_VERSION=3.4.7 -FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim AS base +FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base # Rails app lives here WORKDIR /rails -# Set production environment +# Install base packages +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 && \ + ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + +# Set production environment variables and enable jemalloc for reduced memory usage and latency. ENV RAILS_ENV="production" \ BUNDLE_DEPLOYMENT="1" \ BUNDLE_PATH="/usr/local/bundle" \ - BUNDLE_WITHOUT="development" - + BUNDLE_WITHOUT="development" \ + LD_PRELOAD="/usr/local/lib/libjemalloc.so" # Throw-away build stage to reduce size of final image FROM base AS build # Install packages needed to build gems RUN apt-get update -qq && \ - apt-get install -y --no-install-recommends -y build-essential pkg-config git libvips libyaml-dev libssl-dev && \ + apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config && \ rm -rf /var/lib/apt/lists /var/cache/apt/archives # Install application gems -COPY Gemfile Gemfile.lock .ruby-version ./ -COPY lib/bootstrap.rb ./lib/bootstrap.rb -COPY gems ./gems/ -RUN --mount=type=secret,id=GITHUB_TOKEN --mount=type=cache,id=fizzy-permabundle-${RUBY_VERSION},sharing=locked,target=/permabundle \ - gem install bundler && \ - BUNDLE_PATH=/permabundle BUNDLE_GITHUB__COM="$(cat /run/secrets/GITHUB_TOKEN):x-oauth-basic" bundle install && \ - cp -a /permabundle/. "$BUNDLE_PATH"/ && \ - bundle clean --force && \ - rm -rf "$BUNDLE_PATH"/ruby/*/bundler/gems/*/.git && \ - find "$BUNDLE_PATH" -type f \( -name '*.gem' -o -iname '*.a' -o -iname '*.o' -o -iname '*.h' -o -iname '*.c' -o -iname '*.hpp' -o -iname '*.cpp' \) -delete && \ - bundle exec bootsnap precompile --gemfile +COPY Gemfile Gemfile.lock vendor ./ + +RUN bundle install && \ + rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ + # -j 1 disable parallel compilation to avoid a QEMU bug: https://github.com/rails/bootsnap/issues/495 + bundle exec bootsnap precompile -j 1 --gemfile # Copy application code COPY . . -# Precompile bootsnap code for faster boot times -RUN bundle exec bootsnap precompile app/ lib/ +# Precompile bootsnap code for faster boot times. +# -j 1 disable parallel compilation to avoid a QEMU bug: https://github.com/rails/bootsnap/issues/495 +RUN bundle exec bootsnap precompile -j 1 app/ lib/ # Precompiling assets for production without requiring secret RAILS_MASTER_KEY RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile + + + # Final stage for app image FROM base -# Install packages needed for deployment -RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y curl libsqlite3-0 libvips build-essential ffmpeg groff libreoffice-writer libreoffice-impress libreoffice-calc mupdf-tools sqlite3 libjemalloc-dev && \ - rm -rf /var/lib/apt/lists /var/cache/apt/archives +# Run and own only the runtime files as a non-root user for security +RUN groupadd --system --gid 1000 rails && \ + useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash +USER 1000:1000 # Copy built artifacts: gems, application -COPY --from=build /usr/local/bundle /usr/local/bundle -COPY --from=build /rails /rails - -# Run and own only the runtime files as a non-root user for security -RUN useradd rails --create-home --shell /bin/bash && \ - chown -R rails:rails db log storage tmp -USER rails:rails +COPY --chown=rails:rails --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" +COPY --chown=rails:rails --from=build /rails /rails # Entrypoint prepares the database. ENTRYPOINT ["/rails/bin/docker-entrypoint"] -# Ruby GC tuning values pulled from Autotuner recommendations -ENV RUBY_GC_HEAP_0_INIT_SLOTS=692636 \ - RUBY_GC_HEAP_1_INIT_SLOTS=175943 \ - RUBY_GC_HEAP_2_INIT_SLOTS=148807 \ - RUBY_GC_HEAP_3_INIT_SLOTS=9169 \ - RUBY_GC_HEAP_4_INIT_SLOTS=3054 \ - RUBY_GC_MALLOC_LIMIT=33554432 \ - RUBY_GC_MALLOC_LIMIT_MAX=67108864 \ - LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2 - -# Start the server by default, this can be overwritten at runtime -EXPOSE 80 443 9394 +# Start server via Thruster by default, this can be overwritten at runtime +EXPOSE 80 CMD ["./bin/thrust", "./bin/rails", "server"] diff --git a/Dockerfile.dev b/Dockerfile.dev index 75b284570a..9c20fcda5d 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -18,7 +18,7 @@ RUN apt-get update -qq && \ # Install application gems COPY Gemfile Gemfile.lock .ruby-version ./ -COPY lib/bootstrap.rb ./lib/bootstrap.rb +COPY lib/fizzy.rb ./lib/fizzy.rb COPY gems ./gems/ RUN --mount=type=secret,id=GITHUB_TOKEN --mount=type=cache,id=fizzy-devbundle-${RUBY_VERSION},sharing=locked,target=/devbundle \ gem install bundler foreman && \ diff --git a/Gemfile b/Gemfile index d008f49073..ce2324e1b3 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source "https://rubygems.org" -git_source(:bc) { |repo| "https://github.com/basecamp/#{repo}" } gem "rails", github: "rails/rails", branch: "main" +git_source(:bc) { |repo| "https://github.com/basecamp/#{repo}" } # Assets & front end gem "importmap-rails" @@ -40,7 +40,6 @@ gem "useragent", bc: "useragent" gem "mission_control-jobs" gem "sentry-ruby" gem "sentry-rails" -gem "rails_structured_logging", bc: "rails-structured-logging" gem "yabeda" gem "yabeda-actioncable" gem "yabeda-activejob", github: "basecamp/yabeda-activejob", branch: "bulk-and-scheduled-jobs" @@ -75,9 +74,3 @@ group :test do gem "vcr" gem "mocha" end - -require_relative "lib/bootstrap" -unless Bootstrap.oss_config? - eval_gemfile "gems/fizzy-saas/Gemfile" - gem "fizzy-saas", path: "gems/fizzy-saas" -end diff --git a/Gemfile.lock b/Gemfile.lock index f8001333cc..ded3d30825 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,21 +1,3 @@ -GIT - remote: https://github.com/basecamp/queenbee-plugin - revision: eb01c697de1ad028afc65cc7d9b5345a7a8e849f - ref: eb01c697de1ad028afc65cc7d9b5345a7a8e849f - specs: - queenbee (3.2.0) - activeresource - builder - rexml - -GIT - remote: https://github.com/basecamp/rails-structured-logging - revision: 76960cb5c15fc2b6b5f7542e05d7dcc031cef9e6 - specs: - rails_structured_logging (0.2.1) - json - rails (>= 6.0.0) - GIT remote: https://github.com/basecamp/useragent revision: 433ca320a42db1266c4b89df74d0abdb9a880c5e @@ -132,27 +114,11 @@ GIT tsort (>= 0.2) zeitwerk (~> 2.6) -PATH - remote: gems/fizzy-saas - specs: - fizzy-saas (0.1.0) - queenbee - rails (>= 8.1.0.beta1) - rails_structured_logging - GEM remote: https://rubygems.org/ specs: action_text-trix (2.1.15) railties - activemodel-serializers-xml (1.0.3) - activemodel (>= 5.0.0.a) - activesupport (>= 5.0.0.a) - builder (~> 3.1) - activeresource (6.2.0) - activemodel (>= 7.0) - activemodel-serializers-xml (~> 1.0) - activesupport (>= 7.0) addressable (2.8.8) public_suffix (>= 2.0.2, < 8.0) anyway_config (2.7.2) @@ -585,7 +551,6 @@ PLATFORMS x86_64-linux-musl DEPENDENCIES - activeresource autotuner aws-sdk-s3 bcrypt (~> 3.1.7) @@ -596,7 +561,6 @@ DEPENDENCIES capybara debug faker - fizzy-saas! geared_pagination (~> 1.2) image_processing (~> 1.14) importmap-rails @@ -612,10 +576,8 @@ DEPENDENCIES prometheus-client-mmap (~> 1.3) propshaft puma (>= 5.0) - queenbee! rack-mini-profiler rails! - rails_structured_logging! redcarpet rouge rqrcode diff --git a/Gemfile.saas b/Gemfile.saas new file mode 100644 index 0000000000..779c8c4198 --- /dev/null +++ b/Gemfile.saas @@ -0,0 +1,11 @@ +# This Gemfile extends the base Gemfile with SaaS-specific dependencies +eval_gemfile "Gemfile" + +git_source(:bc) { |repo| "https://github.com/basecamp/#{repo}" } + + +gem "activeresource", require: "active_resource" +gem "queenbee", bc: "queenbee-plugin" +gem "fizzy-saas", bc: "fizzy-saas" +gem "rails_structured_logging", bc: "rails-structured-logging" + diff --git a/Gemfile.saas.lock b/Gemfile.saas.lock new file mode 100644 index 0000000000..bebc6098fa --- /dev/null +++ b/Gemfile.saas.lock @@ -0,0 +1,651 @@ +GIT + remote: https://github.com/basecamp/fizzy-saas + revision: 3bf089d1af610c828fae1e6a15d5d6b0fbce9b5b + specs: + fizzy-saas (0.1.0) + queenbee + rails (>= 8.1.0.beta1) + rails_structured_logging + +GIT + remote: https://github.com/basecamp/queenbee-plugin + revision: 15faf03a876c5e66b67753d2e1ddb24f1eb5abb2 + specs: + queenbee (3.2.0) + activeresource + builder + rexml + +GIT + remote: https://github.com/basecamp/rails-structured-logging + revision: 76960cb5c15fc2b6b5f7542e05d7dcc031cef9e6 + specs: + rails_structured_logging (0.2.1) + json + rails (>= 6.0.0) + +GIT + remote: https://github.com/basecamp/useragent + revision: 433ca320a42db1266c4b89df74d0abdb9a880c5e + specs: + useragent (0.16.11) + +GIT + remote: https://github.com/basecamp/yabeda-activejob.git + revision: 684973f77ff01d8b3dd75874538fae55961e15e6 + branch: bulk-and-scheduled-jobs + specs: + yabeda-activejob (0.6.0) + rails (>= 6.1) + yabeda (~> 0.6) + +GIT + remote: https://github.com/rails/rails.git + revision: 4f7ab01bb5d6be78c7447dbb230c55027d08ae34 + branch: main + specs: + actioncable (8.2.0.alpha) + actionpack (= 8.2.0.alpha) + activesupport (= 8.2.0.alpha) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.2.0.alpha) + actionpack (= 8.2.0.alpha) + activejob (= 8.2.0.alpha) + activerecord (= 8.2.0.alpha) + activestorage (= 8.2.0.alpha) + activesupport (= 8.2.0.alpha) + mail (>= 2.8.0) + actionmailer (8.2.0.alpha) + actionpack (= 8.2.0.alpha) + actionview (= 8.2.0.alpha) + activejob (= 8.2.0.alpha) + activesupport (= 8.2.0.alpha) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.2.0.alpha) + actionview (= 8.2.0.alpha) + activesupport (= 8.2.0.alpha) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.2.0.alpha) + action_text-trix (~> 2.1.15) + actionpack (= 8.2.0.alpha) + activerecord (= 8.2.0.alpha) + activestorage (= 8.2.0.alpha) + activesupport (= 8.2.0.alpha) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.2.0.alpha) + activesupport (= 8.2.0.alpha) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.2.0.alpha) + activesupport (= 8.2.0.alpha) + globalid (>= 0.3.6) + activemodel (8.2.0.alpha) + activesupport (= 8.2.0.alpha) + activerecord (8.2.0.alpha) + activemodel (= 8.2.0.alpha) + activesupport (= 8.2.0.alpha) + timeout (>= 0.4.0) + activestorage (8.2.0.alpha) + actionpack (= 8.2.0.alpha) + activejob (= 8.2.0.alpha) + activerecord (= 8.2.0.alpha) + activesupport (= 8.2.0.alpha) + marcel (~> 1.0) + activesupport (8.2.0.alpha) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + json + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + rails (8.2.0.alpha) + actioncable (= 8.2.0.alpha) + actionmailbox (= 8.2.0.alpha) + actionmailer (= 8.2.0.alpha) + actionpack (= 8.2.0.alpha) + actiontext (= 8.2.0.alpha) + actionview (= 8.2.0.alpha) + activejob (= 8.2.0.alpha) + activemodel (= 8.2.0.alpha) + activerecord (= 8.2.0.alpha) + activestorage (= 8.2.0.alpha) + activesupport (= 8.2.0.alpha) + bundler (>= 1.15.0) + railties (= 8.2.0.alpha) + railties (8.2.0.alpha) + actionpack (= 8.2.0.alpha) + activesupport (= 8.2.0.alpha) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) + zeitwerk (~> 2.6) + +GEM + remote: https://rubygems.org/ + specs: + action_text-trix (2.1.15) + railties + activemodel-serializers-xml (1.0.3) + activemodel (>= 5.0.0.a) + activesupport (>= 5.0.0.a) + builder (~> 3.1) + activeresource (6.2.0) + activemodel (>= 7.0) + activemodel-serializers-xml (~> 1.0) + activesupport (>= 7.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + anyway_config (2.7.2) + ruby-next-core (~> 1.0) + ast (2.4.3) + autotuner (1.1.0) + aws-eventstream (1.4.0) + aws-partitions (1.1187.0) + aws-sdk-core (3.239.1) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + bigdecimal + jmespath (~> 1, >= 1.6.1) + logger + aws-sdk-kms (1.118.0) + aws-sdk-core (~> 3, >= 3.239.1) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.205.0) + aws-sdk-core (~> 3, >= 3.234.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.12.1) + aws-eventstream (~> 1, >= 1.0.2) + base64 (0.3.0) + bcrypt (3.1.20) + bcrypt_pbkdf (1.1.1) + benchmark (0.5.0) + bigdecimal (3.3.1) + bindex (0.8.1) + bootsnap (1.19.0) + msgpack (~> 1.2) + brakeman (7.1.1) + racc + builder (3.3.0) + bundler-audit (0.9.2) + bundler (>= 1.2.0, < 3) + thor (~> 1.0) + capybara (3.40.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.11) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + childprocess (5.1.0) + logger (~> 1.5) + chunky_png (1.4.0) + concurrent-ruby (1.3.5) + connection_pool (2.5.4) + crack (1.0.1) + bigdecimal + rexml + crass (1.0.6) + date (3.5.0) + debug (1.11.0) + irb (~> 1.10) + reline (>= 0.3.8) + dotenv (3.1.8) + drb (2.2.3) + dry-initializer (3.2.0) + ed25519 (1.4.0) + erb (6.0.0) + erubi (1.13.1) + et-orbi (1.4.0) + tzinfo + faker (3.5.2) + i18n (>= 1.8.11, < 2) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + fugit (1.12.1) + et-orbi (~> 1.4) + raabro (~> 1.4) + geared_pagination (1.2.0) + activesupport (>= 5.0) + addressable (>= 2.5.0) + globalid (1.3.0) + activesupport (>= 6.1) + hashdiff (1.2.1) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + image_processing (1.14.0) + mini_magick (>= 4.9.5, < 6) + ruby-vips (>= 2.0.17, < 3) + importmap-rails (2.2.2) + actionpack (>= 6.0.0) + activesupport (>= 6.0.0) + railties (>= 6.0.0) + io-console (0.8.1) + irb (1.15.3) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jbuilder (2.14.1) + actionview (>= 7.0.0) + activesupport (>= 7.0.0) + jmespath (1.6.2) + json (2.16.0) + jwt (3.1.2) + base64 + kamal (2.8.2) + activesupport (>= 7.0) + base64 (~> 0.2) + bcrypt_pbkdf (~> 1.0) + concurrent-ruby (~> 1.2) + dotenv (~> 3.1) + ed25519 (~> 1.4) + net-ssh (~> 7.3) + sshkit (>= 1.23.0, < 2.0) + thor (~> 1.3) + zeitwerk (>= 2.6.18, < 3.0) + language_server-protocol (3.17.0.5) + launchy (3.1.1) + addressable (~> 2.8) + childprocess (~> 5.0) + logger (~> 1.6) + letter_opener (1.10.0) + launchy (>= 2.2, < 4) + lexxy (0.1.20.beta) + rails (>= 8.0.2) + lint_roller (1.1.0) + logger (1.7.0) + loofah (2.24.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + mail (2.9.0) + logger + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.1.0) + matrix (0.4.3) + mini_magick (5.3.1) + logger + mini_mime (1.1.5) + minitest (5.26.2) + mission_control-jobs (1.1.0) + actioncable (>= 7.1) + actionpack (>= 7.1) + activejob (>= 7.1) + activerecord (>= 7.1) + importmap-rails (>= 1.2.1) + irb (~> 1.13) + railties (>= 7.1) + stimulus-rails + turbo-rails + mittens (0.3.0) + mocha (2.8.2) + ruby2_keywords (>= 0.0.5) + msgpack (1.8.0) + net-http-persistent (4.0.6) + connection_pool (~> 2.2, >= 2.2.4) + net-imap (0.5.12) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-scp (4.1.0) + net-ssh (>= 2.6.5, < 8.0.0) + net-sftp (4.0.0) + net-ssh (>= 5.0.0, < 8.0.0) + net-smtp (0.5.1) + net-protocol + net-ssh (7.3.0) + nio4r (2.7.5) + nokogiri (1.18.10-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.18.10-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-linux-musl) + racc (~> 1.4) + openssl (3.3.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.10.0) + ast (~> 2.4.1) + racc + platform_agent (1.0.1) + activesupport (>= 5.2.0) + useragent (~> 0.16.3) + pp (0.6.3) + prettyprint + prettyprint (0.2.0) + prism (1.6.0) + prometheus-client-mmap (1.3.0) + base64 + bigdecimal + logger + rb_sys (~> 0.9.117) + prometheus-client-mmap (1.3.0-aarch64-linux-gnu) + base64 + bigdecimal + logger + rb_sys (~> 0.9.117) + prometheus-client-mmap (1.3.0-aarch64-linux-musl) + base64 + bigdecimal + logger + rb_sys (~> 0.9.117) + prometheus-client-mmap (1.3.0-x86_64-linux-gnu) + base64 + bigdecimal + logger + rb_sys (~> 0.9.117) + prometheus-client-mmap (1.3.0-x86_64-linux-musl) + base64 + bigdecimal + logger + rb_sys (~> 0.9.117) + propshaft (1.3.1) + actionpack (>= 7.0.0) + activesupport (>= 7.0.0) + rack + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + puma (7.1.0) + nio4r (~> 2.0) + raabro (1.4.0) + racc (1.8.1) + rack (3.2.4) + rack-mini-profiler (4.0.1) + rack (>= 1.2.0) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.2.1) + rack (>= 3) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + rainbow (3.1.1) + rake (13.3.1) + rake-compiler-dock (1.9.1) + rb_sys (0.9.117) + rake-compiler-dock (= 1.9.1) + rdoc (6.15.1) + erb + psych (>= 4.0.0) + tsort + redcarpet (3.6.1) + regexp_parser (2.11.3) + reline (0.6.3) + io-console (~> 0.5) + rexml (3.4.4) + rouge (4.6.1) + rqrcode (3.1.0) + chunky_png (~> 1.0) + rqrcode_core (~> 2.0) + rqrcode_core (2.0.0) + rubocop (1.81.7) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.47.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.48.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-performance (1.26.1) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) + rubocop-rails (2.34.0) + activesupport (>= 4.2.0) + lint_roller (~> 1.1) + rack (>= 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) + rubocop-rails-omakase (1.1.0) + rubocop (>= 1.72) + rubocop-performance (>= 1.24) + rubocop-rails (>= 2.30) + ruby-next-core (1.1.2) + ruby-progressbar (1.13.0) + ruby-vips (2.2.5) + ffi (~> 1.12) + logger + ruby2_keywords (0.0.5) + rubyzip (3.2.2) + securerandom (0.4.1) + selenium-webdriver (4.38.0) + base64 (~> 0.2) + logger (~> 1.4) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 4.0) + websocket (~> 1.0) + sentry-rails (6.1.1) + railties (>= 5.2.0) + sentry-ruby (~> 6.1.1) + sentry-ruby (6.1.1) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.0.2) + sniffer (0.5.0) + anyway_config (>= 1.0) + dry-initializer (~> 3) + solid_cable (3.0.12) + actioncable (>= 7.2) + activejob (>= 7.2) + activerecord (>= 7.2) + railties (>= 7.2) + solid_cache (1.0.10) + activejob (>= 7.2) + activerecord (>= 7.2) + railties (>= 7.2) + solid_queue (1.2.4) + activejob (>= 7.1) + activerecord (>= 7.1) + concurrent-ruby (>= 1.3.1) + fugit (~> 1.11) + railties (>= 7.1) + thor (>= 1.3.1) + sqlite3 (2.8.0-aarch64-linux-gnu) + sqlite3 (2.8.0-aarch64-linux-musl) + sqlite3 (2.8.0-arm-linux-gnu) + sqlite3 (2.8.0-arm-linux-musl) + sqlite3 (2.8.0-x86_64-linux-gnu) + sqlite3 (2.8.0-x86_64-linux-musl) + sshkit (1.24.0) + base64 + logger + net-scp (>= 1.1.2) + net-sftp (>= 2.1.2) + net-ssh (>= 2.8.0) + ostruct + stimulus-rails (1.3.4) + railties (>= 6.0.0) + stringio (3.1.8) + thor (1.4.0) + thruster (0.1.16) + thruster (0.1.16-aarch64-linux) + thruster (0.1.16-x86_64-linux) + timeout (0.4.4) + trilogy (2.9.0) + tsort (0.2.0) + turbo-rails (2.0.20) + actionpack (>= 7.1.0) + railties (>= 7.1.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.1.0) + uri (1.1.1) + vcr (6.3.1) + base64 + web-console (4.2.1) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + web-push (3.0.2) + jwt (~> 3.0) + openssl (~> 3.0) + webmock (3.26.1) + addressable (>= 2.8.0) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) + webrick (1.9.1) + websocket (1.2.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + yabeda (0.14.0) + anyway_config (>= 1.0, < 3) + concurrent-ruby + dry-initializer + yabeda-actioncable (0.2.2) + actioncable (>= 7.2) + activesupport (>= 7.2) + railties (>= 7.2) + yabeda (~> 0.8) + yabeda-gc (0.4.0) + yabeda (~> 0.6) + yabeda-http_requests (0.3.0) + anyway_config (>= 1.3, < 3.0) + sniffer + yabeda + yabeda-prometheus-mmap (0.4.0) + prometheus-client-mmap + yabeda (~> 0.10) + yabeda-puma-plugin (0.9.0) + json + puma + yabeda (~> 0.5) + yabeda-rails (0.10.0) + activesupport + anyway_config (>= 1.3, < 3) + railties + yabeda (~> 0.8) + zeitwerk (2.7.3) + +PLATFORMS + aarch64-linux + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + activeresource + autotuner + aws-sdk-s3 + bcrypt (~> 3.1.7) + benchmark + bootsnap + brakeman + bundler-audit + capybara + debug + faker + fizzy-saas! + geared_pagination (~> 1.2) + image_processing (~> 1.14) + importmap-rails + jbuilder + kamal + letter_opener + lexxy + mission_control-jobs + mittens + mocha + net-http-persistent + platform_agent + prometheus-client-mmap (~> 1.3) + propshaft + puma (>= 5.0) + queenbee! + rack-mini-profiler + rails! + rails_structured_logging! + redcarpet + rouge + rqrcode + rubocop-rails-omakase + selenium-webdriver + sentry-rails + sentry-ruby + solid_cable (>= 3.0) + solid_cache (~> 1.0) + solid_queue (~> 1.2) + sqlite3 (>= 2.0) + stimulus-rails + thruster + trilogy (~> 2.9) + turbo-rails + useragent! + vcr + web-console + web-push + webmock + webrick + yabeda + yabeda-actioncable + yabeda-activejob! + yabeda-gc + yabeda-http_requests + yabeda-prometheus-mmap + yabeda-puma-plugin + yabeda-rails + +BUNDLED WITH + 2.7.2 diff --git a/app/controllers/concerns/authentication.rb b/app/controllers/concerns/authentication.rb index f4ad6a0131..4ffca9cb02 100644 --- a/app/controllers/concerns/authentication.rb +++ b/app/controllers/concerns/authentication.rb @@ -81,7 +81,8 @@ def start_new_session_for(identity) end def set_current_session(session) - logger.struct " Authorized Identity##{session.identity.id}", authentication: { identity: { id: session.identity.id } } + # TODO: Release structured logging or look for alternative + logger.try :struct, " Authorized Identity##{session.identity.id}", authentication: { identity: { id: session.identity.id } } Current.session = session cookies.signed.permanent[:session_token] = { value: session.signed_id, httponly: true, same_site: :lax } end diff --git a/bin/kamal b/bin/kamal index cbe59b95ed..862f9036a6 100755 --- a/bin/kamal +++ b/bin/kamal @@ -22,6 +22,23 @@ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this end require "rubygems" + +require_relative "../lib/fizzy" +Fizzy.configure_bundle + require "bundler/setup" +if Fizzy.saas? + gem_path = Gem::Specification.find_by_name("fizzy-saas").gem_dir + deploy_config = File.join(gem_path, "config", "deploy.yml") + + unless ARGV.include?("-c") || ARGV.include?("--config-file") + if ARGV.empty? || ARGV.first.start_with?("-") + ARGV.unshift("-c", deploy_config) + else + ARGV.insert(1, "-c", deploy_config) + end + end +end + load Gem.bin_path("kamal", "kamal") diff --git a/bin/rails b/bin/rails index ab22dd7709..8f0b2d8b3f 100755 --- a/bin/rails +++ b/bin/rails @@ -1,10 +1,11 @@ #!/usr/bin/env ruby -require_relative "../lib/bootstrap" -if !Bootstrap.oss_config? - # default from rails/test_unit/runner.rb but adding the saas gem test files - ENV["DEFAULT_TEST"] = "{gems/fizzy-saas/,}test/**/*_test.rb" - ENV["DEFAULT_TEST_EXCLUDE"] = "{gems/fizzy-saas/,}test/{system,dummy,fixtures}/**/*_test.rb" -end -APP_PATH = File.expand_path('../config/application', __dir__) -require_relative '../config/boot' -require 'rails/commands' +APP_PATH = File.expand_path("../config/application", __dir__) + +require_relative "../lib/fizzy" +Fizzy.configure_bundle + +require_relative "../config/boot" + +require "rails/commands" + +Fizzy::Saas.append_test_paths if Fizzy.saas? && Rails.env.test? diff --git a/bin/setup b/bin/setup index 1c9c6ac61f..e47bc5a939 100755 --- a/bin/setup +++ b/bin/setup @@ -8,6 +8,14 @@ app_root="$( )" export PATH="$app_root/bin:$PATH" +if [ -e tmp/saas.txt ]; then + export SAAS=1 +fi + +if [ -n "$SAAS" ]; then + export BUNDLE_GEMFILE="Gemfile.saas" +fi + # Install gum if needed if ! command -v gum &>/dev/null; then echo @@ -71,14 +79,11 @@ step "Set up gh-signoff" bash -c "gh extension install basecamp/gh-signoff || gh bundle config set --local auto_install true step "Installing RubyGems" bundle install -if [ -e tmp/minio-dev.txt ]; then - step "Starting Docker services" bash -c "[ -d ~/Work/basecamp/docker-dev ] && git -C ~/Work/basecamp/docker-dev pull || gh repo clone basecamp/docker-dev ~/Work/basecamp/docker-dev && ~/Work/basecamp/docker-dev/setup minio" - - step "Configuring MinIO" bin/minio-setup +if [ -n "$SAAS" ]; then + saas_setup=$(bundle show fizzy-saas)/bin/setup + source "$saas_setup" fi -step "Starting mysql" bash -c "[ -d ~/Work/basecamp/docker-dev ] && git -C ~/Work/basecamp/docker-dev pull || gh repo clone basecamp/docker-dev ~/Work/basecamp/docker-dev && ~/Work/basecamp/docker-dev/setup mysql80" - if [[ $* == *--reset* ]]; then step "Resetting the database" rails db:reset else diff --git a/config/application.rb b/config/application.rb index 27177a5cb9..0399caacac 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,5 +1,7 @@ require_relative "boot" require "rails/all" +require_relative "../lib/fizzy" + Bundler.require(*Rails.groups) module Fizzy diff --git a/config/ci.rb b/config/ci.rb index be81c58c96..527d7d8cb3 100644 --- a/config/ci.rb +++ b/config/ci.rb @@ -1,5 +1,7 @@ # Run using bin/ci +require_relative "../lib/fizzy" + CI.run do step "Setup", "bin/setup --skip-server" @@ -9,8 +11,9 @@ step "Security: Importmap audit", "bin/importmap audit" step "Security: Brakeman audit", "bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error" - step "Tests: Rails: SaaS config", "bin/rails test" - step "Tests: Rails: OSS config", "OSS_CONFIG=1 bin/rails test" + step "Tests: Fizzy", "bin/rails test" + step "Tests: SaaS", "SAAS=1 bin/rails test:saas" if Fizzy.saas? + step "Tests: System", "bin/rails test:system" if success? diff --git a/config/database.mysql.yml b/config/database.mysql.yml new file mode 100644 index 0000000000..e3188b1acb --- /dev/null +++ b/config/database.mysql.yml @@ -0,0 +1,20 @@ +default: &default + adapter: trilogy + host: <%= ENV.fetch("MYSQL_HOST", "127.0.0.1") %> + port: <%= ENV.fetch("MYSQL_PORT", "3306") %> + username: <%= ENV.fetch("MYSQL_USER", "root") %> + password: <%= ENV["MYSQL_PASSWORD"] %> + pool: 50 + timeout: 5000 + +development: + <<: *default + database: fizzy_development + +test: + <<: *default + database: fizzy_test + +production: + <<: *default + database: fizzy_production diff --git a/config/database.sqlite.yml b/config/database.sqlite.yml new file mode 100644 index 0000000000..7cc7404c7f --- /dev/null +++ b/config/database.sqlite.yml @@ -0,0 +1,22 @@ +default: &default + adapter: sqlite3 + pool: 5 + timeout: 5000 + +development: + primary: + <<: *default + database: storage/development.sqlite3 + schema_dump: schema_sqlite.rb + +test: + primary: + <<: *default + database: storage/test.sqlite3 + schema_dump: schema_sqlite.rb + +production: + primary: + <<: *default + database: storage/production.sqlite3 + schema_dump: schema_sqlite.rb diff --git a/config/database.yml b/config/database.yml index 913ba7cba0..0229af3e39 100644 --- a/config/database.yml +++ b/config/database.yml @@ -1,126 +1,11 @@ <% - database_adapter = ENV.fetch("DATABASE_ADAPTER", "mysql") - use_sqlite = database_adapter == "sqlite" + require_relative "../lib/fizzy" - if ENV["MIGRATE"].present? - mysql_app_user_key = "MYSQL_ALTER_USER" - mysql_app_password_key = "MYSQL_ALTER_PASSWORD" - max_execution_time_ms = 0 # No limit + config_path = if Fizzy.saas? + gem_path = Gem::Specification.find_by_name("fizzy-saas").gem_dir + File.join(gem_path, "config", "database.yml") else - mysql_app_user_key = "MYSQL_APP_USER" - mysql_app_password_key = "MYSQL_APP_PASSWORD" - max_execution_time_ms = 5_000 + File.join(__dir__, "database.#{Fizzy.db_adapter}.yml") end - - mysql_app_user = ENV[mysql_app_user_key] - mysql_app_password = ENV[mysql_app_password_key] %> - -default: &default - <% if use_sqlite %> - adapter: sqlite3 - pool: 5 - timeout: 5000 - <% else %> - adapter: trilogy - host: <%= ENV.fetch "FIZZY_DB_HOST", "127.0.0.1" %> - port: <%= ENV.fetch "FIZZY_DB_PORT", 3306 %> - pool: 50 - timeout: 5000 - variables: - transaction_isolation: READ-COMMITTED - max_execution_time: <%= max_execution_time_ms %> - <% end %> - -development: - <% if use_sqlite %> - primary: - <<: *default - database: storage/development.sqlite3 - schema_dump: schema_sqlite.rb - <% else %> - primary: - <<: *default - database: fizzy_development - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - replica: - <<: *default - database: fizzy_development - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - replica: true - cable: - <<: *default - database: development_cable - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - migrations_paths: db/cable_migrate - cache: - <<: *default - database: development_cache - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - migrations_paths: db/cache_migrate - queue: - <<: *default - database: development_queue - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - migrations_paths: db/queue_migrate - <% end %> - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: - <% if use_sqlite %> - primary: - <<: *default - database: storage/test.sqlite3 - schema_dump: schema_sqlite.rb - <% else %> - primary: - <<: *default - database: fizzy_test - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - replica: - <<: *default - database: fizzy_test - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - replica: true - <% end %> - -production: &production - primary: - <<: *default - database: fizzy_production - host: <%= ENV["MYSQL_DATABASE_HOST"] %> - username: <%= mysql_app_user %> - password: <%= mysql_app_password %> - replica: - <<: *default - database: fizzy_production - host: <%= ENV["MYSQL_DATABASE_REPLICA_HOST"] %> - username: <%= ENV["MYSQL_READONLY_USER"] %> - password: <%= ENV["MYSQL_READONLY_PASSWORD"] %> - replica: true - cable: - <<: *default - database: fizzy_solidcable_production - host: <%= ENV["MYSQL_SOLID_CABLE_HOST"] %> - username: <%= mysql_app_user %> - password: <%= mysql_app_password %> - migrations_paths: db/cable_migrate - queue: - <<: *default - database: fizzy_solidqueue_production - host: <%= ENV["MYSQL_SOLID_QUEUE_HOST"] %> - username: <%= mysql_app_user %> - password: <%= mysql_app_password %> - migrations_paths: db/queue_migrate - cache: - <<: *default - database: fizzy_solidcache_production - host: <%= ENV["MYSQL_SOLID_CACHE_HOST"] %> - username: <%= mysql_app_user %> - password: <%= mysql_app_password %> - migrations_paths: db/cache_migrate - -beta: *production -staging: *production +<%= ERB.new(File.read(config_path)).result %> diff --git a/config/deploy.beta.yml b/config/deploy.beta.yml deleted file mode 100644 index 4c4c323f61..0000000000 --- a/config/deploy.beta.yml +++ /dev/null @@ -1,53 +0,0 @@ -servers: - web: - hosts: - - fizzy-beta-app-01.sc-chi-int.37signals.com: sc_chi - - fizzy-beta-app-101.df-iad-int.37signals.com: df_iad - labels: - otel_scrape_enabled: true - -# we don't run the jobs role in beta -allow_empty_roles: true - -proxy: - ssl: false - -ssh: - user: app - -env: - clear: - RAILS_ENV: beta - MYSQL_DATABASE_HOST: fizzy-mysql-primary - MYSQL_DATABASE_REPLICA_HOST: fizzy-mysql-replica - MYSQL_SOLID_CABLE_HOST: fizzy-mysql-primary - MYSQL_SOLID_QUEUE_HOST: fizzy-mysql-primary - MYSQL_SOLID_CACHE_HOST: fizzy-beta-solidcache-db-101 - secret: - - RAILS_MASTER_KEY - - MYSQL_ALTER_PASSWORD - - MYSQL_ALTER_USER - - MYSQL_APP_PASSWORD - - MYSQL_APP_USER - - MYSQL_READONLY_PASSWORD - - MYSQL_READONLY_USER - tags: - sc_chi: {} - df_iad: - PRIMARY_DATACENTER: true - df_ams: {} - -accessories: - load-balancer: - image: basecamp/kamal-proxy:lb - host: fizzy-beta-lb-01.sc-chi-int.37signals.com - labels: - otel_role: load-balancer - otel_service: fizzy-load-balancer - otel_scrape_enabled: true - options: - publish: - - 80:80 - - 443:443 - volumes: - - load-balancer:/home/kamal-proxy/.config/kamal-proxy diff --git a/config/deploy.production.yml b/config/deploy.production.yml deleted file mode 100644 index 285dab819e..0000000000 --- a/config/deploy.production.yml +++ /dev/null @@ -1,69 +0,0 @@ -servers: - web: - hosts: - - fizzy-app-01.sc-chi-int.37signals.com: sc_chi - - fizzy-app-02.sc-chi-int.37signals.com: sc_chi - - fizzy-app-101.df-iad-int.37signals.com: df_iad - - fizzy-app-102.df-iad-int.37signals.com: df_iad - - fizzy-app-401.df-ams-int.37signals.com: df_ams - - fizzy-app-402.df-ams-int.37signals.com: df_ams - labels: - otel_scrape_enabled: true - jobs: - hosts: - - fizzy-jobs-101.df-iad-int.37signals.com: df_iad - - fizzy-jobs-102.df-iad-int.37signals.com: df_iad - labels: - otel_scrape_enabled: true - -proxy: - ssl: false - -ssh: - user: app - -env: - clear: - RAILS_ENV: production - MYSQL_DATABASE_HOST: fizzy-mysql-primary - MYSQL_DATABASE_REPLICA_HOST: fizzy-mysql-replica - MYSQL_SOLID_CABLE_HOST: fizzy-mysql-primary - MYSQL_SOLID_QUEUE_HOST: fizzy-mysql-primary - secret: - - RAILS_MASTER_KEY - - MYSQL_ALTER_PASSWORD - - MYSQL_ALTER_USER - - MYSQL_APP_PASSWORD - - MYSQL_APP_USER - - MYSQL_READONLY_PASSWORD - - MYSQL_READONLY_USER - tags: - sc_chi: - MYSQL_SOLID_CACHE_HOST: fizzy-solidcache-db-01.sc-chi-int.37signals.com - df_iad: - MYSQL_SOLID_CACHE_HOST: fizzy-solidcache-db-101.df-iad-int.37signals.com - PRIMARY_DATACENTER: true - df_ams: - MYSQL_SOLID_CACHE_HOST: fizzy-solidcache-db-401.df-ams-int.37signals.com - - -accessories: - load-balancer: - image: basecamp/kamal-proxy:lb - hosts: - - fizzy-lb-101.df-iad-int.37signals.com - - fizzy-lb-01.sc-chi-int.37signals.com - - fizzy-lb-401.df-ams-int.37signals.com - labels: - otel_role: load-balancer - otel_service: fizzy-load-balancer - otel_scrape_enabled: true - options: - publish: - - 80:80 - - 443:443 - # NFS mount for certificates - # See https://3.basecamp.com/2914079/buckets/37331921/todos/9180260061 - mount: type=volume,src=certificates,dst=/certificates,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/fizzy-production-certificates,"volume-opt=o=addr=purestorage.sc-chi-int.37signals.com,nfsvers=3,rw,noatime,nconnect=8,soft,timeo=30,retrans=2" - volumes: - - load-balancer:/home/kamal-proxy/.config/kamal-proxy diff --git a/config/deploy.staging.yml b/config/deploy.staging.yml deleted file mode 100644 index 3163c7a7bf..0000000000 --- a/config/deploy.staging.yml +++ /dev/null @@ -1,69 +0,0 @@ -servers: - web: - hosts: - - fizzy-staging-app-101.df-iad-int.37signals.com: df_iad - - fizzy-staging-app-102.df-iad-int.37signals.com: df_iad - - fizzy-staging-app-01.sc-chi-int.37signals.com: sc_chi - - fizzy-staging-app-02.sc-chi-int.37signals.com: sc_chi - - fizzy-staging-app-401.df-ams-int.37signals.com: df_ams - - fizzy-staging-app-402.df-ams-int.37signals.com: df_ams - labels: - otel_scrape_enabled: true - jobs: - hosts: - - fizzy-staging-jobs-101.df-iad-int.37signals.com: df_iad - - fizzy-staging-jobs-102.df-iad-int.37signals.com: df_iad - labels: - otel_scrape_enabled: true - -proxy: - ssl: false - -ssh: - user: app - -env: - clear: - RAILS_ENV: staging - MYSQL_DATABASE_HOST: fizzy-staging-mysql-primary - MYSQL_DATABASE_REPLICA_HOST: fizzy-staging-mysql-replica - MYSQL_SOLID_CABLE_HOST: fizzy-staging-mysql-primary - MYSQL_SOLID_QUEUE_HOST: fizzy-staging-mysql-primary - secret: - - RAILS_MASTER_KEY - - MYSQL_ALTER_PASSWORD - - MYSQL_ALTER_USER - - MYSQL_APP_PASSWORD - - MYSQL_APP_USER - - MYSQL_READONLY_PASSWORD - - MYSQL_READONLY_USER - tags: - sc_chi: - MYSQL_SOLID_CACHE_HOST: fizzy-staging-solidcache-db-01.sc-chi-int.37signals.com - df_iad: - MYSQL_SOLID_CACHE_HOST: fizzy-staging-solidcache-db-101.df-iad-int.37signals.com - PRIMARY_DATACENTER: true - df_ams: - MYSQL_SOLID_CACHE_HOST: fizzy-staging-solidcache-db-401.df-ams-int.37signals.com - - -accessories: - load-balancer: - image: basecamp/kamal-proxy:lb - hosts: - - fizzy-staging-lb-01.sc-chi-int.37signals.com - - fizzy-staging-lb-101.df-iad-int.37signals.com - - fizzy-staging-lb-401.df-ams-int.37signals.com - labels: - otel_role: load-balancer - otel_service: fizzy-load-balancer - otel_scrape_enabled: true - options: - publish: - - 80:80 - - 443:443 - # NFS mount for certificates - # See https://3.basecamp.com/2914079/buckets/37331921/todos/9180260061 - mount: type=volume,src=certificates,dst=/certificates,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/fizzy-staging-certificates,"volume-opt=o=addr=purestorage.sc-chi-int.37signals.com,nfsvers=3,rw,noatime,nconnect=8,soft,timeo=30,retrans=2" - volumes: - - load-balancer:/home/kamal-proxy/.config/kamal-proxy diff --git a/config/deploy.yml b/config/deploy.yml index b565e5a504..be1e3c21e6 100644 --- a/config/deploy.yml +++ b/config/deploy.yml @@ -1,34 +1,120 @@ +# Name of your application. Used to uniquely configure containers. service: fizzy -image: basecamp/fizzy -asset_path: /rails/public/assets -servers: - jobs: - cmd: bin/jobs +# Name of the container image (use your-user/app-name on external registries). +image: fizzy -volumes: - - fizzy:/rails/storage +# Deploy to these servers. +servers: + web: + - 192.168.0.1 + # job: + # hosts: + # - 192.168.0.1 + # cmd: bin/jobs -proxy: - ssl: true +# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server. +# If used with Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption. +# +# Using an SSL proxy like this requires turning on config.assume_ssl and config.force_ssl in production.rb! +# +# Don't use this when deploying to multiple web servers (then you have to terminate SSL at your load balancer). +# +# proxy: +# ssl: true +# host: app.example.com +# Where you keep your container images. registry: - server: registry.37signals.com - username: robot$harbor-bot - password: - - BASECAMP_REGISTRY_PASSWORD + # Alternatives: hub.docker.com / registry.digitalocean.com / ghcr.io / ... + server: localhost:5555 -builder: - arch: amd64 - secrets: - - GITHUB_TOKEN - remote: ssh://app@docker-builder-102 - local: <%= ENV.fetch("KAMAL_BUILDER_LOCAL", "true") %> + # Needed for authenticated registries. + # username: your-user + # Always use an access token rather than real password when possible. + # password: + # - KAMAL_REGISTRY_PASSWORD + +# Inject ENV variables into containers (secrets come from .kamal/secrets). env: secret: - RAILS_MASTER_KEY + clear: + # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs. + # When you start using multiple servers, you should split out job processing to a dedicated machine. + SOLID_QUEUE_IN_PUMA: true + + # Set number of processes dedicated to Solid Queue (default: 1) + # JOB_CONCURRENCY: 3 + + # Set number of cores available to the application on each server (default: 1). + # WEB_CONCURRENCY: 2 + + # Match this to any external database server to configure Active Record correctly + # Use fizzy-db for a db accessory server on same machine via local kamal docker network. + # DB_HOST: 192.168.0.2 + + # Log everything from Rails + # RAILS_LOG_LEVEL: debug +# Aliases are triggered with "bin/kamal ". You can overwrite arguments on invocation: +# "bin/kamal logs -r job" will tail logs from the first server in the job section. aliases: - console: app exec -i --reuse "bin/rails console" - ssh: app exec -i --reuse /bin/bash + console: app exec --interactive --reuse "bin/rails console" + shell: app exec --interactive --reuse "bash" + logs: app logs -f + dbc: app exec --interactive --reuse "bin/rails dbconsole --include-password" + +# Use a persistent storage volume for sqlite database files and local Active Storage files. +# Recommended to change this to a mounted volume path that is backed up off server. +volumes: + - "fizzy_storage:/rails/storage" + +# Bridge fingerprinted assets, like JS and CSS, between versions to avoid +# hitting 404 on in-flight requests. Combines all files from new and old +# version inside the asset_path. +asset_path: /rails/public/assets + + +# Configure the image builder. +builder: + arch: amd64 + + # # Build image via remote server (useful for faster amd64 builds on arm64 computers) + # remote: ssh://docker@docker-builder-server + # + # # Pass arguments and secrets to the Docker build process + # args: + # RUBY_VERSION: ruby-3.4.7 + # secrets: + # - GITHUB_TOKEN + # - RAILS_MASTER_KEY + +# Use a different ssh user than root +# ssh: +# user: app + +# Use accessory services (secrets come from .kamal/secrets). +# accessories: +# db: +# image: mysql:8.0 +# host: 192.168.0.2 +# # Change to 3306 to expose port to the world instead of just local network. +# port: "127.0.0.1:3306:3306" +# env: +# clear: +# MYSQL_ROOT_HOST: '%' +# secret: +# - MYSQL_ROOT_PASSWORD +# files: +# - config/mysql/production.cnf:/etc/mysql/my.cnf +# - db/production.sql:/docker-entrypoint-initdb.d/setup.sql +# directories: +# - data:/var/lib/mysql +# redis: +# image: valkey/valkey:8 +# host: 192.168.0.2 +# port: 6379 +# directories: +# - data:/data diff --git a/config/initializers/authentication.rb b/config/initializers/authentication.rb deleted file mode 100644 index 5556a9661e..0000000000 --- a/config/initializers/authentication.rb +++ /dev/null @@ -1,2 +0,0 @@ -require "bootstrap" -Rails.application.config.x.oss_config = Bootstrap.oss_config? diff --git a/config/routes.rb b/config/routes.rb index 73acfa75ed..3afb093a77 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -224,10 +224,6 @@ get "manifest" => "rails/pwa#manifest", as: :pwa_manifest get "service-worker" => "pwa#service_worker" - unless Rails.application.config.x.oss_config - mount Fizzy::Saas::Engine, at: "/", as: "saas" - end - namespace :admin do mount MissionControl::Jobs::Engine, at: "/jobs" get "stats", to: "stats#show" diff --git a/gems/fizzy-saas/.github/dependabot.yml b/gems/fizzy-saas/.github/dependabot.yml deleted file mode 100644 index 83610cfa4c..0000000000 --- a/gems/fizzy-saas/.github/dependabot.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: 2 -updates: -- package-ecosystem: bundler - directory: "/" - schedule: - interval: weekly - open-pull-requests-limit: 10 -- package-ecosystem: github-actions - directory: "/" - schedule: - interval: weekly - open-pull-requests-limit: 10 diff --git a/gems/fizzy-saas/.github/workflows/ci.yml b/gems/fizzy-saas/.github/workflows/ci.yml deleted file mode 100644 index ef5e97c73e..0000000000 --- a/gems/fizzy-saas/.github/workflows/ci.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: CI - -on: - pull_request: - push: - branches: [ main ] - -jobs: - lint: - runs-on: ubuntu-latest - env: - RUBY_VERSION: ruby-3.4.5 - RUBOCOP_CACHE_ROOT: tmp/rubocop - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ env.RUBY_VERSION }} - bundler-cache: true - - - name: Prepare RuboCop cache - uses: actions/cache@v4 - env: - DEPENDENCIES_HASH: ${{ hashFiles('**/.rubocop.yml', '**/.rubocop_todo.yml', 'Gemfile.lock') }} - with: - path: ${{ env.RUBOCOP_CACHE_ROOT }} - key: rubocop-${{ runner.os }}-${{ env.RUBY_VERSION }}-${{ env.DEPENDENCIES_HASH }}-${{ github.ref_name == github.event.repository.default_branch && github.run_id || 'default' }} - restore-keys: | - rubocop-${{ runner.os }}-${{ env.RUBY_VERSION }}-${{ env.DEPENDENCIES_HASH }}- - - - name: Lint code for consistent style - run: bin/rubocop -f github - - test: - runs-on: ubuntu-latest - - # services: - # redis: - # image: valkey/valkey:8 - # ports: - # - 6379:6379 - # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ruby-3.4.5 - bundler-cache: true - - - name: Run tests - env: - RAILS_ENV: test - # RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} - # REDIS_URL: redis://localhost:6379/0 - run: bin/rails db:test:prepare test - - - name: Keep screenshots from failed system tests - uses: actions/upload-artifact@v4 - if: failure() - with: - name: screenshots - path: ${{ github.workspace }}/tmp/screenshots - if-no-files-found: ignore diff --git a/gems/fizzy-saas/.gitignore b/gems/fizzy-saas/.gitignore deleted file mode 100644 index a3ee5aad36..0000000000 --- a/gems/fizzy-saas/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -/.bundle/ -/doc/ -/log/*.log -/pkg/ -/tmp/ -/test/dummy/db/*.sqlite3 -/test/dummy/db/*.sqlite3-* -/test/dummy/log/*.log -/test/dummy/storage/ -/test/dummy/tmp/ diff --git a/gems/fizzy-saas/.rubocop.yml b/gems/fizzy-saas/.rubocop.yml deleted file mode 100644 index f9d86d4a54..0000000000 --- a/gems/fizzy-saas/.rubocop.yml +++ /dev/null @@ -1,8 +0,0 @@ -# Omakase Ruby styling for Rails -inherit_gem: { rubocop-rails-omakase: rubocop.yml } - -# Overwrite or add rules to create your own house style -# -# # Use `[a, [b, c]]` not `[ a, [ b, c ] ]` -# Layout/SpaceInsideArrayLiteralBrackets: -# Enabled: false diff --git a/gems/fizzy-saas/Gemfile b/gems/fizzy-saas/Gemfile deleted file mode 100644 index 396a3192e0..0000000000 --- a/gems/fizzy-saas/Gemfile +++ /dev/null @@ -1,6 +0,0 @@ -source "https://rubygems.org" -git_source(:bc) { |repo| "https://github.com/basecamp/#{repo}" } - -# 37id and Queenbee integration -gem "queenbee", bc: "queenbee-plugin", ref: "eb01c697de1ad028afc65cc7d9b5345a7a8e849f" -gem "activeresource", require: "active_resource" # needed by queenbee diff --git a/gems/fizzy-saas/Gemfile.lock b/gems/fizzy-saas/Gemfile.lock deleted file mode 100644 index 5d83987cde..0000000000 --- a/gems/fizzy-saas/Gemfile.lock +++ /dev/null @@ -1,69 +0,0 @@ -GIT - remote: https://github.com/basecamp/queenbee-plugin - revision: eb01c697de1ad028afc65cc7d9b5345a7a8e849f - ref: eb01c697de1ad028afc65cc7d9b5345a7a8e849f - specs: - queenbee (3.2.0) - activeresource - builder - rexml - -GEM - remote: https://rubygems.org/ - specs: - activemodel (8.0.2.1) - activesupport (= 8.0.2.1) - activemodel-serializers-xml (1.0.3) - activemodel (>= 5.0.0.a) - activesupport (>= 5.0.0.a) - builder (~> 3.1) - activeresource (6.1.4) - activemodel (>= 6.0) - activemodel-serializers-xml (~> 1.0) - activesupport (>= 6.0) - activesupport (8.0.2.1) - base64 - benchmark (>= 0.3) - bigdecimal - concurrent-ruby (~> 1.0, >= 1.3.1) - connection_pool (>= 2.2.5) - drb - i18n (>= 1.6, < 2) - logger (>= 1.4.2) - minitest (>= 5.1) - securerandom (>= 0.3) - tzinfo (~> 2.0, >= 2.0.5) - uri (>= 0.13.1) - base64 (0.3.0) - benchmark (0.4.1) - bigdecimal (3.2.3) - builder (3.3.0) - concurrent-ruby (1.3.5) - connection_pool (2.5.4) - drb (2.2.3) - i18n (1.14.7) - concurrent-ruby (~> 1.0) - logger (1.7.0) - minitest (5.25.5) - rexml (3.4.4) - securerandom (0.4.1) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - uri (1.0.3) - -PLATFORMS - aarch64-linux-gnu - aarch64-linux-musl - arm-linux-gnu - arm-linux-musl - arm64-darwin - x86_64-darwin - x86_64-linux-gnu - x86_64-linux-musl - -DEPENDENCIES - activeresource - queenbee! - -BUNDLED WITH - 2.7.0 diff --git a/gems/fizzy-saas/README.md b/gems/fizzy-saas/README.md deleted file mode 100644 index ecaa3ede6f..0000000000 --- a/gems/fizzy-saas/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Fizzy::Saas -Short description and motivation. - -## Usage -How to use my plugin. - -## Installation -Add this line to your application's Gemfile: - -```ruby -gem "fizzy-saas" -``` - -And then execute: -```bash -$ bundle -``` - -Or install it yourself as: -```bash -$ gem install fizzy-saas -``` - -## Contributing -Contribution directions go here. - -## License -The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/gems/fizzy-saas/Rakefile b/gems/fizzy-saas/Rakefile deleted file mode 100644 index e7793b5c12..0000000000 --- a/gems/fizzy-saas/Rakefile +++ /dev/null @@ -1,8 +0,0 @@ -require "bundler/setup" - -APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__) -load "rails/tasks/engine.rake" - -load "rails/tasks/statistics.rake" - -require "bundler/gem_tasks" diff --git a/gems/fizzy-saas/app/assets/images/fizzy/saas/.keep b/gems/fizzy-saas/app/assets/images/fizzy/saas/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gems/fizzy-saas/app/assets/stylesheets/fizzy/saas/application.css b/gems/fizzy-saas/app/assets/stylesheets/fizzy/saas/application.css deleted file mode 100644 index 0ebd7fe829..0000000000 --- a/gems/fizzy-saas/app/assets/stylesheets/fizzy/saas/application.css +++ /dev/null @@ -1,15 +0,0 @@ -/* - * This is a manifest file that'll be compiled into application.css, which will include all the files - * listed below. - * - * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, - * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. - * - * You're free to add application-wide styles to this file and they'll appear at the bottom of the - * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS - * files in this directory. Styles in this file should be added after the last require_* statement. - * It is generally better to create a new file per style scope. - * - *= require_tree . - *= require_self - */ diff --git a/gems/fizzy-saas/app/controllers/concerns/.keep b/gems/fizzy-saas/app/controllers/concerns/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gems/fizzy-saas/app/controllers/signup/completions_controller.rb b/gems/fizzy-saas/app/controllers/signup/completions_controller.rb deleted file mode 100644 index d7f09c0865..0000000000 --- a/gems/fizzy-saas/app/controllers/signup/completions_controller.rb +++ /dev/null @@ -1,24 +0,0 @@ -class Signup::CompletionsController < ApplicationController - layout "public" - - disallow_account_scope - - def new - @signup = Signup.new(identity: Current.identity) - end - - def create - @signup = Signup.new(signup_params) - - if @signup.complete - redirect_to landing_url(script_name: @signup.account.slug) - else - render :new, status: :unprocessable_entity - end - end - - private - def signup_params - params.expect(signup: %i[ full_name ]).with_defaults(identity: Current.identity) - end -end diff --git a/gems/fizzy-saas/app/models/concerns/.keep b/gems/fizzy-saas/app/models/concerns/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gems/fizzy-saas/app/models/signup.rb b/gems/fizzy-saas/app/models/signup.rb deleted file mode 100644 index ca76f5b081..0000000000 --- a/gems/fizzy-saas/app/models/signup.rb +++ /dev/null @@ -1,127 +0,0 @@ -class Signup - include ActiveModel::Model - include ActiveModel::Attributes - include ActiveModel::Validations - - attr_accessor :full_name, :email_address, :identity - attr_reader :queenbee_account, :account, :user - - with_options on: :completion do - validates_presence_of :full_name, :identity - end - - def initialize(...) - @full_name = nil - @email_address = nil - @account = nil - @user = nil - @queenbee_account = nil - @identity = nil - - super - - @email_address = @identity.email_address if @identity - end - - def create_identity - @identity = Identity.find_or_create_by!(email_address: email_address) - @identity.send_magic_link - end - - def complete - if valid?(:completion) - begin - create_queenbee_account - create_account - - true - rescue => error - destroy_account - destroy_queenbee_account - - errors.add(:base, "Something went wrong, and we couldn't create your account. Please give it another try.") - Rails.error.report(error, severity: :error) - Rails.logger.error error - Rails.logger.error error.backtrace.join("\n") - - false - end - else - false - end - end - - private - def create_queenbee_account - @account_name = AccountNameGenerator.new(identity: identity, name: full_name).generate - @queenbee_account = Queenbee::Remote::Account.create!(queenbee_account_attributes) - @tenant = queenbee_account.id.to_s - end - - def destroy_queenbee_account - @queenbee_account&.cancel - @queenbee_account = nil - end - - def create_account - @account = Account.create_with_admin_user( - account: { - external_account_id: @tenant, - name: @account_name - }, - owner: { - name: full_name, - identity: identity - } - ) - @user = @account.users.find_by!(role: :admin) - @account.setup_customer_template - end - - def destroy_account - @account&.destroy! - - @user = nil - @account = nil - @tenant = nil - end - - def queenbee_account_attributes - {}.tap do |attributes| - attributes[:product_name] = "fizzy" - attributes[:name] = @account_name - attributes[:owner_name] = full_name - attributes[:owner_email] = email_address - - attributes[:trial] = true - attributes[:subscription] = subscription_attributes - attributes[:remote_request] = request_attributes - - # # TODO: Terms of Service - # attributes[:terms_of_service] = true - - # We've confirmed the email - attributes[:auto_allow] = true - - # Tell Queenbee to skip the request to create a local account. We've created it ourselves. - attributes[:skip_remote] = true - end - end - - def subscription_attributes - subscription = FreeV1Subscription - - {}.tap do |attributes| - attributes[:name] = subscription.to_param - attributes[:price] = subscription.price - end - end - - def request_attributes - {}.tap do |attributes| - attributes[:remote_address] = Current.ip_address - attributes[:user_agent] = Current.user_agent - attributes[:referrer] = Current.referrer - end - end -end diff --git a/gems/fizzy-saas/app/models/signup/account_name_generator.rb b/gems/fizzy-saas/app/models/signup/account_name_generator.rb deleted file mode 100644 index a6844d3a3a..0000000000 --- a/gems/fizzy-saas/app/models/signup/account_name_generator.rb +++ /dev/null @@ -1,53 +0,0 @@ -class Signup::AccountNameGenerator - SUFFIX = "Fizzy".freeze - - attr_reader :identity, :name - - def initialize(identity:, name:) - @identity = identity - @name = name - end - - def generate - next_index = current_index + 1 - - if next_index == 1 - "#{prefix} #{SUFFIX}" - else - "#{prefix} #{next_index.ordinalize} #{SUFFIX}" - end - end - - private - def current_index - existing_indices.max || 0 - end - - def existing_indices - Current.without_account do - identity.accounts.filter_map do |account| - if account.name.match?(first_account_name_regex) - 1 - elsif match = account.name.match(nth_account_name_regex) - match[1].to_i - end - end - end - end - - def first_account_name_regex - @first_account_name_regex ||= /\A#{prefix}\s+#{SUFFIX}\Z/i - end - - def nth_account_name_regex - @nth_account_name_regex ||= /\A#{prefix}\s+(1st|2nd|3rd|\d+th)\s+#{SUFFIX}/i - end - - def prefix - @prefix ||= "#{first_name}'s" - end - - def first_name - name.strip.split(" ", 2).first - end -end diff --git a/gems/fizzy-saas/app/models/subscription.rb b/gems/fizzy-saas/app/models/subscription.rb deleted file mode 100644 index 5efb1a7fac..0000000000 --- a/gems/fizzy-saas/app/models/subscription.rb +++ /dev/null @@ -1,13 +0,0 @@ -class Subscription < Queenbee::Subscription - SHORT_NAMES = %w[ FreeV1 ] - - def self.short_name - name.demodulize - end - - class FreeV1 < Subscription - property :proper_name, "Free Subscription" - property :price, 0 - property :frequency, "yearly" - end -end diff --git a/gems/fizzy-saas/app/views/layouts/fizzy/saas/application.html.erb b/gems/fizzy-saas/app/views/layouts/fizzy/saas/application.html.erb deleted file mode 100644 index 144b378387..0000000000 --- a/gems/fizzy-saas/app/views/layouts/fizzy/saas/application.html.erb +++ /dev/null @@ -1,17 +0,0 @@ - - - - Fizzy saas - <%= csrf_meta_tags %> - <%= csp_meta_tag %> - - <%= yield :head %> - - <%= stylesheet_link_tag "fizzy/saas/application", media: "all" %> - - - -<%= yield %> - - - diff --git a/gems/fizzy-saas/app/views/signup/completions/new.html.erb b/gems/fizzy-saas/app/views/signup/completions/new.html.erb deleted file mode 100644 index aee8f45869..0000000000 --- a/gems/fizzy-saas/app/views/signup/completions/new.html.erb +++ /dev/null @@ -1,30 +0,0 @@ -<% @page_title = "Complete your sign-up" %> - -
"> -

<%= @page_title %>

- - <%= form_with model: @signup, url: saas.signup_completion_path, scope: "signup", class: "flex flex-column gap", data: { controller: "form" } do |form| %> - <%= form.text_field :full_name, class: "input txt-large", autocomplete: "name", placeholder: "Enter your full name…", autofocus: true, required: true %> - -

You’re one step away. Just enter your name to get your own Fizzy account.

- - <% if @signup.errors.any? %> -
-
    - <% @signup.errors.full_messages.each do |message| %> -
  • <%= message %>
  • - <% end %> -
-
- <% end %> - - - <% end %> -
- -<% content_for :footer do %> - <%= render "sessions/footer" %> -<% end %> diff --git a/gems/fizzy-saas/app/views/signup/new.html.erb b/gems/fizzy-saas/app/views/signup/new.html.erb deleted file mode 100644 index 93166dd2fe..0000000000 --- a/gems/fizzy-saas/app/views/signup/new.html.erb +++ /dev/null @@ -1,28 +0,0 @@ -<% @page_title = "Sign up for Fizzy" %> - -
"> -

Sign up

- - <%= form_with model: @signup, url: saas.signup_path, scope: "signup", class: "flex flex-column gap", data: { turbo: false, controller: "form" } do |form| %> - <%= form.email_field :email_address, class: "input", autocomplete: "username", placeholder: "Email address", required: true %> - - <% if @signup.errors.any? %> -
-
    - <% @signup.errors.full_messages.each do |message| %> -
  • <%= message %>
  • - <% end %> -
-
- <% end %> - - - <% end %> -
- -<% content_for :footer do %> - <%= render "sessions/footer" %> -<% end %> diff --git a/gems/fizzy-saas/bin/rails b/gems/fizzy-saas/bin/rails deleted file mode 100755 index 42a0e5bce3..0000000000 --- a/gems/fizzy-saas/bin/rails +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env ruby -# This command will automatically be run when you run "rails" with Rails gems -# installed from the root of your application. - -ENGINE_ROOT = File.expand_path("..", __dir__) -ENGINE_PATH = File.expand_path("../lib/fizzy/saas/engine", __dir__) -APP_PATH = File.expand_path("../test/dummy/config/application", __dir__) - -# Set up gems listed in the Gemfile. -ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) -require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) - -require "rails/all" -require "rails/engine/commands" diff --git a/gems/fizzy-saas/bin/rubocop b/gems/fizzy-saas/bin/rubocop deleted file mode 100755 index 40330c0ff1..0000000000 --- a/gems/fizzy-saas/bin/rubocop +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env ruby -require "rubygems" -require "bundler/setup" - -# explicit rubocop config increases performance slightly while avoiding config confusion. -ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) - -load Gem.bin_path("rubocop", "rubocop") diff --git a/gems/fizzy-saas/config/routes.rb b/gems/fizzy-saas/config/routes.rb deleted file mode 100644 index 2b9639edb2..0000000000 --- a/gems/fizzy-saas/config/routes.rb +++ /dev/null @@ -1,9 +0,0 @@ -Fizzy::Saas::Engine.routes.draw do - get "/signup/new", to: redirect("/session/new") - - namespace :signup do - resource :completion, only: %i[ new create ] - end - - Queenbee.routes(self) -end diff --git a/gems/fizzy-saas/fizzy-saas.gemspec b/gems/fizzy-saas/fizzy-saas.gemspec deleted file mode 100644 index f1b28de00a..0000000000 --- a/gems/fizzy-saas/fizzy-saas.gemspec +++ /dev/null @@ -1,27 +0,0 @@ -require_relative "lib/fizzy/saas/version" - -Gem::Specification.new do |spec| - spec.name = "fizzy-saas" - spec.version = Fizzy::Saas::VERSION - spec.authors = [ "Mike Dalessio" ] - spec.email = [ "mike@37signals.com" ] - spec.homepage = "TODO" - spec.summary = "TODO: Summary of Fizzy::Saas." - spec.description = "TODO: Description of Fizzy::Saas." - - # Prevent pushing this gem to RubyGems.org. To allow pushes either set the "allowed_push_host" - # to allow pushing to a single host or delete this section to allow pushing to any host. - spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" - - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here." - spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." - - spec.files = Dir.chdir(File.expand_path(__dir__)) do - Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] - end - - spec.add_dependency "rails", ">= 8.1.0.beta1" - spec.add_dependency "queenbee" - spec.add_dependency "rails_structured_logging" -end diff --git a/gems/fizzy-saas/lib/fizzy/saas.rb b/gems/fizzy-saas/lib/fizzy/saas.rb deleted file mode 100644 index dd0d354926..0000000000 --- a/gems/fizzy-saas/lib/fizzy/saas.rb +++ /dev/null @@ -1,7 +0,0 @@ -require "fizzy/saas/version" -require "fizzy/saas/engine" - -module Fizzy - module Saas - end -end diff --git a/gems/fizzy-saas/lib/fizzy/saas/engine.rb b/gems/fizzy-saas/lib/fizzy/saas/engine.rb deleted file mode 100644 index 14ce037590..0000000000 --- a/gems/fizzy-saas/lib/fizzy/saas/engine.rb +++ /dev/null @@ -1,31 +0,0 @@ -require_relative "metrics" -require_relative "transaction_pinning" - -module Fizzy - module Saas - class Engine < ::Rails::Engine - # moved from config/initializers/queenbee.rb - Queenbee.host_app = Fizzy - - initializer "fizzy_saas.transaction_pinning" do |app| - if ActiveRecord::Base.replica_configured? - app.config.middleware.insert_after( - ActiveRecord::Middleware::DatabaseSelector, - TransactionPinning::Middleware - ) - end - end - - config.to_prepare do - Queenbee::Subscription.short_names = Subscription::SHORT_NAMES - Queenbee::ApiToken.token = Rails.application.credentials.dig(:queenbee_api_token) - - Subscription::SHORT_NAMES.each do |short_name| - const_name = "#{short_name}Subscription" - ::Object.send(:remove_const, const_name) if ::Object.const_defined?(const_name) - ::Object.const_set const_name, Subscription.const_get(short_name, false) - end - end - end - end -end diff --git a/gems/fizzy-saas/lib/fizzy/saas/metrics.rb b/gems/fizzy-saas/lib/fizzy/saas/metrics.rb deleted file mode 100644 index 80a2bc194b..0000000000 --- a/gems/fizzy-saas/lib/fizzy/saas/metrics.rb +++ /dev/null @@ -1,13 +0,0 @@ -Yabeda.configure do - SHORT_HISTOGRAM_BUCKETS = [ 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5 ] - - group :fizzy do - counter :replica_stale, - comment: "Number of requests served from a stale replica" - - histogram :replica_wait, - unit: :seconds, - comment: "Time spent waiting for replica to catch up with transaction", - buckets: SHORT_HISTOGRAM_BUCKETS - end -end diff --git a/gems/fizzy-saas/lib/fizzy/saas/transaction_pinning.rb b/gems/fizzy-saas/lib/fizzy/saas/transaction_pinning.rb deleted file mode 100644 index ed3cf2060d..0000000000 --- a/gems/fizzy-saas/lib/fizzy/saas/transaction_pinning.rb +++ /dev/null @@ -1,65 +0,0 @@ -module TransactionPinning - class Middleware - SESSION_KEY = :last_txn - DEFAULT_MAX_WAIT = 0.25 - - def initialize(app) - @app = app - @timeout = Rails.application.config.x.transaction_pinning&.timeout&.to_f || DEFAULT_MAX_WAIT - end - - def call(env) - request = ActionDispatch::Request.new(env) - replica_metrics = {} - - if ApplicationRecord.current_role == :reading - wait_for_replica_catchup(request, replica_metrics) - end - - status, headers, body = @app.call(env) - headers.merge!(replica_metrics.transform_values(&:to_s)) - - if ApplicationRecord.current_role == :writing - capture_transaction_id(request) - end - - [ status, headers, body ] - end - - private - def wait_for_replica_catchup(request, replica_metrics) - if last_txn = request.session[SESSION_KEY].presence - has_transaction = tracking_replica_wait_time(replica_metrics) do - replica_has_transaction(last_txn) - end - - unless has_transaction - Yabeda.fizzy.replica_stale.increment - replica_metrics["X-Replica-Stale"] = true - end - end - end - - def capture_transaction_id(request) - request.session[SESSION_KEY] = ApplicationRecord.connection.show_variable("global.gtid_executed") - end - - def replica_has_transaction(txn) - sql = ApplicationRecord.sanitize_sql_array([ "SELECT WAIT_FOR_EXECUTED_GTID_SET(?, ?)", txn, @timeout ]) - ApplicationRecord.connection.select_value(sql) == 0 - rescue => e - Sentry.capture_exception(e, extra: { gtid: txn }) - true # Treat as if we're up to date, since we don't know - end - - def tracking_replica_wait_time(replica_metrics) - started_at = Time.current - - Yabeda.fizzy.replica_wait.measure do - yield - end.tap do - replica_metrics["X-Replica-Wait"] = Time.current - started_at - end - end - end -end diff --git a/gems/fizzy-saas/lib/fizzy/saas/version.rb b/gems/fizzy-saas/lib/fizzy/saas/version.rb deleted file mode 100644 index 7a95d2d052..0000000000 --- a/gems/fizzy-saas/lib/fizzy/saas/version.rb +++ /dev/null @@ -1,5 +0,0 @@ -module Fizzy - module Saas - VERSION = "0.1.0" - end -end diff --git a/gems/fizzy-saas/lib/tasks/fizzy/saas_tasks.rake b/gems/fizzy-saas/lib/tasks/fizzy/saas_tasks.rake deleted file mode 100644 index 8fe948d94a..0000000000 --- a/gems/fizzy-saas/lib/tasks/fizzy/saas_tasks.rake +++ /dev/null @@ -1,4 +0,0 @@ -# desc "Explaining what the task does" -# task :fizzy_saas do -# # Task goes here -# end diff --git a/gems/fizzy-saas/test/controllers/.keep b/gems/fizzy-saas/test/controllers/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gems/fizzy-saas/test/controllers/signups/completions_controller_test.rb b/gems/fizzy-saas/test/controllers/signups/completions_controller_test.rb deleted file mode 100644 index 49628a188e..0000000000 --- a/gems/fizzy-saas/test/controllers/signups/completions_controller_test.rb +++ /dev/null @@ -1,43 +0,0 @@ -require "test_helper" - -class Signup::CompletionsControllerTest < ActionDispatch::IntegrationTest - setup do - @signup = Signup.new(email_address: "newuser@example.com", full_name: "New User") - - @signup.create_identity || raise("Failed to create identity") - - sign_in_as @signup.identity - end - - test "new" do - untenanted do - get saas.new_signup_completion_path - end - - assert_response :success - end - - test "create" do - untenanted do - post saas.signup_completion_path, params: { - signup: { - full_name: @signup.full_name - } - } - end - - assert_response :redirect, "Valid params should redirect" - end - - test "create with invalid params" do - untenanted do - post saas.signup_completion_path, params: { - signup: { - full_name: "" - } - } - end - - assert_response :unprocessable_entity, "Invalid params should return unprocessable entity" - end -end diff --git a/gems/fizzy-saas/test/fixtures/files/.keep b/gems/fizzy-saas/test/fixtures/files/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gems/fizzy-saas/test/helpers/.keep b/gems/fizzy-saas/test/helpers/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gems/fizzy-saas/test/integration/.keep b/gems/fizzy-saas/test/integration/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gems/fizzy-saas/test/mailers/.keep b/gems/fizzy-saas/test/mailers/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gems/fizzy-saas/test/models/.keep b/gems/fizzy-saas/test/models/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gems/fizzy-saas/test/models/signup/account_name_generator_test.rb b/gems/fizzy-saas/test/models/signup/account_name_generator_test.rb deleted file mode 100644 index d22922b29d..0000000000 --- a/gems/fizzy-saas/test/models/signup/account_name_generator_test.rb +++ /dev/null @@ -1,61 +0,0 @@ -require "test_helper" - -class Signup::AccountNameGeneratorTest < ActiveSupport::TestCase - setup do - @identity = Identity.create!(email_address: "newart.userbaum@example.com") - @name = "Newart userbaum" - @generator = Signup::AccountNameGenerator.new(identity: @identity, name: @name) - end - - test "generate" do - account_name = @generator.generate - assert_equal "Newart's Fizzy", account_name, "The 1st account doesn't have 1st in the name" - - first_account = Account.create!(external_account_id: "1st", name: account_name) - Current.without_account do - @identity.users.create!(account: first_account, name: @name) - @identity.reload - end - - account_name = @generator.generate - assert_equal "Newart's 2nd Fizzy", account_name - - second_account = Account.create!(external_account_id: "2nd", name: account_name) - Current.without_account do - @identity.users.create!(account: second_account, name: @name) - @identity.reload - end - - account_name = @generator.generate - assert_equal "Newart's 3rd Fizzy", account_name - - third_account = Account.create!(external_account_id: "3rd", name: account_name) - Current.without_account do - @identity.users.create!(account: third_account, name: @name) - @identity.reload - end - - account_name = @generator.generate - assert_equal "Newart's 4th Fizzy", account_name - - fourth_account = Account.create!(external_account_id: "4th", name: account_name) - Current.without_account do - @identity.users.create!(account: fourth_account, name: @name) - @identity.reload - end - - account_name = @generator.generate - assert_equal "Newart's 5th Fizzy", account_name - end - - test "generate continues from the previous highest index" do - account = Account.create!(external_account_id: "12th", name: "Newart's 12th Fizzy") - Current.without_account do - @identity.users.create!(account: account, name: @name) - @identity.reload - end - - account_name = @generator.generate - assert_equal "Newart's 13th Fizzy", account_name - end -end diff --git a/gems/fizzy-saas/test/models/signup_test.rb b/gems/fizzy-saas/test/models/signup_test.rb deleted file mode 100644 index 69c0e699f9..0000000000 --- a/gems/fizzy-saas/test/models/signup_test.rb +++ /dev/null @@ -1,53 +0,0 @@ -require "test_helper" - -class SignupTest < ActiveSupport::TestCase - test "#create_identity" do - signup = Signup.new(email_address: "brian@example.com") - - assert_difference -> { Identity.count }, 1 do - assert_difference -> { MagicLink.count }, 1 do - assert signup.create_identity - end - end - - assert_empty signup.errors - assert signup.identity - assert signup.identity.persisted? - - signup_existing = Signup.new(email_address: "brian@example.com") - - assert_no_difference -> { Identity.count } do - assert_difference -> { MagicLink.count }, 1 do - assert signup_existing.create_identity, "Should send magic link for existing identity" - end - end - - signup_invalid = Signup.new(email_address: "") - assert_raises do - signup_invalid.create_identity - end - end - - test "#complete" do - Account.any_instance.expects(:setup_customer_template).once - Current.without_account do - signup = Signup.new( - full_name: "Kevin", - identity: identities(:kevin) - ) - - assert signup.complete - - assert signup.account - assert signup.user - assert_equal "Kevin", signup.user.name - - signup_invalid = Signup.new( - full_name: "", - identity: identities(:kevin) - ) - assert_not signup_invalid.complete - assert_not_empty signup_invalid.errors[:full_name] - end - end -end diff --git a/gems/fizzy-saas/test/test_helper.rb b/gems/fizzy-saas/test/test_helper.rb deleted file mode 100644 index eeaf249547..0000000000 --- a/gems/fizzy-saas/test/test_helper.rb +++ /dev/null @@ -1,9 +0,0 @@ -require "queenbee/testing/mocks" - -Queenbee::Remote::Account.class_eval do - # because we use the account ID as the tenant name, we need it to be unique in each test to avoid - # parallelized tests clobbering each other. - def next_id - super + Random.rand(1000000) - end -end diff --git a/lib/bootstrap.rb b/lib/bootstrap.rb deleted file mode 100644 index 6d52f3f49e..0000000000 --- a/lib/bootstrap.rb +++ /dev/null @@ -1,5 +0,0 @@ -module Bootstrap - def self.oss_config? - ENV.fetch("OSS_CONFIG", "") != "" || !File.directory?(File.expand_path("../gems/fizzy-saas", __dir__)) - end -end diff --git a/lib/fizzy.rb b/lib/fizzy.rb new file mode 100644 index 0000000000..69b3f9c6f2 --- /dev/null +++ b/lib/fizzy.rb @@ -0,0 +1,33 @@ +module Fizzy + class << self + def saas? + return @saas if defined?(@saas) + @saas = !!(ENV["SAAS"] || File.exist?(File.expand_path("../tmp/saas.txt", __dir__))) + end + + def db_adapter + @db_adapter ||= DbAdapter.new ENV.fetch("DATABASE_ADAPTER", saas? ? "mysql" : "sqlite") + end + + def configure_bundle + if saas? + ENV["BUNDLE_GEMFILE"] = "Gemfile.saas" + end + end + end + + class DbAdapter + def initialize(name) + @name = name.to_s + end + + def to_s + @name + end + + # Not using inquiry so that it works before Rails env loads. + def sqlite? + @name == "sqlite" + end + end +end diff --git a/lib/tasks/saas.rake b/lib/tasks/saas.rake new file mode 100644 index 0000000000..4318da05a4 --- /dev/null +++ b/lib/tasks/saas.rake @@ -0,0 +1,18 @@ +namespace :saas do + SAAS_FILE_PATH = "tmp/saas.txt" + + desc "Enable SaaS mode" + task enable: :environment do + file_path = Rails.root.join(SAAS_FILE_PATH) + FileUtils.mkdir_p(File.dirname(file_path)) + FileUtils.touch(file_path) + puts "SaaS mode enabled (#{file_path} created)" + end + + desc "Disable SaaS mode" + task disable: :environment do + file_path = Rails.root.join(SAAS_FILE_PATH) + FileUtils.rm_f(file_path) + puts "SaaS mode disabled (#{file_path} removed)" + end +end diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb index 623423fdf0..2f5b27b0dc 100644 --- a/test/controllers/sessions_controller_test.rb +++ b/test/controllers/sessions_controller_test.rb @@ -21,22 +21,6 @@ class SessionsControllerTest < ActionDispatch::IntegrationTest end end - unless Bootstrap.oss_config? - test "create for a new user" do - untenanted do - assert_difference -> { Identity.count }, +1 do - assert_difference -> { MagicLink.count }, +1 do - post session_path, - params: { email_address: "nonexistent-#{SecureRandom.hex(6)}@example.com" }, - headers: http_basic_auth_headers("testname", "testpassword") - end - end - - assert_redirected_to session_magic_link_path - end - end - end - test "destroy" do sign_in_as :kevin @@ -47,9 +31,4 @@ class SessionsControllerTest < ActionDispatch::IntegrationTest assert_not cookies[:session_token].present? end end - - private - def http_basic_auth_headers(user, password) - { "Authorization" => ActionController::HttpAuthentication::Basic.encode_credentials(user, password) } - end end diff --git a/test/test_helper.rb b/test/test_helper.rb index bad1fdb2a1..722d39d59f 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -152,7 +152,3 @@ def uuid_v7_with_timestamp(time, seed_string) ActiveSupport.on_load(:active_record_fixture_set) do prepend(FixturesTestHelper) end - -unless Rails.application.config.x.oss_config - load File.expand_path("../gems/fizzy-saas/test/test_helper.rb", __dir__) -end