diff --git a/.docker/development/Dockerfile b/.docker/development/Dockerfile index 37a7a38..733b61b 100644 --- a/.docker/development/Dockerfile +++ b/.docker/development/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:2.6.3 AS cartafact_base +FROM ruby:3.2.5 AS cartafact_base ENV HOME /cartafact @@ -21,7 +21,7 @@ ARG BUNDLER_VERSION RUN apt-get update && \ apt-get -yq dist-upgrade && \ - apt-get install -y tzdata build-essential vim && \ + apt-get install -y tzdata build-essential vim libjemalloc2 && \ apt-get autoremove -y && \ gem update --system && \ gem install bundler:$BUNDLER_VERSION @@ -32,5 +32,4 @@ COPY . $HOME ENV RAILS_ENV='development' ENV NODE_ENV='development' -RUN bundle config set deployment 'true' && \ - bundle install +RUN bundle install diff --git a/.docker/production/Dockerfile b/.docker/production/Dockerfile index 368e91b..a21066b 100644 --- a/.docker/production/Dockerfile +++ b/.docker/production/Dockerfile @@ -2,7 +2,7 @@ ### app config ### ######################## -FROM ruby:2.6.3 AS app +FROM ruby:3.2.5 AS app ENV HOME /cartafact @@ -25,7 +25,7 @@ ENV BUNDLER_VERSION=$BUNDLER_VERSION RUN echo "\n\nbundler version: "$BUNDLER_VERSION RUN apt-get update && \ apt-get -yq dist-upgrade && \ - apt-get install -y tzdata build-essential vim && \ + apt-get install -y tzdata build-essential vim libjemalloc2 && \ apt-get autoremove -y && \ gem update --system && \ gem install bundler:$BUNDLER_VERSION diff --git a/.docker/production/Dockerfile.gha b/.docker/production/Dockerfile.gha new file mode 100644 index 0000000..2c77731 --- /dev/null +++ b/.docker/production/Dockerfile.gha @@ -0,0 +1,142 @@ +############################################ +### Base image ### +############################################ + +# Taken from .ruby-version +ARG RUBY_VERSION=3.2.5 +FROM ruby:$RUBY_VERSION-slim-bookworm AS base +LABEL author="DCHBX" + +ARG DEBIAN_FRONTEND=noninteractive + +# Taken from Gemfile.lock +ARG BUNDLER_VERSION=2.4.19 +ENV BUNDLER_VERSION=$BUNDLER_VERSION + +ARG NODE_MAJOR=20 +ENV NODE_MAJOR=$NODE_MAJOR + +RUN apt-get update -qq && \ + apt-get install -yq --no-install-recommends \ + curl \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ + && truncate -s 0 /var/log/*log + +# Add NodeJS to sources list +RUN curl -fsSL https://deb.nodesource.com/setup_$NODE_MAJOR.x | bash - + +# Basic packages needed because we're using the slim image +RUN apt-get update \ + && apt-get -yq dist-upgrade \ + && apt-get install -y \ + fontconfig \ + libcurl4-openssl-dev \ + libffi-dev \ + libsodium23 \ + libxext6 \ + libxrender1 \ + libyaml-cpp-dev \ + nodejs \ + default-jre \ + openssl \ + sshpass \ + unzip \ + zip \ + zlib1g \ + libjemalloc2 \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ + && truncate -s 0 /var/log/*log + +# Configure bundler and PATH, install bundler version +ENV GEM_HOME=/usr/local/bundle +ENV BUNDLE_PATH=$GEM_HOME +ENV BUNDLE_APP_CONFIG=$BUNDLE_PATH +ENV BUNDLE_BIN=/usr/local/bin +ENV BUNDLE_JOBS=4 +ENV BUNDLE_RETRY=3 + +ENV LANG=C.UTF-8 + +ENV HOME=/cartafact + +ENV PATH=$HOME/bin:$BUNDLE_BIN:$GEM_HOME/gems/bin:$PATH + +# rubygems-update > 3.4.22 requires ruby >= 3.0.0 +RUN gem update --system 3.4.22\ + && rm -f /usr/local/bin/ruby/gems/*/specifications/default/bundler-*.gemspec \ + && gem install bundler -v $BUNDLER_VERSION + +RUN groupadd --gid 1001 nonroot \ + && useradd --uid 1001 --gid nonroot --shell /bin/bash --create-home nonroot + +RUN mkdir $HOME \ + && chown -R nonroot:nonroot $HOME + +# Configure app home directory +WORKDIR $HOME + + +############################################################################### +### Builder. Adds node and Yarn. Not necessary in production. ### +############################################################################### + +FROM base AS builder + +ARG DEBIAN_FRONTEND=noninteractive + +# Add Yarn to the sources list +RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ + && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list + +# Install Nodejs & Yarn +RUN apt-get update -qq \ + && apt-get install -yq --no-install-recommends \ + build-essential \ + git \ + libpq-dev \ + yarn \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ + && truncate -s 0 /var/log/*log + + +######################################################## +### Node and Bundle for production ### +######################################################## + +FROM builder AS prod_gems_and_assets + +ENV RAILS_ENV=production + +ARG HOSTNAME=localhost + +COPY --chown=nonroot:nonroot ./Gemfile $HOME/Gemfile +COPY --chown=nonroot:nonroot ./Gemfile.lock $HOME/Gemfile.lock + +RUN bundle config set --local without 'development test' \ + && bundle install + +COPY --chown=nonroot:nonroot . $HOME + +# https://github.com/rubygems/rubygems/issues/3225 +RUN rm -rf $GEM_HOME/bundle/ruby/*/cache + +################################################################ +### Deployable image ### +################################################################ + +FROM base AS deploy + +# Copy prebuilt gems +COPY --chown=nonroot:nonroot --from=prod_gems_and_assets $BUNDLE_PATH $BUNDLE_PATH + +# Copy all app code again (sans gems, node_modules, assets) +COPY --chown=nonroot:nonroot . $HOME + +USER nonroot + +ENV RAILS_ENV=production + +ENTRYPOINT ["bin/docker-entrypoint"] \ No newline at end of file diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..35af776 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,23 @@ +# Release notes template (https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes) + +changelog: + categories: + - title: Breaking Changes 🚧 + labels: + # Semver-Major + - breaking change + - title: New Features ✨ + labels: + # Semver-Minor + - enhancement + - title: Bug Fixes 🐛 + labels: + # Semver-Patch + - bugfix + - title: Other Changes 📦 + labels: + - "*" + exclude: + labels: + - chore + - version bump diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml new file mode 100644 index 0000000..6bdfa97 --- /dev/null +++ b/.github/workflows/build_and_deploy.yml @@ -0,0 +1,214 @@ +name: Build Image and Deploy + +on: + workflow_dispatch: + push: + branches: + - "trunk" + - 'upgrade_ruby_and_rails' + - "fix_skip_parameter_encoding_issue" + pull_request: + branches: + - "trunk" + - 'upgrade_ruby_and_rails' + - "fix_skip_parameter_encoding_issue" + +concurrency: + group: docker-${{ github.ref }} + cancel-in-progress: true + +jobs: + prep: + runs-on: ubuntu-latest + outputs: + taggedImage: ${{ steps.prep.outputs.tagged_image }} + registryGhcr: ${{ steps.prep.outputs.registry_ghcr }} + shortSha: ${{ steps.prep.outputs.short_sha }} + branchName: ${{ steps.prep.outputs.branch_name }} + latestTag: ${{ steps.prep.outputs.latest_tag }} + repositoryName: ${{ steps.prep.outputs.repository_name }} + registryEcr: ${{ steps.prep.outputs.registry_ecr }} + steps: + - name: Prepare info + id: prep + run: | + SHORT_SHA=$(echo $GITHUB_SHA | head -c7) + REPO=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}') + BRANCH_NAME="${{ github.head_ref || github.ref_name }}" + TAG=${BRANCH_NAME}-$(echo $GITHUB_SHA | head -c7) + IMAGE=ideacrew/$REPO + echo "tagged_image=${IMAGE}:${TAG}" >> $GITHUB_OUTPUT + echo "registry_ghcr=ghcr.io" >> $GITHUB_OUTPUT + echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT + echo "branch_name=${BRANCH_NAME}" >> $GITHUB_OUTPUT + echo "repository_name=${REPO}" >> $GITHUB_OUTPUT + echo "latest_tag=${IMAGE}:latest" >> $GITHUB_OUTPUT + echo "registry_ecr=public.ecr.aws" >> $GITHUB_OUTPUT + + # Uses buildx to build and push the image + build-and-upload-image: + needs: [prep] + runs-on: ubuntu-latest + services: + mongo: + image: mongo:4.2 + ports: + - 27017:27017 + options: >- + --name "mongo" + --health-cmd mongo + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + # Check out repository + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3 + with: + install: true + + - name: Cache Docker layers + uses: actions/cache@v4 + with: + path: /tmp/.buildx-cache + # Key is named differently to avoid collision + key: ${{ runner.os }}-multi-buildx-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-multi-buildx-${{ github.ref }}- + ${{ runner.os }}-multi-buildx- + + # Provide credentials for AWS + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + # Must use docker login in order to specify public registry + - name: Login to Public ECR + uses: docker/login-action@v2 + with: + registry: ${{ needs.prep.outputs.registryEcr }} + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ${{ needs.prep.outputs.registryGhcr }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build Image + uses: docker/build-push-action@v6 + with: + context: . + builder: ${{ steps.buildx.outputs.name }} + file: .docker/production/Dockerfile.gha + # Set the desired build target here + target: deploy + # needed to access mongo and rabbit on GHA machine + network: host + # send to public registry if not a pull request + push: ${{ github.event_name != 'pull_request' }} + # create local image (for scanning) if it is a pull request + load: ${{ github.event_name == 'pull_request' }} + tags: | + ${{ format('{0}/{1}', needs.prep.outputs.registryGhcr, needs.prep.outputs.taggedImage) }} + ${{ format('{0}/{1}', needs.prep.outputs.registryGhcr, needs.prep.outputs.latestTag) }} + ${{ format('{0}/{1}', needs.prep.outputs.registryEcr, needs.prep.outputs.taggedImage) }} + ${{ format('{0}/{1}', needs.prep.outputs.registryEcr, needs.prep.outputs.latestTag) }} + cache-from: type=local,src=/tmp/.buildx-cache + # Note the mode=max here + # More: https://github.com/moby/buildkit#--export-cache-options + # And: https://github.com/docker/buildx#--cache-tonametypetypekeyvalue + cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new + build-args: | + HOSTNAME=172.17.0.1 + pull: true + + - name: Scan Docker image + if: github.event_name != 'pull_request' + id: scan + uses: anchore/scan-action@v6 + with: + image: ${{ format('{0}/{1}', needs.prep.outputs.registryGhcr, needs.prep.outputs.taggedImage) }} + acs-report-enable: true + fail-build: false + severity-cutoff: critical + +# - name: upload Anchore scan SARIF report +# if: github.event_name != 'pull_request' +# uses: github/codeql-action/upload-sarif@v2 +# with: +# sarif_file: ${{ steps.scan.outputs.sarif }} + + - name: Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache + + new-image-notification: + if: github.repository_owner == 'ideacrew' && github.event_name != 'pull_request' + needs: [prep, build-and-upload-image] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Git Commit Data w/ Message + uses: jcputney/git-commit-data-action@1.0.2 + - name: Post to a Slack channel + id: ic-slack + uses: slackapi/slack-github-action@v1 + with: + channel-id: 'docker-images-${{ needs.prep.outputs.repositoryName }}' + payload: | + { + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "New image built from on `${{ needs.prep.outputs.branchName }}`" + } + } + ] + } + env: + SLACK_BOT_TOKEN: ${{ secrets.YELLR_BOT_TOKEN }} + + +# enable slack when image is ready + notify-slack: + if: github.repository_owner == 'ideacrew' && github.event_name != 'pull_request' + needs: [new-image-notification, prep, build-and-upload-image] + runs-on: ubuntu-latest + strategy: + matrix: + registry: ['public.ecr.aws', 'ghcr.io'] + steps: + - name: Post to a Slack channel + id: ic-slack + uses: slackapi/slack-github-action@v1 + with: + channel-id: 'docker-images-${{ needs.prep.outputs.repositoryName }}' + payload: | + { + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*${{ matrix.registry }} image*:\n`${{ format('{0}/{1}', matrix.registry, needs.prep.outputs.taggedImage) }}`" + } + }, + { + "type": "divider" + } + ] + } + env: + SLACK_BOT_TOKEN: ${{ secrets.YELLR_BOT_TOKEN }} diff --git a/.github/workflows/label-checker.yml b/.github/workflows/label-checker.yml new file mode 100644 index 0000000..27736c9 --- /dev/null +++ b/.github/workflows/label-checker.yml @@ -0,0 +1,21 @@ +name: Label Checker + +on: + pull_request: + types: + - opened + - synchronize + - reopened + - labeled + - unlabeled + +jobs: + check_labels: + name: Check labels + runs-on: ubuntu-latest + steps: + - uses: docker://agilepathway/pull-request-label-checker:latest + with: + # At least one of the labels listed below must be present on the PR for the check to pass + any_of: breaking change,enhancement,bugfix,version bump,release,chore,documentation,dependencies + repo_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000..e32e495 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,23 @@ +name: Publish Release + +on: + push: + tags: + - v[0-9]+.[0-9]+.[0-9]+ + +permissions: + contents: write + +jobs: + publish: + runs-on: ubuntu-latest + + steps: + - name: Create release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "${{ github.ref_name }}" \ + --repo="$GITHUB_REPOSITORY" \ + --title="${{ github.ref_name }}" \ + --generate-notes diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index ca8e84a..543a7e9 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -14,10 +14,10 @@ jobs: uses: wbari/start-mongoDB@v0.2 with: mongoDBVersion: 3.6 - - name: Set up Ruby 2.6.3 + - name: Set up Ruby 3.2.5 uses: ruby/setup-ruby@v1 with: - ruby-version: 2.6.3 + ruby-version: 3.2.5 - name: Cache Gems uses: actions/cache@v4 with: diff --git a/.rubocop.yml b/.rubocop.yml index db69bdc..6f1d3e4 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,57 +1,119 @@ +# This yaml describes our current checks. AllCops: - TargetRubyVersion: 2.6.3 + TargetRubyVersion: 3.2.5 + SuggestExtensions: false + NewCops: enable + Exclude: + - "bin/**/*" + - "vendor/**/*" + - 'node_modules/**/*' -Layout/LineLength: - Max: 120 +Layout/EmptyLineAfterGuardClause: + Enabled: false -Bundler/OrderedGems: +Layout/EmptyLinesAroundBlockBody: + Enabled: false + +Layout/ExtraSpacing: + Enabled: false + +Layout/EmptyLinesAroundClassBody: + Enabled: false + +Layout/EmptyLinesAroundModuleBody: + Enabled: false + +Layout/SpaceInsideBlockBraces: + Enabled: false + +Layout/TrailingEmptyLines: Enabled: false Metrics/AbcSize: - Max: 20 + Max: 50 + +Metrics/ClassLength: + Max: 300 + +Metrics/ModuleLength: + Max: 200 + +Metrics/CyclomaticComplexity: + Max: 11 Metrics/BlockLength: - Exclude: - - spec/**/*.rb - - app/domain/cartafact/entities/validators/documents/**/*.rb + Enabled: false -Metrics/ClassLength: +Layout/LineLength: Max: 150 Metrics/MethodLength: - Max: 20 + Max: 25 -Metrics/ModuleLength: - Max: 300 +Metrics/PerceivedComplexity: + Max: 15 -Style/BlockComments: - Exclude: - - spec/spec_helper.rb +Naming/VariableNumber: + Enabled: false -Style/BracesAroundHashParameters: +Naming/ConstantName: Enabled: false -Style/Documentation: - Exclude: - - app/controllers/application_controller.rb - - app/mailers/application_mailer.rb - - config/application.rb - - spec/**/*_spec.rb +Style/BlockComments: + Enabled: false + +Style/EachWithObject: + Enabled: false Style/EmptyLiteral: Enabled: false +Style/EmptyMethod: + Enabled: false + Style/ExpandPathArguments: Enabled: false Style/HashSyntax: Enabled: false -Style/NumericLiterals: +Style/NumericPredicate: + Enabled: false + +Style/RedundantPercentQ: + Enabled: false + +Style/RedundantSelf: Enabled: false Style/StringLiterals: Enabled: false Style/SymbolArray: + Enabled: false + +Style/TernaryParentheses: + Enabled: false + +Style/WordArray: + Enabled: false + +Style/Documentation: + Enabled: true + Exclude: + - "config/application.rb" + +Bundler/OrderedGems: + Enabled: false + +Gemspec/OrderedDependencies: + Enabled: false + +Style/RedundantConstantBase: + Enabled: false + +Style/QuotedSymbols: + Enabled: false + +Style/SymbolProc: Enabled: false \ No newline at end of file diff --git a/.ruby-version b/.ruby-version index 4560fb9..ac55709 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-2.6.3 +ruby-3.2.5 diff --git a/Gemfile b/Gemfile index 3214047..0f0b1e5 100644 --- a/Gemfile +++ b/Gemfile @@ -3,17 +3,13 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby '2.6.3' +ruby '3.2.5' -# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '~> 6.0.0' -# TODO: Remove this gem constraint when upgrading beyond Ruby 2.6.3 and Rails 6 -gem 'mimemagic', '~> 0.3.10' -# Use postgresql as the database for Active Record -# gem 'pg', '>= 0.18', '< 2.0' -gem 'mongoid', '~> 7.0.5' +gem 'rails', '~> 7.2.0' + +gem 'mongoid', '~> 8.1.5' # Use Puma as the app server -gem 'puma', '~> 3.11' +gem 'puma', '~> 6.0' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder # gem 'jbuilder', '~> 2.7' # Use Active Model has_secure_password @@ -33,7 +29,7 @@ gem "aws-sdk-s3", "~> 1.14" # Settings, validation and dependency injection # TODO: Use tagged release for resource_registry instead of branch -gem 'resource_registry', git: 'https://github.com/ideacrew/resource_registry.git', branch: 'branch_0.3.2' +gem 'resource_registry', git: 'https://github.com/ideacrew/resource_registry.git', tag: 'v0.10.1' gem 'fast_jsonapi' gem 'shrine-mongoid', '~> 1.0' @@ -56,10 +52,10 @@ gem 'jwt', '~> 2.2.1' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] - gem 'rspec-rails', '~> 3.8' - gem 'shoulda-matchers', '~> 3' - gem 'yard' # , '~> 0.9.12', require: false - gem 'climate_control' # + gem 'rspec-rails', '~> 7.0' + gem 'shoulda-matchers' + gem 'yard' + gem 'climate_control' gem 'factory_bot_rails', '~> 4.11' gem 'database_cleaner', '~> 1.7' gem 'pry-byebug' @@ -67,12 +63,12 @@ end group :development do # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring - gem 'spring' - gem 'spring-watcher-listen', '~> 2.0.0' + # gem 'spring' + # gem 'spring-watcher-listen', '~> 2.0.0' # Access an interactive console on exception pages or by calling 'console' anywhere in the code. gem 'web-console', '>= 3' - gem 'listen', '>= 3.0.5', '< 3.2' + gem 'listen', '~> 3.3' gem 'rubocop', require: false gem 'rubocop-rspec' diff --git a/Gemfile.lock b/Gemfile.lock index 66440e5..5163669 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,190 +1,210 @@ GIT remote: https://github.com/ideacrew/resource_registry.git - revision: 345b24bfa823f602eebcc0c63e43bfa6b76b01e2 - branch: branch_0.3.2 + revision: a3ca0b8001247663a7f052cfdc32180dc7e1f43d + tag: v0.10.1 specs: - resource_registry (0.3.1) + resource_registry (0.10.1) bootsnap (~> 1.0) - deep_merge (~> 1.2.1) - dry-auto_inject (~> 0.6) - dry-container (~> 0.7) - dry-core (~> 0.4) - dry-equalizer (~> 0.2) - dry-inflector (~> 0.1) - dry-initializer (~> 3.0) + deep_merge (>= 1.0.0) + dry-configurable (~> 0.12) + dry-container (~> 0.9) dry-matcher (~> 0.7) - dry-monads (~> 1.2) + dry-monads (~> 1.3) dry-struct (~> 1.0) - dry-system (~> 0.12) - dry-transaction (~> 0.13) dry-types (~> 1.0) dry-validation (~> 1.2) - i18n (~> 1.6) + i18n (>= 0.7.0) mime-types ox (~> 2.0) GEM remote: https://rubygems.org/ specs: - actioncable (6.0.2.1) - actionpack (= 6.0.2.1) + actioncable (7.2.3) + actionpack (= 7.2.3) + activesupport (= 7.2.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.0.2.1) - actionpack (= 6.0.2.1) - activejob (= 6.0.2.1) - activerecord (= 6.0.2.1) - activestorage (= 6.0.2.1) - activesupport (= 6.0.2.1) - mail (>= 2.7.1) - actionmailer (6.0.2.1) - actionpack (= 6.0.2.1) - actionview (= 6.0.2.1) - activejob (= 6.0.2.1) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 2.0) - actionpack (6.0.2.1) - actionview (= 6.0.2.1) - activesupport (= 6.0.2.1) - rack (~> 2.0, >= 2.0.8) + zeitwerk (~> 2.6) + actionmailbox (7.2.3) + actionpack (= 7.2.3) + activejob (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + mail (>= 2.8.0) + actionmailer (7.2.3) + actionpack (= 7.2.3) + actionview (= 7.2.3) + activejob (= 7.2.3) + activesupport (= 7.2.3) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (7.2.3) + actionview (= 7.2.3) + activesupport (= 7.2.3) + cgi + nokogiri (>= 1.8.5) + racc + rack (>= 2.2.4, < 3.3) + rack-session (>= 1.0.1) rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.0.2.1) - actionpack (= 6.0.2.1) - activerecord (= 6.0.2.1) - activestorage (= 6.0.2.1) - activesupport (= 6.0.2.1) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (7.2.3) + actionpack (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (6.0.2.1) - activesupport (= 6.0.2.1) + actionview (7.2.3) + activesupport (= 7.2.3) builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.0.2.1) - activesupport (= 6.0.2.1) + cgi + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (7.2.3) + activesupport (= 7.2.3) globalid (>= 0.3.6) - activemodel (6.0.2.1) - activesupport (= 6.0.2.1) - activerecord (6.0.2.1) - activemodel (= 6.0.2.1) - activesupport (= 6.0.2.1) - activestorage (6.0.2.1) - actionpack (= 6.0.2.1) - activejob (= 6.0.2.1) - activerecord (= 6.0.2.1) - marcel (~> 0.3.1) - activesupport (6.0.2.1) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2) - addressable (2.7.0) - public_suffix (>= 2.0.2, < 5.0) - ast (2.4.0) - aws-eventstream (1.0.3) - aws-partitions (1.270.0) - aws-sdk-core (3.89.1) - aws-eventstream (~> 1.0, >= 1.0.2) - aws-partitions (~> 1, >= 1.239.0) - aws-sigv4 (~> 1.1) - jmespath (~> 1.0) - aws-sdk-kms (1.28.0) - aws-sdk-core (~> 3, >= 3.71.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.60.1) - aws-sdk-core (~> 3, >= 3.83.0) + activemodel (7.2.3) + activesupport (= 7.2.3) + activerecord (7.2.3) + activemodel (= 7.2.3) + activesupport (= 7.2.3) + timeout (>= 0.4.0) + activestorage (7.2.3) + actionpack (= 7.2.3) + activejob (= 7.2.3) + activerecord (= 7.2.3) + activesupport (= 7.2.3) + marcel (~> 1.0) + activesupport (7.2.3) + 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) + addressable (2.8.8) + public_suffix (>= 2.0.2, < 8.0) + ast (2.4.3) + aws-eventstream (1.4.0) + aws-partitions (1.1205.0) + aws-sdk-core (3.241.3) + 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.120.0) + aws-sdk-core (~> 3, >= 3.241.3) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.211.0) + aws-sdk-core (~> 3, >= 3.241.3) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.1) - aws-sigv4 (1.1.0) - aws-eventstream (~> 1.0, >= 1.0.2) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.12.1) + aws-eventstream (~> 1, >= 1.0.2) + base64 (0.3.0) + benchmark (0.5.0) + bigdecimal (4.0.1) bindex (0.8.1) - bootsnap (1.4.5) - msgpack (~> 1.0) - bson (4.7.1) - builder (3.2.4) - byebug (11.1.1) - climate_control (0.2.0) - coderay (1.1.2) - concurrent-ruby (1.1.5) + bootsnap (1.21.1) + msgpack (~> 1.2) + bson (5.2.0) + builder (3.3.0) + byebug (12.0.0) + cgi (0.5.1) + climate_control (1.2.0) + coderay (1.1.3) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) content_disposition (1.0.0) crass (1.0.6) - database_cleaner (1.8.2) - deep_merge (1.2.1) - diff-lcs (1.3) - down (5.1.1) - addressable (~> 2.5) - dry-auto_inject (0.7.0) + database_cleaner (1.99.0) + date (3.5.1) + deep_merge (1.2.2) + diff-lcs (1.6.2) + down (5.4.2) + addressable (~> 2.8) + drb (2.2.3) + dry-auto_inject (0.9.0) dry-container (>= 0.3.4) - dry-configurable (0.9.0) - concurrent-ruby (~> 1.0) - dry-core (~> 0.4, >= 0.4.7) - dry-container (0.7.2) + dry-configurable (0.16.1) + dry-core (~> 0.6) + zeitwerk (~> 2.6) + dry-container (0.11.0) concurrent-ruby (~> 1.0) - dry-configurable (~> 0.1, >= 0.1.3) - dry-core (0.4.9) + dry-core (0.9.1) concurrent-ruby (~> 1.0) + zeitwerk (~> 2.6) dry-equalizer (0.3.0) - dry-events (0.2.0) + dry-events (0.4.0) concurrent-ruby (~> 1.0) - dry-core (~> 0.4) - dry-equalizer (~> 0.2) - dry-inflector (0.2.0) - dry-initializer (3.0.3) - dry-logic (1.0.6) + dry-core (~> 0.9, >= 0.9) + dry-inflector (0.3.0) + dry-initializer (3.2.0) + dry-logic (1.3.0) concurrent-ruby (~> 1.0) - dry-core (~> 0.2) - dry-equalizer (~> 0.2) - dry-matcher (0.8.3) - dry-core (>= 0.4.8) - dry-monads (1.3.5) + dry-core (~> 0.9, >= 0.9) + zeitwerk (~> 2.6) + dry-matcher (0.9.0) + dry-core (~> 0.4, >= 0.4.8) + dry-monads (1.5.0) concurrent-ruby (~> 1.0) - dry-core (~> 0.4, >= 0.4.4) - dry-equalizer - dry-schema (1.4.3) + dry-core (~> 0.9, >= 0.9) + zeitwerk (~> 2.6) + dry-schema (1.11.3) concurrent-ruby (~> 1.0) - dry-configurable (~> 0.8, >= 0.8.3) - dry-core (~> 0.4) - dry-equalizer (~> 0.2) + dry-configurable (~> 0.16, >= 0.16) + dry-core (~> 0.9, >= 0.9) dry-initializer (~> 3.0) - dry-logic (~> 1.0) - dry-types (~> 1.2) - dry-struct (1.3.0) - dry-core (~> 0.4, >= 0.4.4) - dry-equalizer (~> 0.3) - dry-types (~> 1.3) + dry-logic (~> 1.3) + dry-types (~> 1.6) + zeitwerk (~> 2.6) + dry-struct (1.5.2) + dry-core (~> 0.9, >= 0.9) + dry-types (~> 1.6) ice_nine (~> 0.11) - dry-system (0.15.0) - concurrent-ruby (~> 1.0) + zeitwerk (~> 2.6) + dry-system (0.27.2) dry-auto_inject (>= 0.4.0) - dry-configurable (~> 0.7) - dry-container (~> 0.7, >= 0.7.2) - dry-core (~> 0.3, >= 0.3.1) - dry-equalizer (~> 0.2) + dry-configurable (~> 0.16, >= 0.16.0) + dry-container (~> 0.10, >= 0.10.0) + dry-core (~> 0.9, >= 0.9.0) dry-inflector (~> 0.1, >= 0.1.2) - dry-struct (~> 1.0) - dry-transaction (0.13.0) + zeitwerk (~> 2.6) + dry-transaction (0.14.0) dry-container (>= 0.2.8) - dry-events (>= 0.1.0) + dry-events (>= 0.4.0) dry-matcher (>= 0.7.0) - dry-monads (>= 0.4.0) - dry-types (1.3.0) + dry-monads (>= 0.5.0) + dry-types (1.6.1) concurrent-ruby (~> 1.0) dry-container (~> 0.3) - dry-core (~> 0.4, >= 0.4.4) - dry-equalizer (~> 0.3) + dry-core (~> 0.9, >= 0.9) dry-inflector (~> 0.1, >= 0.1.2) - dry-logic (~> 1.0, >= 1.0.2) - dry-validation (1.4.2) + dry-logic (~> 1.3, >= 1.3) + zeitwerk (~> 2.6) + dry-validation (1.9.0) concurrent-ruby (~> 1.0) dry-container (~> 0.7, >= 0.7.1) - dry-core (~> 0.4) - dry-equalizer (~> 0.2) + dry-core (~> 0.9, >= 0.9) dry-initializer (~> 3.0) - dry-schema (~> 1.4, >= 1.4.3) - erubi (1.9.0) + dry-schema (~> 1.11, >= 1.11.0) + zeitwerk (~> 2.6) + erb (6.0.1) + erubi (1.13.1) factory_bot (4.11.1) activesupport (>= 3.0.0) factory_bot_rails (4.11.1) @@ -192,157 +212,213 @@ GEM railties (>= 3.0.0) fast_jsonapi (1.5) activesupport (>= 4.2) - ffi (1.12.2) - globalid (0.4.2) - activesupport (>= 4.2.0) - i18n (1.8.2) + ffi (1.17.3) + globalid (1.3.0) + activesupport (>= 6.1) + i18n (1.14.8) concurrent-ruby (~> 1.0) ice_nine (0.11.2) - jaro_winkler (1.5.4) - jmespath (1.4.0) - jwt (2.2.1) - listen (3.1.5) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) - loofah (2.4.0) + io-console (0.8.2) + irb (1.16.0) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jmespath (1.6.2) + json (2.18.0) + jwt (2.2.3) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.10.0) + logger + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + loofah (2.25.0) crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.1) + nokogiri (>= 1.12.0) + mail (2.9.0) + logger mini_mime (>= 0.1.1) - marcel (0.3.3) - mimemagic (~> 0.3.2) - method_source (0.9.2) - mime-types (3.3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2019.1009) - mimemagic (0.3.10) - nokogiri (~> 1) - rake (>= 0) - mini_mime (1.0.2) - mini_portile2 (2.4.0) - minitest (5.14.0) - mongo (2.11.3) - bson (>= 4.4.2, < 5.0.0) - mongoid (7.0.5) - activemodel (>= 5.1, < 6.1) - mongo (>= 2.5.1, < 3.0.0) - msgpack (1.3.3) - nio4r (2.5.2) - nokogiri (1.10.7) - mini_portile2 (~> 2.4.0) - ox (2.13.2) - parallel (1.19.1) - parser (2.7.0.2) - ast (~> 2.4.0) - pry (0.12.2) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - pry-byebug (3.8.0) - byebug (~> 11.0) - pry (~> 0.10) - public_suffix (4.0.3) - puma (3.12.2) - rack (2.1.2) - rack-cors (1.1.1) - rack (>= 2.0.0) - rack-test (1.1.0) - rack (>= 1.0, < 3) - rails (6.0.2.1) - actioncable (= 6.0.2.1) - actionmailbox (= 6.0.2.1) - actionmailer (= 6.0.2.1) - actionpack (= 6.0.2.1) - actiontext (= 6.0.2.1) - actionview (= 6.0.2.1) - activejob (= 6.0.2.1) - activemodel (= 6.0.2.1) - activerecord (= 6.0.2.1) - activestorage (= 6.0.2.1) - activesupport (= 6.0.2.1) - bundler (>= 1.3.0) - railties (= 6.0.2.1) - sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + net-imap + net-pop + net-smtp + marcel (1.1.0) + method_source (1.1.0) + mime-types (3.7.0) + logger + mime-types-data (~> 3.2025, >= 3.2025.0507) + mime-types-data (3.2026.0113) + mini_mime (1.1.5) + mini_portile2 (2.8.9) + minitest (6.0.1) + prism (~> 1.5) + mongo (2.22.0) + base64 + bson (>= 4.14.1, < 6.0.0) + mongoid (8.1.11) + activemodel (>= 5.1, < 8.1, != 7.0.0) + concurrent-ruby (>= 1.0.5, < 2.0) + mongo (>= 2.18.0, < 3.0.0) + ruby2_keywords (~> 0.0.5) + msgpack (1.8.0) + net-imap (0.6.2) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.5) + nokogiri (1.19.0) + mini_portile2 (~> 2.8.2) + racc (~> 1.4) + ox (2.14.23) + bigdecimal (>= 3.0) + parallel (1.27.0) + parser (3.3.10.1) + ast (~> 2.4.1) + racc + pp (0.6.3) + prettyprint + prettyprint (0.2.0) + prism (1.8.0) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + pry-byebug (3.11.0) + byebug (~> 12.0) + pry (>= 0.13, < 0.16) + psych (5.3.1) + date + stringio + public_suffix (7.0.2) + puma (6.6.1) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.4) + rack-cors (3.0.0) + logger + rack (>= 3.0.14) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.3.1) + rack (>= 3) + rails (7.2.3) + actioncable (= 7.2.3) + actionmailbox (= 7.2.3) + actionmailer (= 7.2.3) + actionpack (= 7.2.3) + actiontext (= 7.2.3) + actionview (= 7.2.3) + activejob (= 7.2.3) + activemodel (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + bundler (>= 1.15.0) + railties (= 7.2.3) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.3.0) - loofah (~> 2.3) - railties (6.0.2.1) - actionpack (= 6.0.2.1) - activesupport (= 6.0.2.1) - method_source - rake (>= 0.8.7) - thor (>= 0.20.3, < 2.0) - rainbow (3.0.0) - rake (13.0.1) - rb-fsevent (0.10.3) - rb-inotify (0.10.1) + 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) + railties (7.2.3) + actionpack (= 7.2.3) + activesupport (= 7.2.3) + cgi + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) ffi (~> 1.0) - rspec-core (3.9.1) - rspec-support (~> 3.9.1) - rspec-expectations (3.9.0) + rdoc (7.1.0) + erb + psych (>= 4.0.0) + tsort + regexp_parser (2.11.3) + reline (0.6.3) + io-console (~> 0.5) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-mocks (3.9.1) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.7) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-rails (3.9.0) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.9.0) - rspec-expectations (~> 3.9.0) - rspec-mocks (~> 3.9.0) - rspec-support (~> 3.9.0) - rspec-support (3.9.2) - rubocop (0.79.0) - jaro_winkler (~> 1.5.1) + rspec-support (~> 3.13.0) + rspec-rails (7.1.1) + actionpack (>= 7.0) + activesupport (>= 7.0) + railties (>= 7.0) + rspec-core (~> 3.13) + rspec-expectations (~> 3.13) + rspec-mocks (~> 3.13) + rspec-support (~> 3.13) + rspec-support (3.13.6) + rubocop (1.82.1) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) parallel (~> 1.10) - parser (>= 2.7.0.1) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.48.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.49.0) + parser (>= 3.3.7.2) + prism (~> 1.7) rubocop-git (0.1.3) rubocop (>= 0.24.1) - rubocop-rspec (1.37.1) - rubocop (>= 0.68.1) - ruby-progressbar (1.10.1) - ruby_dep (1.5.0) - shoulda-matchers (3.1.3) - activesupport (>= 4.0.0) - shrine (3.2.1) + rubocop-rspec (3.9.0) + lint_roller (~> 1.1) + rubocop (~> 1.81) + ruby-progressbar (1.13.0) + ruby2_keywords (0.0.5) + securerandom (0.4.1) + shoulda-matchers (7.0.1) + activesupport (>= 7.1) + shrine (3.6.0) content_disposition (~> 1.0) down (~> 5.1) shrine-mongoid (1.0.0) mongoid (>= 5.0) shrine (>= 3.0.0.beta3, < 4) - spring (2.1.0) - spring-watcher-listen (2.0.1) - listen (>= 2.7, < 4.0) - spring (>= 1.2, < 3.0) - sprockets (4.0.0) + stringio (3.2.0) + thor (1.5.0) + timeout (0.6.0) + tsort (0.2.0) + tzinfo (2.0.6) concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.1) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) - thor (1.0.1) - thread_safe (0.3.6) - tzinfo (1.2.6) - thread_safe (~> 0.1) - unicode-display_width (1.6.1) - web-console (4.0.1) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) + useragent (0.16.11) + web-console (4.2.1) actionview (>= 6.0.0) activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - websocket-driver (0.7.1) + websocket-driver (0.8.0) + base64 websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.4) - yard (0.9.24) - zeitwerk (2.2.2) + websocket-extensions (0.1.5) + yard (0.9.38) + zeitwerk (2.7.4) PLATFORMS ruby @@ -369,26 +445,24 @@ DEPENDENCIES factory_bot_rails (~> 4.11) fast_jsonapi jwt (~> 2.2.1) - listen (>= 3.0.5, < 3.2) - mongoid (~> 7.0.5) + listen (~> 3.3) + mongoid (~> 8.1.5) pry-byebug - puma (~> 3.11) + puma (~> 6.0) rack-cors - rails (~> 6.0.0) + rails (~> 7.2.0) resource_registry! - rspec-rails (~> 3.8) + rspec-rails (~> 7.0) rubocop rubocop-git rubocop-rspec - shoulda-matchers (~> 3) + shoulda-matchers shrine-mongoid (~> 1.0) - spring - spring-watcher-listen (~> 2.0.0) web-console (>= 3) yard RUBY VERSION - ruby 2.6.3p62 + ruby 3.2.5p208 BUNDLED WITH - 2.0.2 + 2.4.19 diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..3c74b3b --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,42 @@ +# Release + +This file describes the process for publishing a new version of the app as a GitHub release. + +Releases are managed through the [GitHub Releases](https://github.com/ideacrew/cartafact/releases) page. + +Release names follow the [Semantic Versioning](https://semver.org/) standard. + +Follow the steps below to package and release a new version. + +## Major/Minor release +### Publishing the Release +1. Checkout the main branch and pull the latest changes. +2. Create and checkout a new release branch, in the pattern of `1.0.x-release`. + - Note: `x` is literal, to aid reuse of same branch for minor bugfixes. +3. `git push` the new branch. +4. Create a new annotated tag with the version number, e.g., `git tag -as v1.0.0 -m "v1.0.0"`. + - IMPORTANT: make sure the tag abides by the format `vX.Y.Z` where `X`, `Y`, and `Z` are integers. It is important that the tag name has a different format than any branch name to avoid confusion with Bundler. +5. Push the tag to the remote repository, e.g., `git push origin refs/tags/v1.0.0`. + - Avoid `git push --tags`, to not accidentally push random assorted local tags. +6. GitHub Actions will automatically create a new release on the [GitHub Releases](https://github.com/ideacrew/cartafact/releases) page with release notes. Confirm that the release was successfully published there and that all intended commits are included in the release. + +## Patch/Bugfix release +### Prepare the release +1. Fix bug in the main branch, via normal PR process. + +For any release that has the bug: +2. Create a temp branch off any live release branch that has the bug. + - Using a branch, which is then PR’d, ensures traceability and inclusion of an item in the generated release notes. +3. Cherry-pick the fix commits to the temp branch. +4. `git push` the temp branch. +5. Issue a PR to merge to the release branch. + +### Publishing the Release +1. Once the pull request is approved and merged, checkout the release branch and pull the latest changes. +2. Create a new annotated tag with the version number, at the point of the release branch with the fix, e.g., `git tag -as v1.0.1 -m "v1.0.1"`. +3. Push the tag to the remote repository, e.g., `git push origin refs/tags/v1.0.1`. + - Again, better to avoid `git push --tags`. +4. Github Actions will create the release and pull in the fix PR's to the changelog. + +## Git Process diagram +![Git Process Diagram - App](docs_assets/release_branching_deployable_app.png) \ No newline at end of file diff --git a/app/controllers/api/v1/documents_controller.rb b/app/controllers/api/v1/documents_controller.rb index a4170bc..426fc88 100644 --- a/app/controllers/api/v1/documents_controller.rb +++ b/app/controllers/api/v1/documents_controller.rb @@ -4,11 +4,19 @@ module Api module V1 # Controller exposing the document API. class DocumentsController < ApplicationController - FILE_STREAM_SIZE = 32768 - - # Rails 7+ enforces UTF-8 parameter validation which rejects binary PDF content - # Skip encoding validation for the create action to allow binary file uploads - skip_parameter_encoding :create if respond_to?(:skip_parameter_encoding) + FILE_STREAM_SIZE = 32_768 + + # Rails 7.1+ enforces strict UTF-8 parameter validation by default, which rejects binary PDF content + # (e.g., %PDF-1.3 with non-UTF-8 bytes) before the controller action runs, raising ActionController::BadRequest. + # skip_parameter_encoding bypasses this validation for the create action to allow binary file uploads. + # Version check ensures compatibility: the method only exists in Rails 7.1+, not in earlier versions. + if Rails.gem_version >= Gem::Version.new('7.1.0') + begin + skip_parameter_encoding :create + rescue NoMethodError + # Method not available in this Rails version + end + end # query for documents. Returns an array. def index @@ -52,6 +60,7 @@ def create end include ActionController::Live + def download authorization_information = verify_authorization_headers_present return nil unless authorization_information diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e5d4b33..7daff4e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -class ApplicationController < ActionController::API +# Application controller +class ApplicationController < ActionController::Base # before_action :authenticate private @@ -9,9 +10,9 @@ def authenticate headers = request.headers if headers['Authorization'].present? begin - token = headers['Authorization'].split(' ').last + token = headers['Authorization'].split.last JWT.decode(token, jwt_secret_key) - rescue JWT::ExpiredSignature, JWT::VerificationError + rescue JWT::ExpiredSignature render json: { status: "failure", errors: ['Expired token'] } nil rescue JWT::DecodeError, JWT::VerificationError diff --git a/app/domain/cartafact/entities/operations/documents/persist.rb b/app/domain/cartafact/entities/operations/documents/persist.rb index 7c5f1f9..869fdeb 100644 --- a/app/domain/cartafact/entities/operations/documents/persist.rb +++ b/app/domain/cartafact/entities/operations/documents/persist.rb @@ -6,7 +6,7 @@ module Operations module Documents # Shared operation for create/update of document. class Persist - include Dry::Transaction::Operation + include Dry::Monads[:result] def call(input) document = ::Document.new(map_to_model_params(input.to_h)) diff --git a/app/domain/cartafact/entities/operations/documents/serialize.rb b/app/domain/cartafact/entities/operations/documents/serialize.rb index 18dc1ba..eb0fb4d 100644 --- a/app/domain/cartafact/entities/operations/documents/serialize.rb +++ b/app/domain/cartafact/entities/operations/documents/serialize.rb @@ -6,7 +6,7 @@ module Operations module Documents # Serialize a document resource to a "showable" hash. class Serialize - include Dry::Transaction::Operation + include Dry::Monads[:result] def call(input) result = ::DocumentSerializer.new(input).serializable_hash[:data][:attributes] diff --git a/app/domain/cartafact/entities/operations/documents/where.rb b/app/domain/cartafact/entities/operations/documents/where.rb index 4b9d010..cf9bedd 100644 --- a/app/domain/cartafact/entities/operations/documents/where.rb +++ b/app/domain/cartafact/entities/operations/documents/where.rb @@ -8,7 +8,7 @@ module Operations module Documents # Given identity and search criteria, return a list of document resources. class Where - include Dry::Transaction::Operation + include Dry::Monads[:result] def self.call(input) new.call(input) diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index d84cb6e..b9708af 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +# Application mailer class ApplicationMailer < ActionMailer::Base default from: 'from@example.com' layout 'mailer' diff --git a/app/serializers/document_serializer.rb b/app/serializers/document_serializer.rb index 248d99d..8a5895e 100644 --- a/app/serializers/document_serializer.rb +++ b/app/serializers/document_serializer.rb @@ -3,9 +3,13 @@ # Serializes a represented document for JSON resource format. class DocumentSerializer include FastJsonapi::ObjectSerializer + attributes :title, :creator, :identifier, :description, :language, :format, :source, :date, :creator - attribute :document_type, &:document_type + attribute :document_type do |object| + object.document_type + end + attribute :subjects do |object| object.subjects.map do |subj| { diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint new file mode 100755 index 0000000..f06279e --- /dev/null +++ b/bin/docker-entrypoint @@ -0,0 +1,9 @@ +#!/bin/bash -e + +# Enable jemalloc for reduced memory usage and latency, especially in production +if [ -z "${LD_PRELOAD+x}" ]; then + LD_PRELOAD=$(find /usr/lib -name libjemalloc.so.2 -print -quit) + export LD_PRELOAD +fi + +exec "${@}" \ No newline at end of file diff --git a/config.ru b/config.ru index 842bccc..9bcb8d2 100644 --- a/config.ru +++ b/config.ru @@ -5,3 +5,4 @@ require_relative 'config/environment' run Rails.application +Rails.application.load_server \ No newline at end of file diff --git a/config/application.rb b/config/application.rb index 9843efe..d327487 100644 --- a/config/application.rb +++ b/config/application.rb @@ -23,7 +23,7 @@ module App class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 6.0 + config.load_defaults 7.1 # Settings in config/environments/* take precedence over those specified here. # Application configuration can go into files in config/initializers diff --git a/config/environments/production.rb b/config/environments/production.rb index 2fc664c..5df24c0 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -71,7 +71,7 @@ # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') if ENV["RAILS_LOG_TO_STDOUT"].present? - logger = ActiveSupport::Logger.new(STDOUT) + logger = ActiveSupport::Logger.new($stdout) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) end diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb index f4556db..6d56e43 100644 --- a/config/initializers/application_controller_renderer.rb +++ b/config/initializers/application_controller_renderer.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # Be sure to restart your server when you modify this file. # ActiveSupport::Reloader.to_prepare do diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb index d0f0d3b..4b63f28 100644 --- a/config/initializers/backtrace_silencers.rb +++ b/config/initializers/backtrace_silencers.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index aa7435f..dc84742 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format. Inflections diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index 6e1d16f..be6fedc 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Add new mime types for use in respond_to blocks: diff --git a/config/initializers/shrine.rb b/config/initializers/shrine.rb index 471be5b..cc2a4eb 100644 --- a/config/initializers/shrine.rb +++ b/config/initializers/shrine.rb @@ -5,13 +5,13 @@ require "shrine/storage/s3" s3_options = { - bucket: ENV['CARTAFACT_BUCKET'], - region: ENV['AWS_REGION'], - access_key_id: ENV['AWS_ACCESS_KEY_ID'], - secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'] + bucket: ENV.fetch('CARTAFACT_BUCKET', nil), + region: ENV.fetch('AWS_REGION', nil), + access_key_id: ENV.fetch('AWS_ACCESS_KEY_ID', nil), + secret_access_key: ENV.fetch('AWS_SECRET_ACCESS_KEY', nil) } -paper_bucket = ENV['CARTAFACT_PAPER_BUCKET'] +paper_bucket = ENV.fetch('CARTAFACT_PAPER_BUCKET', nil) paper_bucket_s3_options = s3_options.merge(bucket: paper_bucket) storage_options = if Rails.env.production? { diff --git a/config/mongoid.yml b/config/mongoid.yml index 751496e..c76178b 100644 --- a/config/mongoid.yml +++ b/config/mongoid.yml @@ -9,7 +9,7 @@ development: # Provides the hosts the default session can connect to. Must be an array # of host:port pairs. (required) hosts: - - <%= ENV['DB_HOST'] %>:<%= ENV['DB_PORT'] %> + - "<%= ENV.fetch('DB_HOST', 'localhost') %>:<%= ENV.fetch('CARTAFACT_DB_PORT', '27017') %>" options: # Change the default write concern. (default = { w: 1 }) # write: @@ -64,9 +64,9 @@ development: test: clients: default: - database: cartafact_test<%= ENV['TEST_ENV_NUMBER'] %> + database: cartafact_test<%= ENV.fetch('TEST_ENV_NUMBER', '') %> hosts: - - <%= ENV['DB_HOST'] %>:<%= ENV['DB_PORT'] %> + - "<%= ENV.fetch('DB_HOST', 'localhost') %>:<%= ENV.fetch('CARTAFACT_DB_PORT', '27017') %>" options: # In the test environment we lower the retries and retry interval to # low amounts for fast failures. @@ -77,8 +77,19 @@ production: default: database: cartafact_production hosts: - - <%= ENV['DB_HOST'] %>:<%= ENV['DB_PORT'] %> + - <%= ENV['CARTAFACT_DB_HOST'] %>:<%= ENV['CARTAFACT_DB_PORT'] %> options: + <% if ENV['CARTAFACT_DB_AUTH'] == true %> + replica_set: <%= ENV['CARTAFACT_DB_REPLICA_SET_NAME'] %> + + user: <%= ENV['CARTAFACT_DB_USERNAME'] %> + password: <%= ENV['CARTAFACT_DB_PASSWORD'] %> + auth_source: admin + + write: + w: 1 + j: true + <% end %> # In the test environment we lower the retries and retry interval to # low amounts for fast failures. max_retries: 1 diff --git a/config/puma.rb b/config/puma.rb index a47b289..36af80e 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -6,20 +6,42 @@ # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum; this matches the default thread size of Active Record. # -max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +max_threads_count = ENV.fetch("RAILS_MAX_THREADS", 5) min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } threads min_threads_count, max_threads_count +preload_app! + +# Specifies the `worker_timeout` threshold that Puma will use to wait before +# terminating a worker in development environments. +# +worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" + # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # -port ENV.fetch("PORT") { 3000 } +port ENV.fetch("PORT", 3000) # Specifies the `environment` that Puma will run in. # -environment ENV.fetch("RAILS_ENV") { "development" } +environment ENV.fetch("RAILS_ENV", "development") + +on_worker_boot do + Mongoid::Clients.clients.each_value do |client| + client.close + client.reconnect + end +end + +before_fork do + Mongoid.disconnect_clients +end + +on_refork do + Mongoid.disconnect_clients +end # Specifies the `pidfile` that Puma will use. -pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } +pidfile ENV.fetch("PIDFILE", "tmp/pids/server.pid") # Specifies the number of `workers` to boot in clustered mode. # Workers are forked web server processes. If using threads and workers together diff --git a/db/seeds.rb b/db/seeds.rb index 8744e3c..ebd1889 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # This file should contain all the record creation needed to seed the database with its default values. # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). # diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index d3eb8ec..7979833 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -6,8 +6,8 @@ services: context: . dockerfile: ./.docker/production/Dockerfile args: - RUBY_VERSION: '2.6.3' - BUNDLER_VERSION: '2.0.2' + RUBY_VERSION: '3.2.5' + BUNDLER_VERSION: '2.4.19' image: ideacrew/cartafact_app:${IMAGE_TAG:-test_prod} tmpfs: - /tmp diff --git a/docker-compose.yml b/docker-compose.yml index 089b546..b7b9f69 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,8 +6,8 @@ services: context: . dockerfile: ./.docker/development/Dockerfile args: - RUBY_VERSION: '2.6.3' - BUNDLER_VERSION: '2.0.2' + RUBY_VERSION: '3.2.5' + BUNDLER_VERSION: '2.4.19' image: cartafact-dev:0.1.0 volumes: - .:/app:cached diff --git a/docs_assets/NOTE.md b/docs_assets/NOTE.md new file mode 100644 index 0000000..7e44061 --- /dev/null +++ b/docs_assets/NOTE.md @@ -0,0 +1 @@ +This folder is intended for assets used in project internal documentation (RELEASING.md, README.md, etc.). diff --git a/docs_assets/release_branching_deployable_app.png b/docs_assets/release_branching_deployable_app.png new file mode 100644 index 0000000..1acece8 Binary files /dev/null and b/docs_assets/release_branching_deployable_app.png differ diff --git a/spec/controllers/api/v1/documents_controller_spec.rb b/spec/controllers/api/v1/documents_controller_spec.rb index a78f818..ca28447 100644 --- a/spec/controllers/api/v1/documents_controller_spec.rb +++ b/spec/controllers/api/v1/documents_controller_spec.rb @@ -1,5 +1,9 @@ # frozen_string_literal: true +# Set ENV variables before Rails loads to allow mongoid.yml to parse correctly +ENV['CARTAFACT_DB_HOST'] ||= 'localhost' +ENV['CARTAFACT_DB_PORT'] ||= '27017' + require 'rails_helper' RSpec.describe Api::V1::DocumentsController, type: :controller do @@ -254,8 +258,8 @@ content_pairs = content_disposition.split("; ").select { |h| h.include?("=") }.map do |h| h.split("=") end - content_hash = Hash[content_pairs] - expect(content_hash["filename"]).to eq("\"" + document.file.original_filename + "\"") + content_hash = content_pairs.to_h + expect(content_hash["filename"]).to eq("\"#{document.file.original_filename}\"") end it "has the original file data" do diff --git a/spec/domain/cartafact/entities/operations/documents/copy_spec.rb b/spec/domain/cartafact/entities/operations/documents/copy_spec.rb index 2f73e71..45e7df4 100644 --- a/spec/domain/cartafact/entities/operations/documents/copy_spec.rb +++ b/spec/domain/cartafact/entities/operations/documents/copy_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Cartafact::Entities::Operations::Documents::Copy do let(:valid_id) { '695bd7d1d88b230001580217' } let(:filename) { '1629675_yourplanenrollment_ivlenr_ivl_20260105152504.pdf' } - let(:file_metadata) { { 'filename' => filename, 'size' => 273802, 'mime_type' => 'application/pdf' } } + let(:file_metadata) { { 'filename' => filename, 'size' => 273_802, 'mime_type' => 'application/pdf' } } let(:uploaded_file) { double('Shrine::UploadedFile', metadata: file_metadata) } let(:document) { double('Document', file: uploaded_file) } let(:copy_instance) { described_class.new } @@ -16,7 +16,7 @@ id: 'RackMultipart20260107-1-u26s7n.pdf', metadata: { 'filename' => 'RackMultipart20260107-1-u26s7n.pdf', - 'size' => 212855, + 'size' => 212_855, 'mime_type' => 'application/pdf' } ) @@ -35,7 +35,7 @@ expect(mock_uploaded_file.storage).to eq(:paper_bucket_store) expect(mock_uploaded_file.id).to eq('RackMultipart20260107-1-u26s7n.pdf') expect(mock_uploaded_file.metadata['filename']).to eq('RackMultipart20260107-1-u26s7n.pdf') - expect(mock_uploaded_file.metadata['size']).to eq(212855) + expect(mock_uploaded_file.metadata['size']).to eq(212_855) expect(mock_uploaded_file.metadata['mime_type']).to eq('application/pdf') end diff --git a/spec/domain/operations/validate_resource_identity_signature_spec.rb b/spec/domain/operations/validate_resource_identity_signature_spec.rb index 6a1835d..0173843 100644 --- a/spec/domain/operations/validate_resource_identity_signature_spec.rb +++ b/spec/domain/operations/validate_resource_identity_signature_spec.rb @@ -3,6 +3,7 @@ require "rails_helper" module Cartafact + # Validate resource identity signature module Operations RSpec.describe ValidateResourceIdentitySignature do subject do diff --git a/spec/factories/documents_documents.rb b/spec/factories/documents_documents.rb deleted file mode 100644 index 34fc5b9..0000000 --- a/spec/factories/documents_documents.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -FactoryBot.define do - factory :documents_document, class: 'Documents::Document' do - end -end