diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 00000000..d487a895
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,14 @@
+# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
+ARG RUBY_VERSION=3.2.2
+FROM ghcr.io/rails/devcontainer/images/ruby:$RUBY_VERSION
+
+# Install packages needed to build gems
+RUN apt-get update -qq && \
+ apt-get install --no-install-recommends -y \
+ libpq-dev libvips \
+ # For video thumbnails
+ ffmpeg \
+ # For pdf thumbnails. If you want to use mupdf instead of poppler,
+ # you can install the following packages instead:
+ # mupdf mupdf-tools
+ poppler-utils
diff --git a/.devcontainer/compose.yaml b/.devcontainer/compose.yaml
new file mode 100644
index 00000000..fb3bcf38
--- /dev/null
+++ b/.devcontainer/compose.yaml
@@ -0,0 +1,50 @@
+services:
+ rails-app:
+ build:
+ context: ..
+ dockerfile: .devcontainer/Dockerfile
+
+ volumes:
+ - ../..:/workspaces:cached
+
+ # Overrides default command so things don't shut down after the process ends.
+ command: sleep infinity
+
+ networks:
+ - default
+
+ # Uncomment the next line to use a non-root user for all processes.
+ # user: vscode
+
+ # Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
+ # (Adding the "ports" property to this file will not forward from a Codespace.)
+ ports:
+ - 45678:45678
+ depends_on:
+ - redis
+ - postgres
+
+
+ redis:
+ image: redis:7.2
+ restart: unless-stopped
+ networks:
+ - default
+ volumes:
+ - redis-data:/data
+
+ postgres:
+ image: postgres:16.1
+ restart: unless-stopped
+ networks:
+ - default
+ volumes:
+ - postgres-data:/var/lib/postgresql/data
+ environment:
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: postgres
+
+
+volumes:
+ redis-data:
+ postgres-data:
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 00000000..3e374a01
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,30 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/ruby
+{
+ "name": "ticket_booth",
+ "dockerComposeFile": "compose.yaml",
+ "service": "rails-app",
+ "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
+
+ // Features to add to the dev container. More info: https://containers.dev/features.
+ "features": {
+ "ghcr.io/devcontainers/features/github-cli:1": {}
+ },
+
+ "containerEnv": {
+ "REDIS_URL": "redis://redis:6379/1",
+ "DB_HOST": "postgres"
+ },
+
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
+ // "forwardPorts": [],
+
+ // Configure tool-specific properties.
+ // "customizations": {},
+
+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
+ // "remoteUser": "root",
+
+ // Use 'postCreateCommand' to run commands after the container is created.
+ "postCreateCommand": "bin/setup"
+}
diff --git a/.env b/.env
deleted file mode 100644
index 6faa3628..00000000
--- a/.env
+++ /dev/null
@@ -1,10 +0,0 @@
-RUBY_VERSION=2.5.8
-PORT=5000
-POSTGRES_DB=cloudwatch_dev
-POSTGRES_USER=postgres
-POSTGRES_PASSWORD=
-CLOUDFLARE_EMAIL=
-POSTGRES_VERSION=12
-TERRAFORM_VERSION=1.1.8
-SENTRY_PLUGIN_VERSION=0.7.0
-ANSIBLE_VERSION="6.0.0a1"
diff --git a/.envrc b/.envrc
index 3306c889..a27a9bf6 100644
--- a/.envrc
+++ b/.envrc
@@ -1,5 +1,27 @@
# vim: ft=bash
PATH_add bin
+PATH_add /opt/homebrew/bin
+
+export VOLTA_HOME=${HOME}/.volta
+PATH_add ${VOLTA_HOME}/bin
+
+export brew_prefix="$(brew --prefix)"
+
+pg_pkg="postgresql@$(cat .postgresql-version)"
+pg_dir="$(brew --prefix "${pg_pkg}")/bin/"
+
+[[ -d ${pg_dir} ]] && PATH_add "${pg_dir}"
+
+export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
+
+export V8HOME=$(brew --prefix v8@3.15)
+
+export CFLAGS="-Wno-error=implicit-function-declaration -Wno-error=no-compound-token-split-by-macro"
+export CPPFLAGS="$CPPFLAGS -I ${brew_prefix}/include -I ${V8HOME}/include"
+export LDFLAGS="$LDFLAGS -L ${brew_prefix}/lib -L ${V8HOME}/lib"
+
+export RUBY_CPPFLAGS="$CPPFLAGS"
+export RUBY_CFLAGS="$CFLAGS"
[[ -f .envrc.local ]] && source .envrc.local
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..8dc43234
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,9 @@
+# See https://git-scm.com/docs/gitattributes for more about git attribute files.
+
+# Mark the database schema as having been generated.
+db/schema.rb linguist-generated
+
+# Mark any vendored files as having been vendored.
+vendor/* linguist-vendored
+config/credentials/*.yml.enc diff=rails_credentials
+config/credentials.yml.enc diff=rails_credentials
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
deleted file mode 100644
index 49f70f21..00000000
--- a/.github/CODEOWNERS
+++ /dev/null
@@ -1,35 +0,0 @@
-# vim: set ft=codeowners
-#=============================================================================
-# NOTE: for VIM syntax, install https://github.com/rhysd/vim-syntax-codeowners
-#=============================================================================
-#
-# FILE: .github/CODEOWNERS
-#
-# For information on how this file is structured, it's purpose
-# and syntax, please read the following: https://bit.ly/github-codeowners
-#
-# REMEMBER: if you add a new team to this file, make sure that:
-#
-# 1. The team has this repo added to its Repo list
-# 2. The team has write access to this repo
-# 3. The settings for the team have "Code Review" tab
-# and all checkboxes checked, except "Never assign...".
-#
-#=============================================================================
-
-# Code
-* @fnf-org/fnf-engineeres
-
-# Build — keeping them separately for the time we have a dedicated security team
-Dockerfile* @fnf-org/fnf-engineeres
-docker-compose.yml @fnf-org/fnf-engineeres
-/deploy @fnf-org/fnf-engineeres
-
-# Security
-/Gemfile @fnf-org/fnf-engineeres
-/Gemfile.lock @fnf-org/fnf-engineeres
-/config @fnf-org/fnf-engineeres
-/webpack.*.json @fnf-org/fnf-engineeres
-
-# Folks in this team should review all migrations.
-
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..f0527e6b
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,12 @@
+version: 2
+updates:
+- package-ecosystem: bundler
+ directory: "/"
+ schedule:
+ interval: daily
+ open-pull-requests-limit: 10
+- package-ecosystem: github-actions
+ directory: "/"
+ schedule:
+ interval: daily
+ open-pull-requests-limit: 10
diff --git a/.github/workflows/bash.yml b/.github/workflows/bash.yml
index 84f4a689..91a50304 100644
--- a/.github/workflows/bash.yml
+++ b/.github/workflows/bash.yml
@@ -10,11 +10,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Run ShellCheck
- uses: ludeeus/action-shellcheck@master
- with:
- check_together: 'yes'
- scandir: './bin'
- ignore_names: init.sh
+ run: bin/shchk
permissions:
checks: read
contents: read
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
deleted file mode 100644
index 1f345d85..00000000
--- a/.github/workflows/build.yaml
+++ /dev/null
@@ -1,41 +0,0 @@
-name: Create and publish a Docker image
-
-on:
- push:
- branches: [ 'release', 'releases/**']
-
-env:
- REGISTRY: ghcr.io
- IMAGE_NAME: ${{ github.repository }}
-
-jobs:
- build-and-push-image:
- runs-on: ubuntu-latest
- permissions:
- contents: read
- packages: write
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v2
-
- - name: Log in to the Container registry
- uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Extract metadata (tags, labels) for Docker
- id: meta
- uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
- with:
- images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
-
- - name: Build and push Docker image
- uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
- with:
- context: .
- push: true
- tags: ${{ steps.meta.outputs.tags }}
- labels: ${{ steps.meta.outputs.labels }}
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..1b6e5dc1
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,63 @@
+name: Create and publish a Docker image
+
+on:
+ push:
+ branches: [ 'release', 'releases/**']
+ workflow_dispatch:
+ inputs:
+ branch:
+ description: 'Branch to build'
+ required: true
+ default: 'main'
+
+env:
+ REGISTRY: ghcr.io
+ IMAGE_NAME: ${{ github.repository }}
+
+
+jobs:
+ build-and-push-image:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+ with:
+ ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.branch || github.ref }}
+
+ # login-action v3.1.0
+ - name: Log in to the Container registry
+ uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ # metadata-action v5.5.1
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81
+ with:
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+ # In order to support deploying multiple versions that don't stomp on
+ # each other some new tagging rules are created.
+ # The tagging below follows:
+ # - When a properly formatted tag is created, as in a release, put the semver version x.y.z in the tag
+ # - If a build is triggered manually, then mark the branch and short sha in the tag
+ # - When the default branch is updated, create a release candiate tag
+ tags: |
+ type=semver,pattern={{version}}
+ type=sha,prefix={{branch || tag}}-{{sha}},event=workflow_dispatch
+ type=raw,value={{date 'YYYYMMDD-HHmm' tz='America/Los_Angeles'}}-rc,event=branch,branch={{is_default_branch}}
+
+ # build-push-action v5.3.0
+ - name: Build and push Docker image
+ uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0
+ with:
+ context: .
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 00000000..3db492e2
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,69 @@
+---
+name: DeployToGKE
+concurrency: production
+
+on:
+ release:
+ types:
+ - created
+ push:
+ branches:
+ - 'releases/*'
+ tags:
+ - '*-rc'
+ workflow_dispatch:
+ inputs:
+ containerTag:
+ required: true
+ description: 'The docker tag for a container already in GCP'
+
+env:
+ GCP_REGION: us-central1
+ GKE_CLUSTER_NAME: fnf-apps
+
+jobs:
+ deploy:
+ name: TicketBoothDeploy
+ runs-on: ubuntu-latest
+ environment: ${{ vars.ENVIRONMENT }}
+ permissions:
+ contents: 'read'
+ id-token: 'write'
+ steps:
+ # This step is only required because the Helm chart is in this repo
+ - id: checkout
+ name: TicketBooth checkout
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event_name == "workflow_dispatch" && 'main' || github.ref }}
+ fetch-depth: 1
+
+ - id: gcpAuth
+ uses: google-github-actions/auth@v2
+ with:
+ workload_identity_provider: "projects/${{ vars.GCP_PROJECT_ID }}/locations/global/workloadIdentityPools"
+ service_account: ${{ vars.GCP_SERVICE_ACCOUNT_ID }}
+
+ - id: gkeLogin
+ name: GKE Login
+ uses: google-github-actions/get-gke-credentials@v2
+ with:
+ cluster_name: ${{ env.GKE_CLUSTER_NAME }}
+ location: ${{ env.GCP_REGION }}
+
+ - id: runHelm
+ name: Helm Update
+ uses: deliverybot/helm@v1
+ with:
+ release: ${{ vars.HELM_RELEASE }}
+ namespace: default
+ chart: deployment/chart
+ token: ${{ github.token }}
+ values: |
+ image.tag: ${{ github.event_name == "workflow_dispatch" && inputs.containerTag || github.ref_name }}
+ value-files: >-
+ [
+ "deployment/env/base.yaml",
+ "deployment/${{ environment }}.yaml"
+ ]
+ atomic: ${{ github.event_name == "workflow_dispatch" && false || true }}
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 00000000..2931244f
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,26 @@
+# .github/workflows/main.yaml
+name: "TicketBooth CI: Lint"
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ main:
+ name: RuboCop
+ runs-on: ubuntu-latest
+ steps:
+ - run: sudo apt-get update -yqq
+ - run: sudo apt-get install -yqq netcat libpq-dev
+
+ - uses: actions/checkout@v4
+
+ - uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '3.2.3'
+ bundler-cache: true
+
+ - name: RuboCop
+ run: bin/rubocop --parallel
diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml
new file mode 100644
index 00000000..feab2fc9
--- /dev/null
+++ b/.github/workflows/rspec.yml
@@ -0,0 +1,77 @@
+# .github/workflows/main.yaml
+name: "TicketBooth CI: RSpec"
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ main:
+ name: RSpec
+ runs-on: ubuntu-latest
+
+ # If you need DB like MySQL then define service below.
+ # Example for PostgreSQL and Redis can be found here:
+ # https://github.com/actions/example-services/tree/master/.github/workflows
+ services:
+ postgresql:
+ image: postgres
+ env:
+ POSTGRES_USER: "postgres"
+ POSTGRES_PASSWORD: ""
+ POSTGRES_HOST_AUTH_METHOD: "trust"
+ ports:
+ - 5432:5432
+ options: >-
+ --health-cmd pg_isready
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 5
+
+ redis:
+ image: redis
+ ports:
+ - 6379:6379
+ options: >-
+ --health-cmd "redis-cli ping"
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 5
+
+ steps:
+
+ - uses: niden/actions-memcached@v7
+
+ - run: sudo sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
+ - run: wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
+ - run: sudo apt-get update -yqq
+ - run: sudo apt-get install -yqq netcat libpq-dev postgresql-client-16
+
+ - name: "Verify Memcached is running"
+ run: echo -e "stats\nquit" | nc localhost 11211 | grep -e "accepting_conns 1" -q
+
+ - uses: actions/checkout@v4
+
+ - uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '3.2.3'
+ bundler-cache: true
+
+ - shell: bash
+ env:
+ RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
+ run: echo -n "${RAILS_MASTER_KEY}" > config/master.key
+
+ - name: "Check master key"
+ run: cat config/master.key | wc
+
+ - name: "Create and migrate the DB"
+ run: |
+ bin/rails db:create
+ bin/rails db:migrate
+ bin/rails db:test:prepare
+
+ - name: "Run Rspec"
+ run: bundle exec rspec
diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml
deleted file mode 100644
index fdefa3c5..00000000
--- a/.github/workflows/rubocop.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-name: Rubocop
-
-on:
- push:
- branches: [ main ]
- pull_request:
- branches: [ main]
-
-jobs:
- test:
- runs-on: ubuntu-latest
- env:
- CODECOV_TOKEN: ""
- steps:
- - uses: actions/checkout@v2
- - uses: ruby/setup-ruby@v1
- with:
- ruby-version: 2.5.8 # Not needed with a .ruby-version file
- bundler-cache: true # runs 'bundle install' and caches installed gems automatically
-
- - run: |
- gem install bundler --version 1.17.3 -N
- bundle _1.17.3_ install -j 12
- bundle exec rubocop --format progress -P
diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml
deleted file mode 100644
index feb3fdab..00000000
--- a/.github/workflows/ruby.yml
+++ /dev/null
@@ -1,44 +0,0 @@
-
-name: RSpec
-on:
- push:
- branches: [ main ]
- pull_request:
- branches: [ main ]
-
-jobs:
- build:
- runs-on: ubuntu-latest
- services:
- postgres:
- image: postgres:13
- ports:
- - 5432:5432
- env:
- POSTGRES_USER: postgres
- POSTGRES_PASSWORD: postgres
- options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
- redis:
- image: redis
- ports:
- - 6379:6379
- options: --entrypoint redis-server
- steps:
- - uses: actions/checkout@v2
- - uses: ruby/setup-ruby@v1
- with:
- ruby-version: 2.5.8 # Not needed with a .ruby-version file
- bundler-cache: true # runs 'bundle install' and caches installed gems automatically
-
- - run: |
- gem update --system 3.2.3 || true
- gem install bundler --version 1.17.3 -N
- bundle _1.17.3_ install -j 12
-
- - run: |
- bundle exec rake db:create
- bundle exec rake db:migrate
- bundle exec rake db:test:prepare
-
- - run: |
- bundle exec rspec
diff --git a/.gitignore b/.gitignore
index ae9e9c53..a84e78e7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,30 +1,46 @@
-# Ignore all logfiles and tempfiles
+!/.env*.erb
+!/app/assets/builds/.keep
+!/log/.keep
+!/storage/.keep
+!/tmp/.keep
+!/tmp/pids/
+!/tmp/pids/.keep
+!/tmp/storage/
+!/tmp/storage/.keep
**/.DS_Store
+./idea
+.dccache
+.env.secrets
+.envrc.local
.idea
+.make.env
+.viminfo
.vscode
-/log
-/tmp
-
-# Ignore file uploads in development
-/public/uploads
-
-# Ignore secrets
-/ansible/vault-password
-.env.secrets
-
-# Ignore backups
-/backups
+/.bundle
+/.env*
+/.history
/.yard
/.yardoc
+/.yarn/*.gz
+/ansible/vault-password
+/app/assets/builds/*
+/backups
+/config/master.key
/coverage
/doc
-/.history
-/.bundle
+/log/*
+/log/*.log
+/node_modules
/public/assets
+/public/uploads
+/storage/*
+/tmp
+/tmp/*
+/tmp/pids/*
+/tmp/storage/*
/vendor/bundle
-.envrc.local
-.make.env
Downloads
-.dccache
-.viminfo
newrelic_agent.log
+
+/app/assets/builds/*
+!/app/assets/builds/.keep
diff --git a/.node-version b/.node-version
new file mode 100644
index 00000000..bc78e9f2
--- /dev/null
+++ b/.node-version
@@ -0,0 +1 @@
+20.12.1
diff --git a/.postgresql-version b/.postgresql-version
new file mode 100644
index 00000000..b6a7d89c
--- /dev/null
+++ b/.postgresql-version
@@ -0,0 +1 @@
+16
diff --git a/.rspec b/.rspec
index 65e0669b..931d72a0 100644
--- a/.rspec
+++ b/.rspec
@@ -2,4 +2,4 @@
--color
--order rand
--profile 5
---require spec_helper
+--require rails_helper
diff --git a/.rubocop.yml b/.rubocop.yml
index 489bf554..1e3fffce 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -2,111 +2,15 @@ inherit_from: .rubocop_todo.yml
AllCops:
NewCops: enable
-Gemspec/DateAssignment: # new in 1.10
- Enabled: true
-Gemspec/RequireMFA: # new in 1.23
- Enabled: true
-Layout/LineEndStringConcatenationIndentation: # new in 1.18
- Enabled: true
-Layout/SpaceBeforeBrackets: # new in 1.7
- Enabled: true
-Lint/AmbiguousAssignment: # new in 1.7
- Enabled: true
-Lint/AmbiguousOperatorPrecedence: # new in 1.21
- Enabled: true
-Lint/AmbiguousRange: # new in 1.19
- Enabled: true
-Lint/DeprecatedConstants: # new in 1.8
- Enabled: true
-Lint/DuplicateBranch: # new in 1.3
- Enabled: true
-Lint/DuplicateRegexpCharacterClassElement: # new in 1.1
- Enabled: true
-Lint/EmptyBlock: # new in 1.1
- Enabled: true
-Lint/EmptyClass: # new in 1.3
- Enabled: true
-Lint/EmptyInPattern: # new in 1.16
- Enabled: true
-Lint/IncompatibleIoSelectWithFiberScheduler: # new in 1.21
- Enabled: true
-Lint/LambdaWithoutLiteralBlock: # new in 1.8
- Enabled: true
-Lint/NoReturnInBeginEndBlocks: # new in 1.2
- Enabled: true
-Lint/NumberedParameterAssignment: # new in 1.9
- Enabled: true
-Lint/OrAssignmentToConstant: # new in 1.9
- Enabled: true
-Lint/RedundantDirGlobSort: # new in 1.8
- Enabled: true
-Lint/RefinementImportMethods: # new in 1.27
- Enabled: true
-Lint/RequireRelativeSelfPath: # new in 1.22
- Enabled: true
-Lint/SymbolConversion: # new in 1.9
- Enabled: true
-Lint/ToEnumArguments: # new in 1.1
- Enabled: true
-Lint/TripleQuotes: # new in 1.9
- Enabled: true
-Lint/UnexpectedBlockArity: # new in 1.5
- Enabled: true
-Lint/UnmodifiedReduceAccumulator: # new in 1.1
- Enabled: true
-Lint/UselessRuby2Keywords: # new in 1.23
- Enabled: true
-Naming/BlockForwarding: # new in 1.24
- Enabled: true
-Security/IoMethods: # new in 1.22
- Enabled: true
-Style/ArgumentsForwarding: # new in 1.1
- Enabled: true
-Style/CollectionCompact: # new in 1.2
- Enabled: true
-Style/DocumentDynamicEvalDefinition: # new in 1.1
- Enabled: true
-Style/EndlessMethod: # new in 1.8
- Enabled: true
-Style/FileRead: # new in 1.24
- Enabled: true
-Style/FileWrite: # new in 1.24
- Enabled: true
-Style/HashConversion: # new in 1.10
- Enabled: true
-Style/HashExcept: # new in 1.7
- Enabled: true
-Style/IfWithBooleanLiteralBranches: # new in 1.9
- Enabled: true
-Style/InPatternThen: # new in 1.16
- Enabled: true
-Style/MapToHash: # new in 1.24
- Enabled: true
-Style/MultilineInPatternThen: # new in 1.16
- Enabled: true
-Style/NegatedIfElseCondition: # new in 1.2
- Enabled: true
-Style/NestedFileDirname: # new in 1.26
- Enabled: true
-Style/NilLambda: # new in 1.3
- Enabled: true
-Style/NumberedParameters: # new in 1.22
- Enabled: true
-Style/NumberedParametersLimit: # new in 1.22
- Enabled: true
-Style/OpenStructUse: # new in 1.23
- Enabled: true
-Style/QuotedSymbols: # new in 1.16
- Enabled: true
-Style/RedundantArgument: # new in 1.4
- Enabled: true
-Style/RedundantInitialize: # new in 1.27
- Enabled: true
-Style/RedundantSelfAssignmentBranch: # new in 1.19
- Enabled: true
-Style/SelectByRegexp: # new in 1.22
- Enabled: true
-Style/StringChars: # new in 1.12
- Enabled: true
-Style/SwapValues: # new in 1.1
- Enabled: true
+require:
+ - rubocop-factory_bot
+ - rubocop-rails
+ - rubocop-rake
+ - rubocop-rspec
+ - rubocop-rspec_rails
+
+Style/IfUnlessModifier:
+ Enabled: false
+
+RSpecRails/InferredSpecType:
+ Enabled: false
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index ceaf9ed4..36c90d96 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -1,17 +1,17 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
-# on 2022-05-02 03:13:36 UTC using RuboCop version 1.27.0.
+# on 2024-04-16 10:59:33 UTC using RuboCop version 1.63.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
-# Offense count: 2
+# Offense count: 1
+# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: AllowSafeAssignment.
Lint/AssignmentInCondition:
Exclude:
- 'app/controllers/passwords_controller.rb'
- - 'app/controllers/ticket_requests_controller.rb'
# Offense count: 1
Lint/ShadowingOuterLocalVariable:
@@ -23,78 +23,226 @@ Lint/UnmodifiedReduceAccumulator:
Exclude:
- 'app/helpers/shifts_helper.rb'
-# Offense count: 1
-Lint/UselessAssignment:
- Exclude:
- - 'config/unicorn.rb'
-
-# Offense count: 20
-# Configuration parameters: IgnoredMethods, CountRepeatedAttributes.
+# Offense count: 27
+# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize:
- Max: 62
+ Max: 75
-# Offense count: 21
-# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
-# IgnoredMethods: refine
+# Offense count: 6
+# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
+# AllowedMethods: refine
Metrics/BlockLength:
- Max: 255
+ Max: 47
# Offense count: 2
# Configuration parameters: CountComments, CountAsOne.
Metrics/ClassLength:
- Max: 157
+ Max: 148
-# Offense count: 4
-# Configuration parameters: IgnoredMethods.
+# Offense count: 10
+# Configuration parameters: AllowedMethods, AllowedPatterns.
Metrics/CyclomaticComplexity:
Max: 13
-# Offense count: 25
-# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
+# Offense count: 36
+# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
Metrics/MethodLength:
- Max: 59
+ Max: 58
# Offense count: 1
# Configuration parameters: CountComments, CountAsOne.
Metrics/ModuleLength:
Max: 114
-# Offense count: 3
-# Configuration parameters: IgnoredMethods.
+# Offense count: 8
+# Configuration parameters: AllowedMethods, AllowedPatterns.
Metrics/PerceivedComplexity:
Max: 15
-# Offense count: 6
+# Offense count: 3
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
-# AllowedNames: at, by, db, id, in, io, ip, of, on, os, pp, to
+# AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to
Naming/MethodParameterName:
Exclude:
- - 'spec/support/factory_bot.rb'
- 'spec/support/time_extensions.rb'
-# Offense count: 74
+# Offense count: 19
+# Configuration parameters: Prefixes, AllowedPatterns.
+# Prefixes: when, with, without
+RSpec/ContextWording:
+ Exclude:
+ - 'spec/controllers/events_controller_spec.rb'
+ - 'spec/models/event_spec.rb'
+ - 'spec/models/ticket_request_spec.rb'
+
+# Offense count: 1
+# Configuration parameters: IgnoredMetadata.
+RSpec/DescribeClass:
+ Exclude:
+ - 'spec/lib/fnf/music_submissions_spec.rb'
+
+# Offense count: 12
+# Configuration parameters: AllowSubject.
+RSpec/MultipleMemoizedHelpers:
+ Max: 12
+
+# Offense count: 2
+# Configuration parameters: EnforcedStyle, IgnoreSharedExamples.
+# SupportedStyles: always, named_only
+RSpec/NamedSubject:
+ Exclude:
+ - 'spec/lib/fnf/csv_reader_spec.rb'
+ - 'spec/models/ticket_request_spec.rb'
+
+# Offense count: 71
+# Configuration parameters: AllowedGroups.
+RSpec/NestedGroups:
+ Max: 6
+
+# Offense count: 14
+# Configuration parameters: AllowedPatterns.
+# AllowedPatterns: ^expect_, ^assert_
+RSpec/NoExpectationExample:
+ Exclude:
+ - 'spec/controllers/events_controller_spec.rb'
+ - 'spec/models/ticket_request_spec.rb'
+ - 'spec/models/user_spec.rb'
+
+# Offense count: 2
+RSpec/RepeatedExampleGroupBody:
+ Exclude:
+ - 'spec/models/event_spec.rb'
+
+# Offense count: 9
+# Configuration parameters: Database, Include.
+# SupportedDatabases: mysql, postgresql
+# Include: db/**/*.rb
+Rails/BulkChangeTable:
+ Exclude:
+ - 'db/migrate/20130226221916_add_user_to_ticket_request.rb'
+ - 'db/migrate/20130325024448_add_ticket_cost_info_to_event.rb'
+ - 'db/migrate/20130803212458_add_start_and_end_ticket_sale_times_to_events.rb'
+ - 'db/migrate/20140428045329_add_role_to_ticket_requests.rb'
+ - 'db/migrate/20140605045004_extract_address_into_multiple_fields.rb'
+ - 'db/migrate/20140616030905_change_camping_type_on_ticket_requests.rb'
+ - 'db/migrate/20160611234315_add_eald_columns.rb'
+
+# Offense count: 3
+# Configuration parameters: Include.
+# Include: app/models/**/*.rb
+Rails/HasManyOrHasOneDependent:
+ Exclude:
+ - 'app/models/event.rb'
+ - 'app/models/ticket_request.rb'
+ - 'app/models/user.rb'
+
+# Offense count: 3
+# Configuration parameters: Include.
+# Include: app/helpers/**/*.rb
+Rails/HelperInstanceVariable:
+ Exclude:
+ - 'app/helpers/shifts_helper.rb'
+
+# Offense count: 16
+Rails/I18nLocaleTexts:
+ Exclude:
+ - 'app/controllers/events_controller.rb'
+ - 'app/controllers/home_controller.rb'
+ - 'app/controllers/jobs_controller.rb'
+ - 'app/controllers/passwords_controller.rb'
+ - 'app/controllers/payments_controller.rb'
+ - 'app/controllers/shifts_controller.rb'
+ - 'app/controllers/ticket_requests_controller.rb'
+ - 'app/models/event_admin.rb'
+ - 'app/models/payment.rb'
+ - 'app/models/site_admin.rb'
+ - 'app/models/user.rb'
+
+# Offense count: 1
+# Configuration parameters: Database, Include.
+# SupportedDatabases: mysql
+# Include: db/**/*.rb
+Rails/NotNullColumn:
+ Exclude:
+ - 'db/migrate/20130311213508_add_event_id_to_ticket_request.rb'
+
+# Offense count: 9
+# Configuration parameters: Include.
+# Include: db/**/*.rb
+Rails/ReversibleMigration:
+ Exclude:
+ - 'db/migrate/20130226221916_add_user_to_ticket_request.rb'
+ - 'db/migrate/20140515053804_remove_performer_from_ticket_requests.rb'
+ - 'db/migrate/20140605045004_extract_address_into_multiple_fields.rb'
+ - 'db/migrate/20140605052627_remove_volunteer_shifts_from_ticket_requests.rb'
+ - 'db/migrate/20140605053705_remove_ask_how_many_shifts_from_events.rb'
+ - 'db/migrate/20140605060026_add_camping_type_to_ticket_requests.rb'
+ - 'db/migrate/20140616030905_change_camping_type_on_ticket_requests.rb'
+
+# Offense count: 3
+# Configuration parameters: ForbiddenMethods, AllowedMethods.
+# ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all
+Rails/SkipsModelValidations:
+ Exclude:
+ - 'app/controllers/application_controller.rb'
+ - 'app/controllers/ticket_requests_controller.rb'
+ - 'app/models/user.rb'
+
+# Offense count: 3
+# Configuration parameters: Include.
+# Include: db/**/*.rb
+Rails/ThreeStateBooleanColumn:
+ Exclude:
+ - 'db/migrate/20140515054433_add_vehicle_camping_requested_to_ticket_requests.rb'
+ - 'db/migrate/20140616030905_change_camping_type_on_ticket_requests.rb'
+ - 'db/migrate/20150609064608_add_agrees_terms_to_ticket_requests.rb'
+
+# Offense count: 1
+# Configuration parameters: TransactionMethods.
+Rails/TransactionExitStatement:
+ Exclude:
+ - 'app/models/ticket_request.rb'
+
+# Offense count: 2
+# Configuration parameters: Include.
+# Include: app/models/**/*.rb
+Rails/UniqueValidationWithoutIndex:
+ Exclude:
+ - 'app/models/payment.rb'
+ - 'app/models/site_admin.rb'
+
+# Offense count: 2
+# This cop supports safe autocorrection (--autocorrect).
+Rake/Desc:
+ Exclude:
+ - 'Rakefile'
+
+# Offense count: 81
# Configuration parameters: AllowedConstants.
Style/Documentation:
Enabled: false
-# Offense count: 5
-# Configuration parameters: MinBodyLength.
+# Offense count: 1
+# This cop supports unsafe autocorrection (--autocorrect-all).
+# Configuration parameters: EnforcedStyle.
+# SupportedStyles: always, always_true, never
+Style/FrozenStringLiteralComment:
+ Exclude:
+ - 'app/models/application_record.rb'
+
+# Offense count: 6
+# This cop supports safe autocorrection (--autocorrect).
+# Configuration parameters: MinBodyLength, AllowConsecutiveConditionals.
Style/GuardClause:
Exclude:
- 'app/controllers/application_controller.rb'
- - 'app/controllers/home_controller.rb'
- 'app/controllers/payments_controller.rb'
- 'app/controllers/ticket_requests_controller.rb'
- 'app/models/event.rb'
-# Offense count: 1
-Style/MissingRespondToMissing:
- Exclude:
- - 'spec/support/factory_bot.rb'
-
-# Offense count: 3
-# This cop supports safe auto-correction (--auto-correct).
-# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
+# Offense count: 9
+# This cop supports safe autocorrection (--autocorrect).
+# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
# URISchemes: http, https
Layout/LineLength:
- Max: 174
+ Max: 160
diff --git a/.ruby-version b/.ruby-version
index 30f69e8c..b347b11e 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.5.9
+3.2.3
diff --git a/.yarnrc.yml b/.yarnrc.yml
new file mode 100644
index 00000000..3186f3f0
--- /dev/null
+++ b/.yarnrc.yml
@@ -0,0 +1 @@
+nodeLinker: node-modules
diff --git a/Brewfile b/Brewfile
new file mode 100644
index 00000000..b3ccf977
--- /dev/null
+++ b/Brewfile
@@ -0,0 +1,121 @@
+# frozen_string_literal: true
+
+# vim: ft=ruby
+
+tap 'homebrew/bundle'
+tap 'homebrew/services'
+tap 'heroku/brew'
+
+# heroku CLI
+brew 'heroku'
+
+# Build Environment/C/C++
+brew 'autoconf'
+brew 'automake'
+brew 'bash-completion@2'
+brew 'bat'
+brew 'cmake'
+brew 'coreutils'
+brew 'direnv'
+brew 'fzf'
+brew 'gcc'
+brew 'glances'
+brew 'gnutls'
+brew 'gnu-indent'
+brew 'gnu-sed'
+brew 'gnu-tar'
+brew 'gnu-time'
+brew 'gnu-which'
+brew 'gpg2'
+brew 'jemalloc'
+brew 'jq'
+brew 'libffi'
+brew 'libmemcached'
+brew 'libpq'
+brew 'libtiff'
+brew 'libtool'
+brew 'libxml2'
+brew 'libxslt'
+brew 'libyaml'
+brew 'libzip'
+brew 'make'
+brew 'memcached'
+brew 'p7zip'
+brew 'parallel'
+brew 'pcre'
+brew 'pcre'
+brew 'pg_top'
+brew 'pkg-config'
+brew 'postgresql@16'
+brew 'pstree'
+brew 'pyenv'
+brew 'rbenv'
+brew 'readline'
+brew 'redis'
+brew 'ripgrep'
+brew 'rsync'
+brew 'ruby-build'
+brew 'ruby-completion'
+brew 'shellcheck'
+brew 'stripe/stripe-mock/stripe-mock'
+brew 'tree'
+brew 'vim'
+brew 'watch'
+brew 'wget'
+brew 'yamlfmt'
+brew 'yamllint'
+brew 'ydiff'
+brew 'yq'
+brew 'zlib'
+brew 'volta'
+brew 'v8'
+
+# k8s
+brew 'minikube'
+brew 'kubernetes-cli'
+
+cask 'chromedriver'
+cask 'github'
+
+cask 'font-andale-mono'
+cask 'font-anonymice-powerline'
+cask 'font-arvo'
+cask 'font-aurulent-sans-mono-nerd-font'
+cask 'font-awesome-terminal-fonts'
+cask 'font-bitstream-vera-sans-mono-nerd-font'
+cask 'font-blex-mono-nerd-font'
+cask 'font-cairo'
+cask 'font-cascadia-mono'
+cask 'font-consolas-for-powerline'
+cask 'font-cousine'
+cask 'font-cutive-mono'
+cask 'font-d2coding'
+cask 'font-dejavu-sans-mono-for-powerline'
+cask 'font-everson-mono'
+cask 'font-fantasque-sans-mono'
+cask 'font-fontawesome'
+cask 'font-ia-writer-mono'
+cask 'font-inconsolata'
+cask 'font-jetbrains-mono'
+cask 'font-kawkab-mono'
+cask 'font-lekton-nerd-font'
+cask 'font-menlo-for-powerline'
+cask 'font-meslo-for-powerline'
+cask 'font-miriam-mono-clm'
+cask 'font-monoid'
+cask 'font-monoisome'
+cask 'font-mononoki'
+cask 'font-monoton'
+cask 'font-noto-mono'
+cask 'font-oswald'
+cask 'font-oxygen'
+cask 'font-oxygen-mono'
+cask 'font-powerline-symbols'
+cask 'font-pt-mono'
+cask 'font-roboto'
+cask 'font-roboto-mono-nerd-font'
+cask 'font-share-tech-mono'
+cask 'font-sometype-mono'
+cask 'font-titillium'
+cask 'font-ubuntu'
+cask 'font-victor-mono'
diff --git a/Brewfile.lock.json b/Brewfile.lock.json
new file mode 100644
index 00000000..d78951c2
--- /dev/null
+++ b/Brewfile.lock.json
@@ -0,0 +1,3036 @@
+{
+ "entries": {
+ "tap": {
+ "homebrew/bundle": {
+ "revision": "40ab9cfe3e5e59b0b690b5afd0dd59288d407551"
+ },
+ "homebrew/services": {
+ "revision": "a6fcf4f1ce20c6e01a0ff051074e7e9d30a887e1"
+ },
+ "heroku/brew": {
+ "revision": "379bc49cf928a18cb0e7dcad4946278ff831e420"
+ }
+ },
+ "brew": {
+ "heroku": {
+ "version": "8.5.0",
+ "bottle": false
+ },
+ "autoconf": {
+ "version": "2.72",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/autoconf/blobs/sha256:bb39057e87dd9cb940bee15ff5b5172952a0350e59b14a6f55b8006a07813186",
+ "sha256": "bb39057e87dd9cb940bee15ff5b5172952a0350e59b14a6f55b8006a07813186"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/autoconf/blobs/sha256:bb39057e87dd9cb940bee15ff5b5172952a0350e59b14a6f55b8006a07813186",
+ "sha256": "bb39057e87dd9cb940bee15ff5b5172952a0350e59b14a6f55b8006a07813186"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/autoconf/blobs/sha256:bb39057e87dd9cb940bee15ff5b5172952a0350e59b14a6f55b8006a07813186",
+ "sha256": "bb39057e87dd9cb940bee15ff5b5172952a0350e59b14a6f55b8006a07813186"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/autoconf/blobs/sha256:12368e33b89d221550ba9e261b0c6ece0b0e89250fb4c95169d09081e0ebb2dd",
+ "sha256": "12368e33b89d221550ba9e261b0c6ece0b0e89250fb4c95169d09081e0ebb2dd"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/autoconf/blobs/sha256:12368e33b89d221550ba9e261b0c6ece0b0e89250fb4c95169d09081e0ebb2dd",
+ "sha256": "12368e33b89d221550ba9e261b0c6ece0b0e89250fb4c95169d09081e0ebb2dd"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/autoconf/blobs/sha256:12368e33b89d221550ba9e261b0c6ece0b0e89250fb4c95169d09081e0ebb2dd",
+ "sha256": "12368e33b89d221550ba9e261b0c6ece0b0e89250fb4c95169d09081e0ebb2dd"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/autoconf/blobs/sha256:d760774b6bcad93822e666a8a2ee8557f674eba7f784b9ab9e397313378474b8",
+ "sha256": "d760774b6bcad93822e666a8a2ee8557f674eba7f784b9ab9e397313378474b8"
+ }
+ }
+ }
+ },
+ "automake": {
+ "version": "1.16.5",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/automake/blobs/sha256:7a116fd1052d95a13b40837b85a34cca35dbae9ed2864d1db92abfa960176b55",
+ "sha256": "7a116fd1052d95a13b40837b85a34cca35dbae9ed2864d1db92abfa960176b55"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/automake/blobs/sha256:f68481d06be7fa3f0a0881edb825a336e7f6548191c762d68bd817183b238f5a",
+ "sha256": "f68481d06be7fa3f0a0881edb825a336e7f6548191c762d68bd817183b238f5a"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/automake/blobs/sha256:f68481d06be7fa3f0a0881edb825a336e7f6548191c762d68bd817183b238f5a",
+ "sha256": "f68481d06be7fa3f0a0881edb825a336e7f6548191c762d68bd817183b238f5a"
+ },
+ "arm64_big_sur": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/automake/blobs/sha256:f68481d06be7fa3f0a0881edb825a336e7f6548191c762d68bd817183b238f5a",
+ "sha256": "f68481d06be7fa3f0a0881edb825a336e7f6548191c762d68bd817183b238f5a"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/automake/blobs/sha256:4f3ecdf86b3a0302f64b848440b0595095face19a0b9778498e5e64d022c1a81",
+ "sha256": "4f3ecdf86b3a0302f64b848440b0595095face19a0b9778498e5e64d022c1a81"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/automake/blobs/sha256:ae77a247a13ea860236a29b02769f5327395f712413f694d8a8d20cb6c21332d",
+ "sha256": "ae77a247a13ea860236a29b02769f5327395f712413f694d8a8d20cb6c21332d"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/automake/blobs/sha256:ae77a247a13ea860236a29b02769f5327395f712413f694d8a8d20cb6c21332d",
+ "sha256": "ae77a247a13ea860236a29b02769f5327395f712413f694d8a8d20cb6c21332d"
+ },
+ "big_sur": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/automake/blobs/sha256:ae77a247a13ea860236a29b02769f5327395f712413f694d8a8d20cb6c21332d",
+ "sha256": "ae77a247a13ea860236a29b02769f5327395f712413f694d8a8d20cb6c21332d"
+ },
+ "catalina": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/automake/blobs/sha256:ae77a247a13ea860236a29b02769f5327395f712413f694d8a8d20cb6c21332d",
+ "sha256": "ae77a247a13ea860236a29b02769f5327395f712413f694d8a8d20cb6c21332d"
+ },
+ "mojave": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/automake/blobs/sha256:ae77a247a13ea860236a29b02769f5327395f712413f694d8a8d20cb6c21332d",
+ "sha256": "ae77a247a13ea860236a29b02769f5327395f712413f694d8a8d20cb6c21332d"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/automake/blobs/sha256:59808c20f7dc565f106b432941b43c52f3d7f46a8d562ab27a4aabd424783158",
+ "sha256": "59808c20f7dc565f106b432941b43c52f3d7f46a8d562ab27a4aabd424783158"
+ }
+ }
+ }
+ },
+ "bash-completion@2": {
+ "version": "2.13.0",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/bash-completion/2/blobs/sha256:fb3f1f447d93f7f6b18e196cd735e94a2e8157f33de2e60f4d3d687de79a2e2c",
+ "sha256": "fb3f1f447d93f7f6b18e196cd735e94a2e8157f33de2e60f4d3d687de79a2e2c"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/bash-completion/2/blobs/sha256:fb3f1f447d93f7f6b18e196cd735e94a2e8157f33de2e60f4d3d687de79a2e2c",
+ "sha256": "fb3f1f447d93f7f6b18e196cd735e94a2e8157f33de2e60f4d3d687de79a2e2c"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/bash-completion/2/blobs/sha256:fb3f1f447d93f7f6b18e196cd735e94a2e8157f33de2e60f4d3d687de79a2e2c",
+ "sha256": "fb3f1f447d93f7f6b18e196cd735e94a2e8157f33de2e60f4d3d687de79a2e2c"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/bash-completion/2/blobs/sha256:5e29be6b53324aa6c5fa8d0e58a3f89c3e33577b0c387fe512c9417657619f0b",
+ "sha256": "5e29be6b53324aa6c5fa8d0e58a3f89c3e33577b0c387fe512c9417657619f0b"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/bash-completion/2/blobs/sha256:5e29be6b53324aa6c5fa8d0e58a3f89c3e33577b0c387fe512c9417657619f0b",
+ "sha256": "5e29be6b53324aa6c5fa8d0e58a3f89c3e33577b0c387fe512c9417657619f0b"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/bash-completion/2/blobs/sha256:5e29be6b53324aa6c5fa8d0e58a3f89c3e33577b0c387fe512c9417657619f0b",
+ "sha256": "5e29be6b53324aa6c5fa8d0e58a3f89c3e33577b0c387fe512c9417657619f0b"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/bash-completion/2/blobs/sha256:f573a3d9d0870b241f9c6f80d7e608da947a2e06d0137c99ad90f6b48761181b",
+ "sha256": "f573a3d9d0870b241f9c6f80d7e608da947a2e06d0137c99ad90f6b48761181b"
+ }
+ }
+ }
+ },
+ "bat": {
+ "version": "0.24.0",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/bat/blobs/sha256:66f03028e55d7a9ce344c7910b8469e16c0acd812ebc2886cdff8c10f9cf34c4",
+ "sha256": "66f03028e55d7a9ce344c7910b8469e16c0acd812ebc2886cdff8c10f9cf34c4"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/bat/blobs/sha256:b36dd52fda8441a5b9c83f0914b4f362c8caa9c6a1143b1ee2c7f54941b8ed6b",
+ "sha256": "b36dd52fda8441a5b9c83f0914b4f362c8caa9c6a1143b1ee2c7f54941b8ed6b"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/bat/blobs/sha256:0a7454b37d7b095de1006996ceb43a585ca05339c2f540dde1703202b139695d",
+ "sha256": "0a7454b37d7b095de1006996ceb43a585ca05339c2f540dde1703202b139695d"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/bat/blobs/sha256:58769b8c6b1380e9d066586bf8f678993457ef9ea449c3d4d7955461018d3b49",
+ "sha256": "58769b8c6b1380e9d066586bf8f678993457ef9ea449c3d4d7955461018d3b49"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/bat/blobs/sha256:d6e91c86547c67292cb6abf92fac7f9c6272bf6bca5483466e3e9adc744ce1c0",
+ "sha256": "d6e91c86547c67292cb6abf92fac7f9c6272bf6bca5483466e3e9adc744ce1c0"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/bat/blobs/sha256:eb2c932132331cb87e5cace268b034e32c3a4741fccd42813cf853269e3a9c21",
+ "sha256": "eb2c932132331cb87e5cace268b034e32c3a4741fccd42813cf853269e3a9c21"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/bat/blobs/sha256:0ae5db045ded8528d1588d703d62d6be481ebe006888c7e29f7e178b07e0e926",
+ "sha256": "0ae5db045ded8528d1588d703d62d6be481ebe006888c7e29f7e178b07e0e926"
+ }
+ }
+ }
+ },
+ "cmake": {
+ "version": "3.29.1",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/cmake/blobs/sha256:8d9b7d484d371d2bb66452ebadd31034708c553ddcadab8097ed9911e2bbae31",
+ "sha256": "8d9b7d484d371d2bb66452ebadd31034708c553ddcadab8097ed9911e2bbae31"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/cmake/blobs/sha256:3cd076060f01246d42bbc507192d466d05b6cc430a0680eb6308b1c4c4cfb88e",
+ "sha256": "3cd076060f01246d42bbc507192d466d05b6cc430a0680eb6308b1c4c4cfb88e"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/cmake/blobs/sha256:262ae87f45fbf8f007e6220f64969dbab76f982a484c9d412ddcf3bc0917ffa4",
+ "sha256": "262ae87f45fbf8f007e6220f64969dbab76f982a484c9d412ddcf3bc0917ffa4"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/cmake/blobs/sha256:04631ef4e4292c1315e12ad2dfb1ebb022e3677b908778278480e56a8e0140ac",
+ "sha256": "04631ef4e4292c1315e12ad2dfb1ebb022e3677b908778278480e56a8e0140ac"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/cmake/blobs/sha256:ba061351890b139f712d48e916515b4a1baf16b1efb2feb8e5a63b63e22f6d47",
+ "sha256": "ba061351890b139f712d48e916515b4a1baf16b1efb2feb8e5a63b63e22f6d47"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/cmake/blobs/sha256:042ac0a646359b5c6cafdacd066fa59cc5b99cac3130586836a44063450a857d",
+ "sha256": "042ac0a646359b5c6cafdacd066fa59cc5b99cac3130586836a44063450a857d"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/cmake/blobs/sha256:3f253af3f526668a48602648b8461613e07c8388f1366211186c036602ca60df",
+ "sha256": "3f253af3f526668a48602648b8461613e07c8388f1366211186c036602ca60df"
+ }
+ }
+ }
+ },
+ "coreutils": {
+ "version": "9.5",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/coreutils/blobs/sha256:b2c643420d7d9de89385d86e0c3f5e9f9ae2404ce378db574dabbfce3ca37a91",
+ "sha256": "b2c643420d7d9de89385d86e0c3f5e9f9ae2404ce378db574dabbfce3ca37a91"
+ },
+ "arm64_ventura": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/coreutils/blobs/sha256:0f889fb75ebc8e96aa1f38aff6ed1bc7e87c45b70f7644c7e1492f1f9480f352",
+ "sha256": "0f889fb75ebc8e96aa1f38aff6ed1bc7e87c45b70f7644c7e1492f1f9480f352"
+ },
+ "arm64_monterey": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/coreutils/blobs/sha256:43bb62929309c51bb600e0d156b107ef147094445b29ada1387c222d9a2465c4",
+ "sha256": "43bb62929309c51bb600e0d156b107ef147094445b29ada1387c222d9a2465c4"
+ },
+ "sonoma": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/coreutils/blobs/sha256:19eccdcccfcacd67000acf89e3261174dfe30b0a764d10ccc39be82a4b37c0a5",
+ "sha256": "19eccdcccfcacd67000acf89e3261174dfe30b0a764d10ccc39be82a4b37c0a5"
+ },
+ "ventura": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/coreutils/blobs/sha256:7c8c3c6eab6032c379bb7266bf78e25b3b3d38d167c4eee92a7c023b131b86e0",
+ "sha256": "7c8c3c6eab6032c379bb7266bf78e25b3b3d38d167c4eee92a7c023b131b86e0"
+ },
+ "monterey": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/coreutils/blobs/sha256:44ce33f1d4d73b54bf312f48c9d93bd7a186f4ce1adc004c9f3168da004eee6c",
+ "sha256": "44ce33f1d4d73b54bf312f48c9d93bd7a186f4ce1adc004c9f3168da004eee6c"
+ },
+ "x86_64_linux": {
+ "cellar": "/home/linuxbrew/.linuxbrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/coreutils/blobs/sha256:e48884f502b3236e747b1280d5373d058b4bb47f872c99533d90ba2e730f3266",
+ "sha256": "e48884f502b3236e747b1280d5373d058b4bb47f872c99533d90ba2e730f3266"
+ }
+ }
+ }
+ },
+ "direnv": {
+ "version": "2.34.0",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:fd210e16bd6764b33cd2e556a7f07ed579453ba19d518ec11de33edcf3c5c2c7",
+ "sha256": "fd210e16bd6764b33cd2e556a7f07ed579453ba19d518ec11de33edcf3c5c2c7"
+ },
+ "arm64_ventura": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:59af7e0d05a50eda59d60a8c2c67eb0a3491c0650a334568ae13988da3b32951",
+ "sha256": "59af7e0d05a50eda59d60a8c2c67eb0a3491c0650a334568ae13988da3b32951"
+ },
+ "arm64_monterey": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:2577f8c5e2c3c7d1ee2f6966e3c92a16853edb9302d78089ddfc4f8ef9efda24",
+ "sha256": "2577f8c5e2c3c7d1ee2f6966e3c92a16853edb9302d78089ddfc4f8ef9efda24"
+ },
+ "sonoma": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:4148bce1352772af61eb44303877e57e54a8531240cb551ec2c879660ac90c54",
+ "sha256": "4148bce1352772af61eb44303877e57e54a8531240cb551ec2c879660ac90c54"
+ },
+ "ventura": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:b4eefec1b63c6c32713290af5f5e1f2c318d3c64ba052aab786aab0b87c1b437",
+ "sha256": "b4eefec1b63c6c32713290af5f5e1f2c318d3c64ba052aab786aab0b87c1b437"
+ },
+ "monterey": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:41cadfe20ab1913f07376ac5206ee49c3322ac8689ecd9a5dc85c5146850dff2",
+ "sha256": "41cadfe20ab1913f07376ac5206ee49c3322ac8689ecd9a5dc85c5146850dff2"
+ },
+ "x86_64_linux": {
+ "cellar": "/home/linuxbrew/.linuxbrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:be4b933f8f607bf1a705c13abe75d04a99856f1698c3ebcb71e07e469850e964",
+ "sha256": "be4b933f8f607bf1a705c13abe75d04a99856f1698c3ebcb71e07e469850e964"
+ }
+ }
+ }
+ },
+ "fzf": {
+ "version": "0.49.0",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/fzf/blobs/sha256:aa0ffe3f66ba44c9abe384f30a6a1fd1945a02fbf3ebdd28de772da69b182678",
+ "sha256": "aa0ffe3f66ba44c9abe384f30a6a1fd1945a02fbf3ebdd28de772da69b182678"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/fzf/blobs/sha256:00d9f8ce734a571b6e738311c31f2a4ebc8e6f9d766392246741e577a74e63b1",
+ "sha256": "00d9f8ce734a571b6e738311c31f2a4ebc8e6f9d766392246741e577a74e63b1"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/fzf/blobs/sha256:601b706d130821ad7cc41c914336da06a2e4d7295a9b8347d535b150600210ad",
+ "sha256": "601b706d130821ad7cc41c914336da06a2e4d7295a9b8347d535b150600210ad"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/fzf/blobs/sha256:319d7e7c9d886e9f5f8f59169e1cc51674a1c2b53f33cf38cc42c2127acba2e5",
+ "sha256": "319d7e7c9d886e9f5f8f59169e1cc51674a1c2b53f33cf38cc42c2127acba2e5"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/fzf/blobs/sha256:b388f737963853aa89d7cf5c88bdb95cd316a13d5f4d9216e891ebd845e7fcf2",
+ "sha256": "b388f737963853aa89d7cf5c88bdb95cd316a13d5f4d9216e891ebd845e7fcf2"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/fzf/blobs/sha256:c28898f6057b4ba0b99d3bb18c667fa5276cf6168802bb7fe2d87980effaed22",
+ "sha256": "c28898f6057b4ba0b99d3bb18c667fa5276cf6168802bb7fe2d87980effaed22"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/fzf/blobs/sha256:e95cd810ccec9cdd85942514cacdeacaf38015c3af55d48a9c215abc4f876f6e",
+ "sha256": "e95cd810ccec9cdd85942514cacdeacaf38015c3af55d48a9c215abc4f876f6e"
+ }
+ }
+ }
+ },
+ "gcc": {
+ "version": "13.2.0",
+ "bottle": {
+ "rebuild": 2,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:85037a5e7d463f55d9a0ff3963b24008c8a10937d137909bd6e91cf64ddfe8b6",
+ "sha256": "85037a5e7d463f55d9a0ff3963b24008c8a10937d137909bd6e91cf64ddfe8b6"
+ },
+ "arm64_ventura": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:38c7d0503b0a99dddaefe5a1512e927cb3976927c2b1882e5519501bdf1e9015",
+ "sha256": "38c7d0503b0a99dddaefe5a1512e927cb3976927c2b1882e5519501bdf1e9015"
+ },
+ "arm64_monterey": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:026a25661c70e7c0ca6a33afeb406c9b76fd87b93396a1bc2e94aa10ba0801e3",
+ "sha256": "026a25661c70e7c0ca6a33afeb406c9b76fd87b93396a1bc2e94aa10ba0801e3"
+ },
+ "sonoma": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:e93cce391ed5d2898d3186403e7256d997d03855a72e9cb0c85067fd7825cf13",
+ "sha256": "e93cce391ed5d2898d3186403e7256d997d03855a72e9cb0c85067fd7825cf13"
+ },
+ "ventura": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:29f3443225b387ae5542aeee0a941fa9af1c91da44f27101735f510bdfc3a11b",
+ "sha256": "29f3443225b387ae5542aeee0a941fa9af1c91da44f27101735f510bdfc3a11b"
+ },
+ "monterey": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:52f6401306f6facb4b2005ca6d1c8e02592ef50e26922d9f5cc2a75b00703a0f",
+ "sha256": "52f6401306f6facb4b2005ca6d1c8e02592ef50e26922d9f5cc2a75b00703a0f"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:28257893721f3b163e4364b0ae437dcfdf5e3fd22b8d6d703fa8e02821d0dcd2",
+ "sha256": "28257893721f3b163e4364b0ae437dcfdf5e3fd22b8d6d703fa8e02821d0dcd2"
+ }
+ }
+ }
+ },
+ "glances": {
+ "version": "3.4.0.5",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/glances/blobs/sha256:cb0171b9330e64101f124a84c6f9ae6ba2673ebc8b4b4d7ec593d5ee67aeebfd",
+ "sha256": "cb0171b9330e64101f124a84c6f9ae6ba2673ebc8b4b4d7ec593d5ee67aeebfd"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/glances/blobs/sha256:d311a5456063ca3321978458d4e1dee26c48b227594bc0efec0bdf32cf43d45c",
+ "sha256": "d311a5456063ca3321978458d4e1dee26c48b227594bc0efec0bdf32cf43d45c"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/glances/blobs/sha256:a1526d01c2cd68a41efb1f9638727f62921baeb8db02cd2f6267ba3879e56c98",
+ "sha256": "a1526d01c2cd68a41efb1f9638727f62921baeb8db02cd2f6267ba3879e56c98"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/glances/blobs/sha256:2aad74ca872e153376265e88605bdcc46c0d6107ef03269c6dc8cf36618e208c",
+ "sha256": "2aad74ca872e153376265e88605bdcc46c0d6107ef03269c6dc8cf36618e208c"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/glances/blobs/sha256:44f9428eb2ff986ef51c52664503b50ac70b72869e24947ef1c36cf12c25b485",
+ "sha256": "44f9428eb2ff986ef51c52664503b50ac70b72869e24947ef1c36cf12c25b485"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/glances/blobs/sha256:e653240ae3b6e1111760da11b1954af2a3ee227e87393175890c31537385f6bc",
+ "sha256": "e653240ae3b6e1111760da11b1954af2a3ee227e87393175890c31537385f6bc"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/glances/blobs/sha256:592fd120e04f6830f4ee0792d563c4163413770e5f84601e21885333866eb7e0",
+ "sha256": "592fd120e04f6830f4ee0792d563c4163413770e5f84601e21885333866eb7e0"
+ }
+ }
+ }
+ },
+ "gnutls": {
+ "version": "3.8.4",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnutls/blobs/sha256:46373a7206cc70289bfef2081508c62cc74a2589060b21ce26c44c4c86fbda41",
+ "sha256": "46373a7206cc70289bfef2081508c62cc74a2589060b21ce26c44c4c86fbda41"
+ },
+ "arm64_ventura": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnutls/blobs/sha256:7b18d9403f8cc6a5e2e3fd427a07e32ccb1d7969715fbf5b72cfb4b5a01d8a3c",
+ "sha256": "7b18d9403f8cc6a5e2e3fd427a07e32ccb1d7969715fbf5b72cfb4b5a01d8a3c"
+ },
+ "arm64_monterey": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnutls/blobs/sha256:2a6bb19c341be5dcc2e351e68380b05f246407bd57b2dc7e94743d14e473cde8",
+ "sha256": "2a6bb19c341be5dcc2e351e68380b05f246407bd57b2dc7e94743d14e473cde8"
+ },
+ "sonoma": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnutls/blobs/sha256:7136ceb68e1bf94ad28db2990cc10da909b742390be65963b78e8b115f97b51d",
+ "sha256": "7136ceb68e1bf94ad28db2990cc10da909b742390be65963b78e8b115f97b51d"
+ },
+ "ventura": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnutls/blobs/sha256:08b8fc7ded2a17510ab505965c754bccf3cf21ae690d76af744f96d800223de2",
+ "sha256": "08b8fc7ded2a17510ab505965c754bccf3cf21ae690d76af744f96d800223de2"
+ },
+ "monterey": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnutls/blobs/sha256:80f7875ba4d2409f85851a3c61bf8c178415e863528357bc587578e8d0536c10",
+ "sha256": "80f7875ba4d2409f85851a3c61bf8c178415e863528357bc587578e8d0536c10"
+ },
+ "x86_64_linux": {
+ "cellar": "/home/linuxbrew/.linuxbrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnutls/blobs/sha256:9bedb5b302e02e32c64bf75c488216dd644bc205d9e99d2b26edfdf7f3d81b93",
+ "sha256": "9bedb5b302e02e32c64bf75c488216dd644bc205d9e99d2b26edfdf7f3d81b93"
+ }
+ }
+ }
+ },
+ "gnu-indent": {
+ "version": "2.2.13",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_ventura": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-indent/blobs/sha256:90269c7d0cb032e8defb0ed1a46222decdf12856f47206d7290aa42f41f64dc5",
+ "sha256": "90269c7d0cb032e8defb0ed1a46222decdf12856f47206d7290aa42f41f64dc5"
+ },
+ "arm64_monterey": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-indent/blobs/sha256:ed32867a9b921557dcbd8eab24d0bd8045f6525d9000d0034fa9ed2a14e23a54",
+ "sha256": "ed32867a9b921557dcbd8eab24d0bd8045f6525d9000d0034fa9ed2a14e23a54"
+ },
+ "arm64_big_sur": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-indent/blobs/sha256:e60464107020d08df53cf12dd388825cbeefd0d1ecf986f00cdf890d7cc58413",
+ "sha256": "e60464107020d08df53cf12dd388825cbeefd0d1ecf986f00cdf890d7cc58413"
+ },
+ "ventura": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-indent/blobs/sha256:97399d01070ba20f588dde6cddf6a20353a1e2def99bd99d9f11d0d3c8f12748",
+ "sha256": "97399d01070ba20f588dde6cddf6a20353a1e2def99bd99d9f11d0d3c8f12748"
+ },
+ "monterey": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-indent/blobs/sha256:ece97222820cb413acad02586561c87d8cda14370e6b4d0e2e5d47f5e7774402",
+ "sha256": "ece97222820cb413acad02586561c87d8cda14370e6b4d0e2e5d47f5e7774402"
+ },
+ "big_sur": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-indent/blobs/sha256:cf85276b497f4cf5e909ee415393207ad67c94bb9aa130e564f92f7b435d09a6",
+ "sha256": "cf85276b497f4cf5e909ee415393207ad67c94bb9aa130e564f92f7b435d09a6"
+ },
+ "x86_64_linux": {
+ "cellar": "/home/linuxbrew/.linuxbrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-indent/blobs/sha256:0e3f4a54c4abad7a07b57331772f24737237413f9ad4bd67ed8827909b515ced",
+ "sha256": "0e3f4a54c4abad7a07b57331772f24737237413f9ad4bd67ed8827909b515ced"
+ }
+ }
+ }
+ },
+ "gnu-sed": {
+ "version": "4.9",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-sed/blobs/sha256:1c4c92a7683dcbd3d251bf2e541ed46151401423cc9cbf30db9ce7185dc218a3",
+ "sha256": "1c4c92a7683dcbd3d251bf2e541ed46151401423cc9cbf30db9ce7185dc218a3"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-sed/blobs/sha256:5abaf39c16d02125db97d14cd36a96cf1a20a87821199cb38a55134fd4e0aaef",
+ "sha256": "5abaf39c16d02125db97d14cd36a96cf1a20a87821199cb38a55134fd4e0aaef"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-sed/blobs/sha256:20ae3f853a32e7f7f0f340e8c751ab7350888a655bfe7c5c20e5746c61a24fd7",
+ "sha256": "20ae3f853a32e7f7f0f340e8c751ab7350888a655bfe7c5c20e5746c61a24fd7"
+ },
+ "arm64_big_sur": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-sed/blobs/sha256:d7c89842a90d03dbb497bc1ded17b7d732fe20eaf69613fd4abb48820ab80895",
+ "sha256": "d7c89842a90d03dbb497bc1ded17b7d732fe20eaf69613fd4abb48820ab80895"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-sed/blobs/sha256:6ab6bc1628639ff2789fe20e4dd690e4bfe047d3a772bbb7b5d57efe88787951",
+ "sha256": "6ab6bc1628639ff2789fe20e4dd690e4bfe047d3a772bbb7b5d57efe88787951"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-sed/blobs/sha256:a1ac59a9a6fa20c6c904e047df3ee4d0b4e57c0a5df3821b17b8cd82bcc67b5a",
+ "sha256": "a1ac59a9a6fa20c6c904e047df3ee4d0b4e57c0a5df3821b17b8cd82bcc67b5a"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-sed/blobs/sha256:f5e2460ad86516b2517f1e77d672a4fd6ad30b158c470cccbb3b6464f228674d",
+ "sha256": "f5e2460ad86516b2517f1e77d672a4fd6ad30b158c470cccbb3b6464f228674d"
+ },
+ "big_sur": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-sed/blobs/sha256:c1c63d995d132a82fadc80b470eecfe816cb86c8cd716f01de5f003bc1199fcc",
+ "sha256": "c1c63d995d132a82fadc80b470eecfe816cb86c8cd716f01de5f003bc1199fcc"
+ },
+ "catalina": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-sed/blobs/sha256:fb5ee7317d987d9ac7f2ee357736a9bc594c88b5fbbca4f6a65046f1c2898c44",
+ "sha256": "fb5ee7317d987d9ac7f2ee357736a9bc594c88b5fbbca4f6a65046f1c2898c44"
+ },
+ "x86_64_linux": {
+ "cellar": "/home/linuxbrew/.linuxbrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-sed/blobs/sha256:8abd5b48de6b706c1ce7c2f7b8775420f63078ba294bd5ad801e458776228bbc",
+ "sha256": "8abd5b48de6b706c1ce7c2f7b8775420f63078ba294bd5ad801e458776228bbc"
+ }
+ }
+ }
+ },
+ "gnu-tar": {
+ "version": "1.35",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-tar/blobs/sha256:15a0dbc1fe67cae4498891493686afca6d745b001a2913760ce79cd52c918079",
+ "sha256": "15a0dbc1fe67cae4498891493686afca6d745b001a2913760ce79cd52c918079"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-tar/blobs/sha256:0b5debb34f53626f09c119c96ab75e46dfcc9c816ca5ccbf4ce1b051251c3752",
+ "sha256": "0b5debb34f53626f09c119c96ab75e46dfcc9c816ca5ccbf4ce1b051251c3752"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-tar/blobs/sha256:78bbae315786562366b35a1c1d25c391824281aab63421e4243ec927dbe647b1",
+ "sha256": "78bbae315786562366b35a1c1d25c391824281aab63421e4243ec927dbe647b1"
+ },
+ "arm64_big_sur": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-tar/blobs/sha256:98ce20547135be57ef9ee9b6e85df5081ba8b907f113c6d19b3e4a296b3930fc",
+ "sha256": "98ce20547135be57ef9ee9b6e85df5081ba8b907f113c6d19b3e4a296b3930fc"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-tar/blobs/sha256:da82f5abafab8ba2bfc25a33e500a546985e0b2789ea915d7ad05292b41a373b",
+ "sha256": "da82f5abafab8ba2bfc25a33e500a546985e0b2789ea915d7ad05292b41a373b"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-tar/blobs/sha256:5078709c3c1643d2ac42da4fc354baee127d06e4a4f8b04c9770867ec5166188",
+ "sha256": "5078709c3c1643d2ac42da4fc354baee127d06e4a4f8b04c9770867ec5166188"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-tar/blobs/sha256:b083b4ca16eea4b23615ce1b90b7e1a3ee52dd90cd5a4275567cb0ea55339ee4",
+ "sha256": "b083b4ca16eea4b23615ce1b90b7e1a3ee52dd90cd5a4275567cb0ea55339ee4"
+ },
+ "big_sur": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-tar/blobs/sha256:d7947a84f5bd5458a1faf9854a90d788c7661a6aba37b7ff7f8fba1e9d04ac24",
+ "sha256": "d7947a84f5bd5458a1faf9854a90d788c7661a6aba37b7ff7f8fba1e9d04ac24"
+ },
+ "x86_64_linux": {
+ "cellar": "/home/linuxbrew/.linuxbrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-tar/blobs/sha256:5209eb2c2693093b26bc232c09df1caf0b5254f9a2003aa88b81a7c7f9f2391a",
+ "sha256": "5209eb2c2693093b26bc232c09df1caf0b5254f9a2003aa88b81a7c7f9f2391a"
+ }
+ }
+ }
+ },
+ "gnu-time": {
+ "version": "1.9",
+ "bottle": {
+ "rebuild": 2,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-time/blobs/sha256:77a541727f4a75443a930e18391ed483b0a3fc797876376a0f7b34260db9a88d",
+ "sha256": "77a541727f4a75443a930e18391ed483b0a3fc797876376a0f7b34260db9a88d"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-time/blobs/sha256:f5015e7e94a474156cdc47fb188143388231916979598f5398b72e79393a2000",
+ "sha256": "f5015e7e94a474156cdc47fb188143388231916979598f5398b72e79393a2000"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-time/blobs/sha256:ff4691a2d76432eb0222284ccbeda79b3375cefdb1c606ba74ea3e8e06ac25f5",
+ "sha256": "ff4691a2d76432eb0222284ccbeda79b3375cefdb1c606ba74ea3e8e06ac25f5"
+ },
+ "arm64_big_sur": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-time/blobs/sha256:3930463651363f08ca7a90ec25deafd85c57f7a71be8ee236f7e15f20de7ff22",
+ "sha256": "3930463651363f08ca7a90ec25deafd85c57f7a71be8ee236f7e15f20de7ff22"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-time/blobs/sha256:99cb9faea67317bef1bd531f810c91b4f77817d127942ba3902dcc8b6605c437",
+ "sha256": "99cb9faea67317bef1bd531f810c91b4f77817d127942ba3902dcc8b6605c437"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-time/blobs/sha256:950250410ffda2307e42e465b0a563b0d4175f83017467df59c580cba41d8c85",
+ "sha256": "950250410ffda2307e42e465b0a563b0d4175f83017467df59c580cba41d8c85"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-time/blobs/sha256:e7b649410f591aa0d0068ae267e7357db99c86c73fd5e992db0f5512614a07a6",
+ "sha256": "e7b649410f591aa0d0068ae267e7357db99c86c73fd5e992db0f5512614a07a6"
+ },
+ "big_sur": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-time/blobs/sha256:f4fc9d2c49b65130d04a476d4cd887b1e1033a7870df9805be28aba09be901f0",
+ "sha256": "f4fc9d2c49b65130d04a476d4cd887b1e1033a7870df9805be28aba09be901f0"
+ },
+ "catalina": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-time/blobs/sha256:9a1d1160f85f46b3022dc4d978dfafe6b3a02fc97446bc51f8b1ae4580b7c69a",
+ "sha256": "9a1d1160f85f46b3022dc4d978dfafe6b3a02fc97446bc51f8b1ae4580b7c69a"
+ },
+ "mojave": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-time/blobs/sha256:dc007b95e2f9fb0df3380da55d3c9337529b1a4a3cd762972eb88512f567ea1c",
+ "sha256": "dc007b95e2f9fb0df3380da55d3c9337529b1a4a3cd762972eb88512f567ea1c"
+ },
+ "high_sierra": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-time/blobs/sha256:ad5d776c38e43f16fad8976770eeaa18e40562c166fa65fdaa12af61981c7b90",
+ "sha256": "ad5d776c38e43f16fad8976770eeaa18e40562c166fa65fdaa12af61981c7b90"
+ },
+ "sierra": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-time/blobs/sha256:d51ef948a5a87281175fef771cb28469cbdb3085e3c51ad325d780ff921cc013",
+ "sha256": "d51ef948a5a87281175fef771cb28469cbdb3085e3c51ad325d780ff921cc013"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-time/blobs/sha256:c9c5ae5e7ac2f00cf8655ce4b6095e4706bcc36300d36a1c7121ab03d010ea5f",
+ "sha256": "c9c5ae5e7ac2f00cf8655ce4b6095e4706bcc36300d36a1c7121ab03d010ea5f"
+ }
+ }
+ }
+ },
+ "gnu-which": {
+ "version": "2.21",
+ "bottle": {
+ "rebuild": 3,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-which/blobs/sha256:97c6d0e56281d2adc27fd8c38b267edc4ff12dff39a5dfa4adb7db45fd0cb042",
+ "sha256": "97c6d0e56281d2adc27fd8c38b267edc4ff12dff39a5dfa4adb7db45fd0cb042"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-which/blobs/sha256:0778972c32eb2eb9cbde5026fe69c0a5c4bdfbc1e16f18c327e0c6f92a32385e",
+ "sha256": "0778972c32eb2eb9cbde5026fe69c0a5c4bdfbc1e16f18c327e0c6f92a32385e"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-which/blobs/sha256:8343d2e916151642b540143f2b3f8a79af4a6e22df55e01b846bad2d0e509074",
+ "sha256": "8343d2e916151642b540143f2b3f8a79af4a6e22df55e01b846bad2d0e509074"
+ },
+ "arm64_big_sur": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-which/blobs/sha256:873e8ac50fdc7b40699f7ebcf29c73c768d2db8d958f1fdb2d4be13c0b670c3a",
+ "sha256": "873e8ac50fdc7b40699f7ebcf29c73c768d2db8d958f1fdb2d4be13c0b670c3a"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-which/blobs/sha256:5524dce5bf9b05388b10687ba53cb3d2cb2a12f589ebd67625688aec5f3598de",
+ "sha256": "5524dce5bf9b05388b10687ba53cb3d2cb2a12f589ebd67625688aec5f3598de"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-which/blobs/sha256:ff5af1e6019d670be02d24175b1be0cc0973e303f4788d1e5b8ef4c167f0d36f",
+ "sha256": "ff5af1e6019d670be02d24175b1be0cc0973e303f4788d1e5b8ef4c167f0d36f"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-which/blobs/sha256:21e5e71e2a9aadc88636bdb7e76dc5aef17e5ca31b99e02553bc61263e2c36e8",
+ "sha256": "21e5e71e2a9aadc88636bdb7e76dc5aef17e5ca31b99e02553bc61263e2c36e8"
+ },
+ "big_sur": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-which/blobs/sha256:eeb493d3cc6252da45b29cf1d2a1d6daca630a6cd467ae690c3979673ea9a589",
+ "sha256": "eeb493d3cc6252da45b29cf1d2a1d6daca630a6cd467ae690c3979673ea9a589"
+ },
+ "catalina": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-which/blobs/sha256:f9e6512591096a9f53067ea4a0b5b9f8516515b49fd5bdabfc6e31c1c0c876f2",
+ "sha256": "f9e6512591096a9f53067ea4a0b5b9f8516515b49fd5bdabfc6e31c1c0c876f2"
+ },
+ "mojave": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-which/blobs/sha256:170008e80a4cc5f1e45b3445f9fb6f099d7700aa6dd825602f6d32316c27735b",
+ "sha256": "170008e80a4cc5f1e45b3445f9fb6f099d7700aa6dd825602f6d32316c27735b"
+ },
+ "high_sierra": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-which/blobs/sha256:66446416b0dc367076ab38cfc9775d8c201fc571b1a2cd2fc0197daa6b83882a",
+ "sha256": "66446416b0dc367076ab38cfc9775d8c201fc571b1a2cd2fc0197daa6b83882a"
+ },
+ "sierra": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-which/blobs/sha256:68ea3522ec318c9b25d711ce4405b4cd6a41edca20b7df008adc499ab794c4fa",
+ "sha256": "68ea3522ec318c9b25d711ce4405b4cd6a41edca20b7df008adc499ab794c4fa"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gnu-which/blobs/sha256:cf191a85d1f5684e84909ccf5d5df3ec3b9ffd7facc629bc2664f99078bf414e",
+ "sha256": "cf191a85d1f5684e84909ccf5d5df3ec3b9ffd7facc629bc2664f99078bf414e"
+ }
+ }
+ }
+ },
+ "jemalloc": {
+ "version": "5.3.0",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/jemalloc/blobs/sha256:f70f02aa2f1b858ed5e5cef84a271efeaaa27e79f266844997aab95daa66a7fa",
+ "sha256": "f70f02aa2f1b858ed5e5cef84a271efeaaa27e79f266844997aab95daa66a7fa"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/jemalloc/blobs/sha256:33e0c3fbe56642e081018a9674df734d34afdc35af7d03f5dd2b484a804555e3",
+ "sha256": "33e0c3fbe56642e081018a9674df734d34afdc35af7d03f5dd2b484a804555e3"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/jemalloc/blobs/sha256:b7ef9abad498e6eb53fb476fde4396fc9ab99a23092ea14bcf576548e198f9bd",
+ "sha256": "b7ef9abad498e6eb53fb476fde4396fc9ab99a23092ea14bcf576548e198f9bd"
+ },
+ "arm64_big_sur": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/jemalloc/blobs/sha256:b24e4a9413b347397a10ebc9a7a2d309d88c0f9479c1cdebe6c302acba9a43a9",
+ "sha256": "b24e4a9413b347397a10ebc9a7a2d309d88c0f9479c1cdebe6c302acba9a43a9"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/jemalloc/blobs/sha256:cb1d95640b85ec863d457722af363119b9a16274ce6f9e968f939fcf85bdd350",
+ "sha256": "cb1d95640b85ec863d457722af363119b9a16274ce6f9e968f939fcf85bdd350"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/jemalloc/blobs/sha256:66b5f3a4c4ad9f7801e6ad2e76d1586e7b57e2cc64b24c2684dd1c2af8bc82f3",
+ "sha256": "66b5f3a4c4ad9f7801e6ad2e76d1586e7b57e2cc64b24c2684dd1c2af8bc82f3"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/jemalloc/blobs/sha256:27ae29c02d718c38ee5f623c3ef08ad3530a6fd3595d16d2ddadd6552bf32c12",
+ "sha256": "27ae29c02d718c38ee5f623c3ef08ad3530a6fd3595d16d2ddadd6552bf32c12"
+ },
+ "big_sur": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/jemalloc/blobs/sha256:72aef17aa140b457400c4f2b74d0473bf1160616c3df7cb8604ac2bf734afea5",
+ "sha256": "72aef17aa140b457400c4f2b74d0473bf1160616c3df7cb8604ac2bf734afea5"
+ },
+ "catalina": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/jemalloc/blobs/sha256:3f5cf334d16ab432bf210c7e171510d0edcd834f939b57bddfd428af5ed248ae",
+ "sha256": "3f5cf334d16ab432bf210c7e171510d0edcd834f939b57bddfd428af5ed248ae"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/jemalloc/blobs/sha256:240b20cc078b21d90c32bd34447952b9b464958b1858ae109f168558993f9278",
+ "sha256": "240b20cc078b21d90c32bd34447952b9b464958b1858ae109f168558993f9278"
+ }
+ }
+ }
+ },
+ "jq": {
+ "version": "1.7.1",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/jq/blobs/sha256:07bc9081c0fdb43aca089e5839f6a270fc45ca9aa7d7633e16fac0fdfe4c4ad8",
+ "sha256": "07bc9081c0fdb43aca089e5839f6a270fc45ca9aa7d7633e16fac0fdfe4c4ad8"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/jq/blobs/sha256:1b27f5277eb2cdfac9f3970ee9adadddc5e04e45469de05a663bc16e793b4eea",
+ "sha256": "1b27f5277eb2cdfac9f3970ee9adadddc5e04e45469de05a663bc16e793b4eea"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/jq/blobs/sha256:41911a73dc6a44c9788c198abc18307213d070d7ca6375e8dd6994335aaee136",
+ "sha256": "41911a73dc6a44c9788c198abc18307213d070d7ca6375e8dd6994335aaee136"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/jq/blobs/sha256:b68d33a5e3c79a0f457d96de1ad1f200c05314f5fea9244d712847c92032b5f7",
+ "sha256": "b68d33a5e3c79a0f457d96de1ad1f200c05314f5fea9244d712847c92032b5f7"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/jq/blobs/sha256:10b845b1505892ff585b49e89fe3b09761d148b2c14ca6f5a1aa58002452f8f0",
+ "sha256": "10b845b1505892ff585b49e89fe3b09761d148b2c14ca6f5a1aa58002452f8f0"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/jq/blobs/sha256:449c76665ac72b34daeb1a09dd19217e3be1e723c63ec3ac88e02b8c9a750f34",
+ "sha256": "449c76665ac72b34daeb1a09dd19217e3be1e723c63ec3ac88e02b8c9a750f34"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/jq/blobs/sha256:ed490b627b327b3458a70a78c546be07d57bfc6958921f875b76e85f6be51f47",
+ "sha256": "ed490b627b327b3458a70a78c546be07d57bfc6958921f875b76e85f6be51f47"
+ }
+ }
+ }
+ },
+ "libffi": {
+ "version": "3.4.6",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libffi/blobs/sha256:e81237234a3e21d5222c1c8baf4017bc2f2ad7e444fbf58ad6b635fc0ace5078",
+ "sha256": "e81237234a3e21d5222c1c8baf4017bc2f2ad7e444fbf58ad6b635fc0ace5078"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libffi/blobs/sha256:7a6a1d1dffe41d4e9bf117440190be51c432a2a192945ed8e2e10c4bb1f95ad0",
+ "sha256": "7a6a1d1dffe41d4e9bf117440190be51c432a2a192945ed8e2e10c4bb1f95ad0"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libffi/blobs/sha256:eacdfea3b29d48dc8c3fb7578a9a59dbeb9048eca6493b8cd95605c86652e6de",
+ "sha256": "eacdfea3b29d48dc8c3fb7578a9a59dbeb9048eca6493b8cd95605c86652e6de"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libffi/blobs/sha256:d783974753df1f7347d8cef16403e157f0625302848e8267626064c4f79a97d8",
+ "sha256": "d783974753df1f7347d8cef16403e157f0625302848e8267626064c4f79a97d8"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libffi/blobs/sha256:e5adecfb6ddd1a18ccb492c051adfd693eb091c4b24a58ad7b1cecb6afb0a575",
+ "sha256": "e5adecfb6ddd1a18ccb492c051adfd693eb091c4b24a58ad7b1cecb6afb0a575"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libffi/blobs/sha256:8b3cb29277a584f1684661823c8232659b04234873430164bc80ba484c8aa8da",
+ "sha256": "8b3cb29277a584f1684661823c8232659b04234873430164bc80ba484c8aa8da"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/libffi/blobs/sha256:798c3983a917698d5dd0c60063e7b8c1e5b4fc377d9e11d7cba010725eca1bfb",
+ "sha256": "798c3983a917698d5dd0c60063e7b8c1e5b4fc377d9e11d7cba010725eca1bfb"
+ }
+ }
+ }
+ },
+ "libmemcached": {
+ "version": "1.0.18_2",
+ "bottle": {
+ "rebuild": 2,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libmemcached/blobs/sha256:652c9f9862e367e62acc5d2b1ddc20d798e6f15c51bccbc69e642acd4df1be0a",
+ "sha256": "652c9f9862e367e62acc5d2b1ddc20d798e6f15c51bccbc69e642acd4df1be0a"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libmemcached/blobs/sha256:0511d48bcc88a6860030c5c6bec5d36818068b43f11d67561f1519ce0dbf6b73",
+ "sha256": "0511d48bcc88a6860030c5c6bec5d36818068b43f11d67561f1519ce0dbf6b73"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libmemcached/blobs/sha256:37977639be769bfd5ef97d38f408f57cf84f3607ce881c4d6f2c2d7c70a9b2a4",
+ "sha256": "37977639be769bfd5ef97d38f408f57cf84f3607ce881c4d6f2c2d7c70a9b2a4"
+ },
+ "arm64_big_sur": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libmemcached/blobs/sha256:2ec7b12e9181c83bbbd45b62ba2a1a0e2958fe2caaa0d94be1da2319831de3be",
+ "sha256": "2ec7b12e9181c83bbbd45b62ba2a1a0e2958fe2caaa0d94be1da2319831de3be"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libmemcached/blobs/sha256:feedeadf282983ee5a86dff88537f2ba2f470d53d664efbc6a9c6bd393177037",
+ "sha256": "feedeadf282983ee5a86dff88537f2ba2f470d53d664efbc6a9c6bd393177037"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libmemcached/blobs/sha256:2807a08a7c29739bd49450c44ec6f926e7c626b3b2104b1ed160226820a5465b",
+ "sha256": "2807a08a7c29739bd49450c44ec6f926e7c626b3b2104b1ed160226820a5465b"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libmemcached/blobs/sha256:902c0e16ba5ec76696c3f45888ef0c61b840a10b344149242bec812a7c99ee0d",
+ "sha256": "902c0e16ba5ec76696c3f45888ef0c61b840a10b344149242bec812a7c99ee0d"
+ },
+ "big_sur": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libmemcached/blobs/sha256:c41f0bfdc440d240f8d0653dcc87270bd315571eab6979ff94d3271f863cb0e7",
+ "sha256": "c41f0bfdc440d240f8d0653dcc87270bd315571eab6979ff94d3271f863cb0e7"
+ },
+ "catalina": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libmemcached/blobs/sha256:70c6e1e3dd76241e343a4a3b38a62fae5bea6d2e2405739b11473d084f4409a9",
+ "sha256": "70c6e1e3dd76241e343a4a3b38a62fae5bea6d2e2405739b11473d084f4409a9"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/libmemcached/blobs/sha256:b551d4cc72d953e3018369057901f77a88b1b633661f5acfedcf6bba37385a8b",
+ "sha256": "b551d4cc72d953e3018369057901f77a88b1b633661f5acfedcf6bba37385a8b"
+ }
+ }
+ }
+ },
+ "libpq": {
+ "version": "16.2_1",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/libpq/blobs/sha256:34ec05de1540053d140f435b9927edf7e4a4e84ed25253085e55a817b451c0cb",
+ "sha256": "34ec05de1540053d140f435b9927edf7e4a4e84ed25253085e55a817b451c0cb"
+ },
+ "arm64_ventura": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/libpq/blobs/sha256:81980d2cc07094693afdec5016eb3acf298bfc3c2e19b5e4aa035b5d815a8a86",
+ "sha256": "81980d2cc07094693afdec5016eb3acf298bfc3c2e19b5e4aa035b5d815a8a86"
+ },
+ "arm64_monterey": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/libpq/blobs/sha256:5b18a17730c5f0707c2e837acb86927e092e3ea997cf8354e5fdcf1de5ab1ac9",
+ "sha256": "5b18a17730c5f0707c2e837acb86927e092e3ea997cf8354e5fdcf1de5ab1ac9"
+ },
+ "sonoma": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/libpq/blobs/sha256:b25bcb80440de0301426ec4e9159a49dd5f690aec12e3c0fee4897b8a2a909e7",
+ "sha256": "b25bcb80440de0301426ec4e9159a49dd5f690aec12e3c0fee4897b8a2a909e7"
+ },
+ "ventura": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/libpq/blobs/sha256:02fde1ff8fb54fe5c2e518d48447707ee2ee5bc90935a7782b877dff58e03b70",
+ "sha256": "02fde1ff8fb54fe5c2e518d48447707ee2ee5bc90935a7782b877dff58e03b70"
+ },
+ "monterey": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/libpq/blobs/sha256:2c38685f2e169b2f3cee5c77128ae77a15144d45ea2b836bd41ebe2c95292eb3",
+ "sha256": "2c38685f2e169b2f3cee5c77128ae77a15144d45ea2b836bd41ebe2c95292eb3"
+ },
+ "x86_64_linux": {
+ "cellar": "/home/linuxbrew/.linuxbrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/libpq/blobs/sha256:ec1122e7f681a2788b16d614169aadb0ed9b7056af79b8fd380fea4e31e3c6ae",
+ "sha256": "ec1122e7f681a2788b16d614169aadb0ed9b7056af79b8fd380fea4e31e3c6ae"
+ }
+ }
+ }
+ },
+ "libtiff": {
+ "version": "4.6.0",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libtiff/blobs/sha256:a9cafbce08b697fb25e326ea1dd3a0e01c3acc3f8f616e844940e49b33386ab3",
+ "sha256": "a9cafbce08b697fb25e326ea1dd3a0e01c3acc3f8f616e844940e49b33386ab3"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libtiff/blobs/sha256:12f3e1b0e5cd225a05d914692cf6de0f86f29ba1f51b806723237da2f85a7b13",
+ "sha256": "12f3e1b0e5cd225a05d914692cf6de0f86f29ba1f51b806723237da2f85a7b13"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libtiff/blobs/sha256:8a7ed5ea7efe9534f15bca3ae2134d9f35bd08372da5949c33d025f80ae1d47e",
+ "sha256": "8a7ed5ea7efe9534f15bca3ae2134d9f35bd08372da5949c33d025f80ae1d47e"
+ },
+ "arm64_big_sur": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libtiff/blobs/sha256:53b3bed3893804a56efa2ef20af3c2087298ba313b44e4cc6531d0bcfc54aaa9",
+ "sha256": "53b3bed3893804a56efa2ef20af3c2087298ba313b44e4cc6531d0bcfc54aaa9"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libtiff/blobs/sha256:a89a2671064dbf7af6b84a9f2d20546b3dff82ed4b6f95c17bdfe48ce6c615fc",
+ "sha256": "a89a2671064dbf7af6b84a9f2d20546b3dff82ed4b6f95c17bdfe48ce6c615fc"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libtiff/blobs/sha256:7347c37cf98bec3f956296caee0ecee54e7bfcc7b32d6e2e02b9ae04c80e3ca6",
+ "sha256": "7347c37cf98bec3f956296caee0ecee54e7bfcc7b32d6e2e02b9ae04c80e3ca6"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libtiff/blobs/sha256:8e3e1d5d4da3485867a6e0e2b35cf79e37f1b00e3e5399cf9b36996b1cbbff0c",
+ "sha256": "8e3e1d5d4da3485867a6e0e2b35cf79e37f1b00e3e5399cf9b36996b1cbbff0c"
+ },
+ "big_sur": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libtiff/blobs/sha256:e0e6f2c0bc25665bfffb66505ebc9fc410aeeed3435edf770e9ecee88c7bc0e1",
+ "sha256": "e0e6f2c0bc25665bfffb66505ebc9fc410aeeed3435edf770e9ecee88c7bc0e1"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/libtiff/blobs/sha256:9a6e0bb56c39b72a33b0a5629dc3fd49e4f1391513bcf7d04a764523cc0321c8",
+ "sha256": "9a6e0bb56c39b72a33b0a5629dc3fd49e4f1391513bcf7d04a764523cc0321c8"
+ }
+ }
+ }
+ },
+ "libtool": {
+ "version": "2.4.7",
+ "bottle": {
+ "rebuild": 1,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libtool/blobs/sha256:211b174c29c24b3bdd42c44a12262ba479c4707b19bd2abd41f41a67f1b45cf5",
+ "sha256": "211b174c29c24b3bdd42c44a12262ba479c4707b19bd2abd41f41a67f1b45cf5"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libtool/blobs/sha256:a7196b340a6b2ee833b9451409a2e83b08ba192bebe4fd019c6e658789c76298",
+ "sha256": "a7196b340a6b2ee833b9451409a2e83b08ba192bebe4fd019c6e658789c76298"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libtool/blobs/sha256:359d2a8f85d03f310263b91c665bf591703e8a7a6e79396bc2fc64df75e0655a",
+ "sha256": "359d2a8f85d03f310263b91c665bf591703e8a7a6e79396bc2fc64df75e0655a"
+ },
+ "arm64_big_sur": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libtool/blobs/sha256:faa1bb0c78ff5881efcaf476ccfc6ec400e56a4583fcc850d265b70f37fd577e",
+ "sha256": "faa1bb0c78ff5881efcaf476ccfc6ec400e56a4583fcc850d265b70f37fd577e"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libtool/blobs/sha256:47676ae503261483d5f1f35caa074efc416527bc471e25b0dc5c19bf588ed39f",
+ "sha256": "47676ae503261483d5f1f35caa074efc416527bc471e25b0dc5c19bf588ed39f"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libtool/blobs/sha256:d20beb0eb96c3ab67be5987393c64a575781c5d7abe6fb20efd2ae343a0680c7",
+ "sha256": "d20beb0eb96c3ab67be5987393c64a575781c5d7abe6fb20efd2ae343a0680c7"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libtool/blobs/sha256:4b248059b3fed99774183f17e335eca05edb25698dabcecbe916f4ec63a48cc6",
+ "sha256": "4b248059b3fed99774183f17e335eca05edb25698dabcecbe916f4ec63a48cc6"
+ },
+ "big_sur": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libtool/blobs/sha256:deffadfecec61da06dde9edf5eae19381f80f99ae78e57607732fd54be366b8a",
+ "sha256": "deffadfecec61da06dde9edf5eae19381f80f99ae78e57607732fd54be366b8a"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/libtool/blobs/sha256:f55d5bcc07a45f599800b2c9fb5818c13be90803355e169cdb0e1ddc621bee5e",
+ "sha256": "f55d5bcc07a45f599800b2c9fb5818c13be90803355e169cdb0e1ddc621bee5e"
+ }
+ }
+ }
+ },
+ "libxml2": {
+ "version": "2.12.6",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libxml2/blobs/sha256:9c37ec3428a8874a2c06cb6b7c06e6fa245f6c901d11e98cd1fb1a026c19b107",
+ "sha256": "9c37ec3428a8874a2c06cb6b7c06e6fa245f6c901d11e98cd1fb1a026c19b107"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libxml2/blobs/sha256:42f2f051d27bffbc179a9ffd4a3004c7422c776233267b1b471e10c1e413744d",
+ "sha256": "42f2f051d27bffbc179a9ffd4a3004c7422c776233267b1b471e10c1e413744d"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libxml2/blobs/sha256:2891384cf532ff7e81ed87248ba71c50039dbb21b03890ade4ba4e03f30af9de",
+ "sha256": "2891384cf532ff7e81ed87248ba71c50039dbb21b03890ade4ba4e03f30af9de"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libxml2/blobs/sha256:67634d87d45a885b274d3282e36d795251d4db9f00996b0e7d815f0e5c6c8a22",
+ "sha256": "67634d87d45a885b274d3282e36d795251d4db9f00996b0e7d815f0e5c6c8a22"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libxml2/blobs/sha256:7ade43ba71ec4b40b8d287063d86b671644b18760eba47a04de37ac4a76efad8",
+ "sha256": "7ade43ba71ec4b40b8d287063d86b671644b18760eba47a04de37ac4a76efad8"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libxml2/blobs/sha256:a6d3512246ccd15f742332c17fa77c635c194b917da0c5ff0ed2a580dfcb5271",
+ "sha256": "a6d3512246ccd15f742332c17fa77c635c194b917da0c5ff0ed2a580dfcb5271"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/libxml2/blobs/sha256:e71c388c95d8b83c907784a9fcc57bf2462e33ee97ea4406d214e177e67c095d",
+ "sha256": "e71c388c95d8b83c907784a9fcc57bf2462e33ee97ea4406d214e177e67c095d"
+ }
+ }
+ }
+ },
+ "libxslt": {
+ "version": "1.1.39",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libxslt/blobs/sha256:28019195eef786264be3a0e67f814753a9108653c5f9e07964b89502c66b06e9",
+ "sha256": "28019195eef786264be3a0e67f814753a9108653c5f9e07964b89502c66b06e9"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libxslt/blobs/sha256:9921d7bd84d8fc6914244d5142fb60741eabc71a9a3af87b3c04967f9d334aba",
+ "sha256": "9921d7bd84d8fc6914244d5142fb60741eabc71a9a3af87b3c04967f9d334aba"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libxslt/blobs/sha256:c48449d1ad89ada8cf9133ea7ea88b247730144ea874dff9608eae0a7b89b882",
+ "sha256": "c48449d1ad89ada8cf9133ea7ea88b247730144ea874dff9608eae0a7b89b882"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libxslt/blobs/sha256:9a4458989d734defc29a7c042b1144a0a66c3768530fb0e07fe52ea78828e606",
+ "sha256": "9a4458989d734defc29a7c042b1144a0a66c3768530fb0e07fe52ea78828e606"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libxslt/blobs/sha256:695cb26667ee927b4f20fae395b48b8af4bf666f3dc9625bef2e3823aa2e65d8",
+ "sha256": "695cb26667ee927b4f20fae395b48b8af4bf666f3dc9625bef2e3823aa2e65d8"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libxslt/blobs/sha256:ed196bcf4372dacf751a8ba6d45feac8aa6220a877828785651c4694e6209f5b",
+ "sha256": "ed196bcf4372dacf751a8ba6d45feac8aa6220a877828785651c4694e6209f5b"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/libxslt/blobs/sha256:81e671ea1b060a25b0db9ab3486ae21b1da5982b6e4a35593a411e9c6d103544",
+ "sha256": "81e671ea1b060a25b0db9ab3486ae21b1da5982b6e4a35593a411e9c6d103544"
+ }
+ }
+ }
+ },
+ "libyaml": {
+ "version": "0.2.5",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libyaml/blobs/sha256:98c0cf81bcdf7577d5fdc8cc18732970b9ae7e0e7423a733f88f0f566ba483ad",
+ "sha256": "98c0cf81bcdf7577d5fdc8cc18732970b9ae7e0e7423a733f88f0f566ba483ad"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libyaml/blobs/sha256:11239e8f5066c6d0d0718208d4eab518da00c7289f33c9c76c0a09ba5c0417c9",
+ "sha256": "11239e8f5066c6d0d0718208d4eab518da00c7289f33c9c76c0a09ba5c0417c9"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libyaml/blobs/sha256:a436da33a05f805258c5951a365dec4e8d70a908dbe5dacdeb6b2ecd0efd5024",
+ "sha256": "a436da33a05f805258c5951a365dec4e8d70a908dbe5dacdeb6b2ecd0efd5024"
+ },
+ "arm64_big_sur": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libyaml/blobs/sha256:fe1082f3475a144261b41e2c3e0728b9331911b1cbfadfbc1f3d70d454709154",
+ "sha256": "fe1082f3475a144261b41e2c3e0728b9331911b1cbfadfbc1f3d70d454709154"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libyaml/blobs/sha256:4d6e02ce3a82b60033bc7e55bef841dcfef0c05c051176d96accb50744136c6d",
+ "sha256": "4d6e02ce3a82b60033bc7e55bef841dcfef0c05c051176d96accb50744136c6d"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libyaml/blobs/sha256:b49e62f014b3e7d85a169b422b7521356700c7caaaea9f4901086cafe692a86e",
+ "sha256": "b49e62f014b3e7d85a169b422b7521356700c7caaaea9f4901086cafe692a86e"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libyaml/blobs/sha256:dbd54ce703c6d8eb77e708f75b4730ad2653d28f6291c4a26dc22158beb3f210",
+ "sha256": "dbd54ce703c6d8eb77e708f75b4730ad2653d28f6291c4a26dc22158beb3f210"
+ },
+ "big_sur": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libyaml/blobs/sha256:83547fba540a38c30705a59a2e746952c68857212e823c6ee97c186e088f75cd",
+ "sha256": "83547fba540a38c30705a59a2e746952c68857212e823c6ee97c186e088f75cd"
+ },
+ "catalina": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libyaml/blobs/sha256:56d3549b342cffb181e3eb05356697bbb362b9733c73e0eeff9b637ecf92cd23",
+ "sha256": "56d3549b342cffb181e3eb05356697bbb362b9733c73e0eeff9b637ecf92cd23"
+ },
+ "mojave": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libyaml/blobs/sha256:a04988b3868cfadf7bcaff6b753b59388cbea70b38f2fa41a25229150d073696",
+ "sha256": "a04988b3868cfadf7bcaff6b753b59388cbea70b38f2fa41a25229150d073696"
+ },
+ "high_sierra": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libyaml/blobs/sha256:d3e22ad09c3d6872c5f7ee7c7f1146c9f14c178ff4c3a3488a20bf584bc854d5",
+ "sha256": "d3e22ad09c3d6872c5f7ee7c7f1146c9f14c178ff4c3a3488a20bf584bc854d5"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/libyaml/blobs/sha256:354677a745b6c62109e792ddbd0cbdaf9e6a471d84fdbde3a7d9bae36d832da8",
+ "sha256": "354677a745b6c62109e792ddbd0cbdaf9e6a471d84fdbde3a7d9bae36d832da8"
+ }
+ }
+ }
+ },
+ "libzip": {
+ "version": "1.10.1",
+ "bottle": {
+ "rebuild": 1,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libzip/blobs/sha256:8ecf154f8c0bab71c0008c6f73eb8cd2df78cfa424d8bdcffc66dc95b3bf7c14",
+ "sha256": "8ecf154f8c0bab71c0008c6f73eb8cd2df78cfa424d8bdcffc66dc95b3bf7c14"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libzip/blobs/sha256:cd7bda731a8b2e5d1a3cdf5be6b515718c56d55d16a5b45faa1a91daf9c0ca2b",
+ "sha256": "cd7bda731a8b2e5d1a3cdf5be6b515718c56d55d16a5b45faa1a91daf9c0ca2b"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libzip/blobs/sha256:a0d8bae54df1068c92ad894eddca0cd7465ecbaa3ef875c07c46bcea764bac71",
+ "sha256": "a0d8bae54df1068c92ad894eddca0cd7465ecbaa3ef875c07c46bcea764bac71"
+ },
+ "arm64_big_sur": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libzip/blobs/sha256:6549fda9b8f6ac3904b55bc0b8c601ecf15773eb4c97c40091148559d69bfec1",
+ "sha256": "6549fda9b8f6ac3904b55bc0b8c601ecf15773eb4c97c40091148559d69bfec1"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libzip/blobs/sha256:f782643b254f58ddf3830272c0221f5d35db84ebd4f3d4ef19894ca0c91648ad",
+ "sha256": "f782643b254f58ddf3830272c0221f5d35db84ebd4f3d4ef19894ca0c91648ad"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libzip/blobs/sha256:4fca00c15a69f25064b40b12e37a6f552edd632f77e2947e076745b55aaeffd3",
+ "sha256": "4fca00c15a69f25064b40b12e37a6f552edd632f77e2947e076745b55aaeffd3"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libzip/blobs/sha256:5fbb0e2a2cd9b17a416d518d324d9eb3eac88626851bad41d9fb144ccebd8757",
+ "sha256": "5fbb0e2a2cd9b17a416d518d324d9eb3eac88626851bad41d9fb144ccebd8757"
+ },
+ "big_sur": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/libzip/blobs/sha256:db6453b117d39f0fe310f30e0d92124c453dd1568edd5800fd886bdb2b35e9df",
+ "sha256": "db6453b117d39f0fe310f30e0d92124c453dd1568edd5800fd886bdb2b35e9df"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/libzip/blobs/sha256:a5c180236137518d040277c1310e4b7c34337a0d396053e9a2534861453f70bc",
+ "sha256": "a5c180236137518d040277c1310e4b7c34337a0d396053e9a2534861453f70bc"
+ }
+ }
+ }
+ },
+ "make": {
+ "version": "4.4.1",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/make/blobs/sha256:2cf9b5846e07363681d41819a13d2d9a993a69dd5090bbfae3da182915e777b9",
+ "sha256": "2cf9b5846e07363681d41819a13d2d9a993a69dd5090bbfae3da182915e777b9"
+ },
+ "arm64_ventura": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/make/blobs/sha256:23e26446ffdefd2b7fe44c559e11ab6bc127abd32233847f4e73bb3de87d98c6",
+ "sha256": "23e26446ffdefd2b7fe44c559e11ab6bc127abd32233847f4e73bb3de87d98c6"
+ },
+ "arm64_monterey": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/make/blobs/sha256:f3c69489afdb2ad686c7674d85deac4fcfdb3f891664c08c5d255af20a6eddcb",
+ "sha256": "f3c69489afdb2ad686c7674d85deac4fcfdb3f891664c08c5d255af20a6eddcb"
+ },
+ "arm64_big_sur": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/make/blobs/sha256:cdb852c53ed94d31d5f4988338336b004f21857d1ecaa8e84b1c155bf92e0c47",
+ "sha256": "cdb852c53ed94d31d5f4988338336b004f21857d1ecaa8e84b1c155bf92e0c47"
+ },
+ "sonoma": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/make/blobs/sha256:8c51e1eebb1cb1ae3acc4c52d041b141dd7d1ca005ba0081fd7c47162d4a50db",
+ "sha256": "8c51e1eebb1cb1ae3acc4c52d041b141dd7d1ca005ba0081fd7c47162d4a50db"
+ },
+ "ventura": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/make/blobs/sha256:206c13dc47f17131b1337ed24677b69288c2f03f780d09d1c3e5fd11a41d6ad9",
+ "sha256": "206c13dc47f17131b1337ed24677b69288c2f03f780d09d1c3e5fd11a41d6ad9"
+ },
+ "monterey": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/make/blobs/sha256:75651f4a57f1a712dfed7ed926de8b4c7f6c728544627ea059304f28455c4bab",
+ "sha256": "75651f4a57f1a712dfed7ed926de8b4c7f6c728544627ea059304f28455c4bab"
+ },
+ "big_sur": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/make/blobs/sha256:2571cf69a3d123408660797685af0040097b1c273b13dfd0e3653ca1150830e2",
+ "sha256": "2571cf69a3d123408660797685af0040097b1c273b13dfd0e3653ca1150830e2"
+ },
+ "x86_64_linux": {
+ "cellar": "/home/linuxbrew/.linuxbrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/make/blobs/sha256:bded8e436d51f10ee36207ec69a0a318fb8583f83a5863f45bb203d3ae055170",
+ "sha256": "bded8e436d51f10ee36207ec69a0a318fb8583f83a5863f45bb203d3ae055170"
+ }
+ }
+ }
+ },
+ "p7zip": {
+ "version": "17.05",
+ "bottle": {
+ "rebuild": 1,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/p7zip/blobs/sha256:19bf0feb4e993c7cfad0d42bf8b9820ba67a9ebbd7ad4efd312a4a7953704a1a",
+ "sha256": "19bf0feb4e993c7cfad0d42bf8b9820ba67a9ebbd7ad4efd312a4a7953704a1a"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/p7zip/blobs/sha256:ba7f3e60841e85ab16ae76e7f0be634e15ea1b0c4a3a631cbe57447cbc9d77b6",
+ "sha256": "ba7f3e60841e85ab16ae76e7f0be634e15ea1b0c4a3a631cbe57447cbc9d77b6"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/p7zip/blobs/sha256:ed5af58015207456c265187cd73b53a80db239a9029bed1579065faa2391fec1",
+ "sha256": "ed5af58015207456c265187cd73b53a80db239a9029bed1579065faa2391fec1"
+ },
+ "arm64_big_sur": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/p7zip/blobs/sha256:145a4d0ecb748931931030b2e8844d5e007cba92cfed3b4ae07b4f15bc461e22",
+ "sha256": "145a4d0ecb748931931030b2e8844d5e007cba92cfed3b4ae07b4f15bc461e22"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/p7zip/blobs/sha256:2827eb7db9135c059d4498667e08ac37e4e020d39df6df0cebb1080d09cea9c5",
+ "sha256": "2827eb7db9135c059d4498667e08ac37e4e020d39df6df0cebb1080d09cea9c5"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/p7zip/blobs/sha256:6b4bac2c955ef9902583dafa2f9bf6e0e3f5d503c81e51c1ed1ddde01b2ae4df",
+ "sha256": "6b4bac2c955ef9902583dafa2f9bf6e0e3f5d503c81e51c1ed1ddde01b2ae4df"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/p7zip/blobs/sha256:91623462e2bdad09edfa899267359fcfd03ab34d8b70176462b1364e6f23f91c",
+ "sha256": "91623462e2bdad09edfa899267359fcfd03ab34d8b70176462b1364e6f23f91c"
+ },
+ "big_sur": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/p7zip/blobs/sha256:663d0ac5174855af24bf4dd7b729ef5693b7a421327379ba2d210b370f12aef0",
+ "sha256": "663d0ac5174855af24bf4dd7b729ef5693b7a421327379ba2d210b370f12aef0"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/p7zip/blobs/sha256:a421f6e2445fa536da9ab14e83060f3a2949cbdf1e5ac38484339e7b6b22fa04",
+ "sha256": "a421f6e2445fa536da9ab14e83060f3a2949cbdf1e5ac38484339e7b6b22fa04"
+ }
+ }
+ }
+ },
+ "parallel": {
+ "version": "20240322",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "all": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/parallel/blobs/sha256:edb8aaa82c0eb53277f2399a2e9e56ec5633c4879f4827f84977cbd2cc2aa641",
+ "sha256": "edb8aaa82c0eb53277f2399a2e9e56ec5633c4879f4827f84977cbd2cc2aa641"
+ }
+ }
+ }
+ },
+ "pcre": {
+ "version": "8.45",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pcre/blobs/sha256:fbc1ec29701c2c3f0eb750a0aecf03b90acb6d47f1bbf1dc07eb8a7c9340650e",
+ "sha256": "fbc1ec29701c2c3f0eb750a0aecf03b90acb6d47f1bbf1dc07eb8a7c9340650e"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pcre/blobs/sha256:542a6e5dcf5f1ac6592992f949687a56515d154abf1bfdd71327edcfb5183fb6",
+ "sha256": "542a6e5dcf5f1ac6592992f949687a56515d154abf1bfdd71327edcfb5183fb6"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pcre/blobs/sha256:11193fd0a113c0bb330b1c2c21ab6f40d225c1893a451bba85e8a1562b914a1c",
+ "sha256": "11193fd0a113c0bb330b1c2c21ab6f40d225c1893a451bba85e8a1562b914a1c"
+ },
+ "arm64_big_sur": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pcre/blobs/sha256:2d6bfcafce9da9739e32ee433087e69a78cda3f18291350953e6ad260fefc50b",
+ "sha256": "2d6bfcafce9da9739e32ee433087e69a78cda3f18291350953e6ad260fefc50b"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pcre/blobs/sha256:636ad19cc22f9c7608d5be592f8404c67458723d9629dbae026a93b8a3810e39",
+ "sha256": "636ad19cc22f9c7608d5be592f8404c67458723d9629dbae026a93b8a3810e39"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pcre/blobs/sha256:df481fdd99c1dff924ea2d679623512d6c0c275e3b7c223e753ec654994ac6e5",
+ "sha256": "df481fdd99c1dff924ea2d679623512d6c0c275e3b7c223e753ec654994ac6e5"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pcre/blobs/sha256:5e5cc7a5bf8bb6488ec57d4263bf6b0bc89e93252a0a2460f846de29373162d8",
+ "sha256": "5e5cc7a5bf8bb6488ec57d4263bf6b0bc89e93252a0a2460f846de29373162d8"
+ },
+ "big_sur": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pcre/blobs/sha256:fb2fefbe1232706a603a6b385fc37253e5aafaf3536cb68b828ad1940b95e601",
+ "sha256": "fb2fefbe1232706a603a6b385fc37253e5aafaf3536cb68b828ad1940b95e601"
+ },
+ "catalina": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pcre/blobs/sha256:180d88dc2230e98162685b86d00436903db4349aac701f9769997d61adb78418",
+ "sha256": "180d88dc2230e98162685b86d00436903db4349aac701f9769997d61adb78418"
+ },
+ "mojave": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pcre/blobs/sha256:a42b79956773d18c4ac337868cfc15fadadf5e779d65c12ffd6f8fd379b5514c",
+ "sha256": "a42b79956773d18c4ac337868cfc15fadadf5e779d65c12ffd6f8fd379b5514c"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/pcre/blobs/sha256:296026b6d5430399e40fb4f8074045a9a27d5374d83f2f6d4659c2647959f36d",
+ "sha256": "296026b6d5430399e40fb4f8074045a9a27d5374d83f2f6d4659c2647959f36d"
+ }
+ }
+ }
+ },
+ "pg_top": {
+ "version": "3.7.0_4",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pg_top/blobs/sha256:852a0e040171868c8c6c677306c82c81ed1fc52e7cb47413c1ddcb48cf5bb987",
+ "sha256": "852a0e040171868c8c6c677306c82c81ed1fc52e7cb47413c1ddcb48cf5bb987"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pg_top/blobs/sha256:c7d46c3124f4336b96d82dac38fdaf58ecb871587f7e1f1bc52368ab3ba29e78",
+ "sha256": "c7d46c3124f4336b96d82dac38fdaf58ecb871587f7e1f1bc52368ab3ba29e78"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pg_top/blobs/sha256:a157f605a85907c0d04410199dfcc4d7de515844f0ad41bcbcde1b8b771431c8",
+ "sha256": "a157f605a85907c0d04410199dfcc4d7de515844f0ad41bcbcde1b8b771431c8"
+ },
+ "arm64_big_sur": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pg_top/blobs/sha256:506d2459e302e37bac0f38f99cd2cc2d3c3f5fd39631ee540a6f54d59af07f4a",
+ "sha256": "506d2459e302e37bac0f38f99cd2cc2d3c3f5fd39631ee540a6f54d59af07f4a"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pg_top/blobs/sha256:59ad81e7e985e9b841a4667a901e94cadac8923be21654c5918326a230424910",
+ "sha256": "59ad81e7e985e9b841a4667a901e94cadac8923be21654c5918326a230424910"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pg_top/blobs/sha256:825e51d876eb38a90e72413f751b88c291b1da0956c8f07b494da5d51f10ca95",
+ "sha256": "825e51d876eb38a90e72413f751b88c291b1da0956c8f07b494da5d51f10ca95"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pg_top/blobs/sha256:6252dc42f3d6e6570b0371f2f10cd146a06bd52b492636bbb35f62ff07239b7a",
+ "sha256": "6252dc42f3d6e6570b0371f2f10cd146a06bd52b492636bbb35f62ff07239b7a"
+ },
+ "big_sur": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pg_top/blobs/sha256:7980c5af9dec1de3a76a74fbd4b359ec1a90bdd7223fa7ffc8f4294642042fc8",
+ "sha256": "7980c5af9dec1de3a76a74fbd4b359ec1a90bdd7223fa7ffc8f4294642042fc8"
+ },
+ "catalina": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pg_top/blobs/sha256:edf54d452403cf5be9b63a0a744560a00bb9e83ace3885ae33d36d96b0a8c2a4",
+ "sha256": "edf54d452403cf5be9b63a0a744560a00bb9e83ace3885ae33d36d96b0a8c2a4"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/pg_top/blobs/sha256:65fe3861c5e90a4c9403f4b551892cd8ac85fbbea1cc23f551ee0eda3c9de01d",
+ "sha256": "65fe3861c5e90a4c9403f4b551892cd8ac85fbbea1cc23f551ee0eda3c9de01d"
+ }
+ }
+ }
+ },
+ "pkg-config": {
+ "version": "0.29.2_3",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/pkg-config/blobs/sha256:7b59abc0b5381065b1eab174217307af9324e0d02edf903171b29250ae58aeaf",
+ "sha256": "7b59abc0b5381065b1eab174217307af9324e0d02edf903171b29250ae58aeaf"
+ },
+ "arm64_ventura": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/pkg-config/blobs/sha256:3ff612c5e44b945c8c0cc6df7d3edb407ca67cddad9c89f9ab99ced494b7a8c2",
+ "sha256": "3ff612c5e44b945c8c0cc6df7d3edb407ca67cddad9c89f9ab99ced494b7a8c2"
+ },
+ "arm64_monterey": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/pkg-config/blobs/sha256:2af9bceb60b70a259f236f1d46d2bb24c4d0a4af8cd63d974dde4d76313711e0",
+ "sha256": "2af9bceb60b70a259f236f1d46d2bb24c4d0a4af8cd63d974dde4d76313711e0"
+ },
+ "arm64_big_sur": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/pkg-config/blobs/sha256:ffd4491f62201d14b7eca6beff954a2ab265351589cd5b3b79b8bbb414485574",
+ "sha256": "ffd4491f62201d14b7eca6beff954a2ab265351589cd5b3b79b8bbb414485574"
+ },
+ "sonoma": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/pkg-config/blobs/sha256:421571f340277c62c5cc6fd68737bd7c4e085de113452ea49b33bcd46509bb12",
+ "sha256": "421571f340277c62c5cc6fd68737bd7c4e085de113452ea49b33bcd46509bb12"
+ },
+ "ventura": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/pkg-config/blobs/sha256:c44b1544815518726d280d92d6f6df09bd45e41ad20fd43424725c1c20760be8",
+ "sha256": "c44b1544815518726d280d92d6f6df09bd45e41ad20fd43424725c1c20760be8"
+ },
+ "monterey": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/pkg-config/blobs/sha256:a6ba80711f98b65d8a2bf2c9278540860415e9b5e545da338a4d94f39d119285",
+ "sha256": "a6ba80711f98b65d8a2bf2c9278540860415e9b5e545da338a4d94f39d119285"
+ },
+ "big_sur": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/pkg-config/blobs/sha256:0040b6ebe07f60549800b211343fd5fb3cf83c866d9f62e40f5fb2f38b71e161",
+ "sha256": "0040b6ebe07f60549800b211343fd5fb3cf83c866d9f62e40f5fb2f38b71e161"
+ },
+ "catalina": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/pkg-config/blobs/sha256:80f141e695f73bd058fd82e9f539dc67471666ff6800c5e280b5af7d3050f435",
+ "sha256": "80f141e695f73bd058fd82e9f539dc67471666ff6800c5e280b5af7d3050f435"
+ },
+ "mojave": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/pkg-config/blobs/sha256:0d14b797dba0e0ab595c9afba8ab7ef9c901b60b4f806b36580ef95ebb370232",
+ "sha256": "0d14b797dba0e0ab595c9afba8ab7ef9c901b60b4f806b36580ef95ebb370232"
+ },
+ "high_sierra": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/pkg-config/blobs/sha256:8c6160305abd948b8cf3e0d5c6bb0df192fa765bbb9535dda0b573cb60abbe52",
+ "sha256": "8c6160305abd948b8cf3e0d5c6bb0df192fa765bbb9535dda0b573cb60abbe52"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/pkg-config/blobs/sha256:3d9b8bf9b7b4bd08086be1104e3e18afb1c437dfaca03e6e7df8f2710b9c1c1a",
+ "sha256": "3d9b8bf9b7b4bd08086be1104e3e18afb1c437dfaca03e6e7df8f2710b9c1c1a"
+ }
+ }
+ }
+ },
+ "postgresql@15": {
+ "version": "15.6_1",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/15/blobs/sha256:a85110c06097d4cfbc11e0397caf48060d12eac7852556c7eeb99a24885c3fa8",
+ "sha256": "a85110c06097d4cfbc11e0397caf48060d12eac7852556c7eeb99a24885c3fa8"
+ },
+ "arm64_ventura": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/15/blobs/sha256:328bcd13c829c6fe759a038e7dfb17c7af75fd242452a09cb77915a78fa233a4",
+ "sha256": "328bcd13c829c6fe759a038e7dfb17c7af75fd242452a09cb77915a78fa233a4"
+ },
+ "arm64_monterey": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/15/blobs/sha256:5c5e76210be1a35306ace4e75cc0683991fb91b3343223a8f774a71309711ac7",
+ "sha256": "5c5e76210be1a35306ace4e75cc0683991fb91b3343223a8f774a71309711ac7"
+ },
+ "sonoma": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/15/blobs/sha256:0e2af74b4a53cd4f9411c9b18d75041a7954352aa4ff1efe072233ac6771fe0e",
+ "sha256": "0e2af74b4a53cd4f9411c9b18d75041a7954352aa4ff1efe072233ac6771fe0e"
+ },
+ "ventura": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/15/blobs/sha256:627c40901853c1f97ca21293fcadaaa138cbe2d4b390e0d7fa15d8c335ff1cf9",
+ "sha256": "627c40901853c1f97ca21293fcadaaa138cbe2d4b390e0d7fa15d8c335ff1cf9"
+ },
+ "monterey": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/15/blobs/sha256:5a9687c58bae0fc14730eeb7a8d693c9841a9d58066ae8c90cdbeea510de0a51",
+ "sha256": "5a9687c58bae0fc14730eeb7a8d693c9841a9d58066ae8c90cdbeea510de0a51"
+ },
+ "x86_64_linux": {
+ "cellar": "/home/linuxbrew/.linuxbrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/15/blobs/sha256:27403cb27e7201906e1debca1eb01a731637a47eebd3aa56d9bc4dcc4702766f",
+ "sha256": "27403cb27e7201906e1debca1eb01a731637a47eebd3aa56d9bc4dcc4702766f"
+ }
+ }
+ }
+ },
+ "pstree": {
+ "version": "2.40",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/pstree/blobs/sha256:05dcf81f64516f4b96ccb2a82fe610e65d2733d0cfb6dbff41b54fcd9f45f111",
+ "sha256": "05dcf81f64516f4b96ccb2a82fe610e65d2733d0cfb6dbff41b54fcd9f45f111"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/pstree/blobs/sha256:ca06848c6042d6f8c7ee44477aae9d5e1ed8f73be77dc99d9ec126460bc1f9f8",
+ "sha256": "ca06848c6042d6f8c7ee44477aae9d5e1ed8f73be77dc99d9ec126460bc1f9f8"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/pstree/blobs/sha256:e43ea23b1cc41dbd5717b22c8de73faae3fa58e88a9f18845533e7f4acc24eeb",
+ "sha256": "e43ea23b1cc41dbd5717b22c8de73faae3fa58e88a9f18845533e7f4acc24eeb"
+ },
+ "arm64_big_sur": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/pstree/blobs/sha256:bc1765755ab89e61a17983692eb4ceb6c659f2f90b1f26bfea0ed1a908a7dc07",
+ "sha256": "bc1765755ab89e61a17983692eb4ceb6c659f2f90b1f26bfea0ed1a908a7dc07"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/pstree/blobs/sha256:ceea272a5221af7418627c38fb6c310e65bdd316ca201d06861e1ddb5314e570",
+ "sha256": "ceea272a5221af7418627c38fb6c310e65bdd316ca201d06861e1ddb5314e570"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/pstree/blobs/sha256:b95d35e5b4f3bb8953ccec2ee1e3f25fdd14ed942606de9f4abcd9b2dfa31a5b",
+ "sha256": "b95d35e5b4f3bb8953ccec2ee1e3f25fdd14ed942606de9f4abcd9b2dfa31a5b"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/pstree/blobs/sha256:fc23e46dee144842b941ad5b6527018154d38b67827e4f019bf9efab24a15365",
+ "sha256": "fc23e46dee144842b941ad5b6527018154d38b67827e4f019bf9efab24a15365"
+ },
+ "big_sur": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/pstree/blobs/sha256:bf6f7f6e9a1ec7b0e5454e15973ee091a143eb887c67d81b07f262c447c685b7",
+ "sha256": "bf6f7f6e9a1ec7b0e5454e15973ee091a143eb887c67d81b07f262c447c685b7"
+ },
+ "catalina": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/pstree/blobs/sha256:820b3dd1b26142457348dfc27c29ad8f1b6d86367995d8895ff41d8c74f91c8a",
+ "sha256": "820b3dd1b26142457348dfc27c29ad8f1b6d86367995d8895ff41d8c74f91c8a"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/pstree/blobs/sha256:d65aff524b410c2ea45556fad2e5b07b9052896c07ee386fc6213208fdc7bc43",
+ "sha256": "d65aff524b410c2ea45556fad2e5b07b9052896c07ee386fc6213208fdc7bc43"
+ }
+ }
+ }
+ },
+ "pyenv": {
+ "version": "2.4.0",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pyenv/blobs/sha256:423e0c467f4c0e07f093132d036790679752c6ec3e150966f448284e8666870d",
+ "sha256": "423e0c467f4c0e07f093132d036790679752c6ec3e150966f448284e8666870d"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pyenv/blobs/sha256:d36e0a02a291c69a97adefac255bb3ac051d4db2330991567e0f642335bd312f",
+ "sha256": "d36e0a02a291c69a97adefac255bb3ac051d4db2330991567e0f642335bd312f"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pyenv/blobs/sha256:c864a789872d8febd82456fbe97ee9f61f3f99b0d8624f46a418502fc8970526",
+ "sha256": "c864a789872d8febd82456fbe97ee9f61f3f99b0d8624f46a418502fc8970526"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pyenv/blobs/sha256:717187999c534627f41052dcfa3d740bcc8cc0e3b10242ee8755d00e8b70d683",
+ "sha256": "717187999c534627f41052dcfa3d740bcc8cc0e3b10242ee8755d00e8b70d683"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pyenv/blobs/sha256:3833eb68cf61a81be46cf8fdfb1f919817c6d433455f3feb0dd199470ad758b3",
+ "sha256": "3833eb68cf61a81be46cf8fdfb1f919817c6d433455f3feb0dd199470ad758b3"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/pyenv/blobs/sha256:825f2204bb2e642d6fc883db15ec0ef5873ceff81c6c37d3f8a89f00791d3c94",
+ "sha256": "825f2204bb2e642d6fc883db15ec0ef5873ceff81c6c37d3f8a89f00791d3c94"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/pyenv/blobs/sha256:7c2dc4ac138eb5d65d88ac65b84d0b572e66b2439e0ca4d16a8db943e418386a",
+ "sha256": "7c2dc4ac138eb5d65d88ac65b84d0b572e66b2439e0ca4d16a8db943e418386a"
+ }
+ }
+ }
+ },
+ "rbenv": {
+ "version": "1.2.0",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/rbenv/blobs/sha256:8b4c091ff01a423d4c091cfde63243341517694014430d248c2b9fa6efeda8a7",
+ "sha256": "8b4c091ff01a423d4c091cfde63243341517694014430d248c2b9fa6efeda8a7"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/rbenv/blobs/sha256:09bccc5974bd7b23f60a42c94bf7bc7d0e605cf4ef1f4068f63a1fe905bc5c74",
+ "sha256": "09bccc5974bd7b23f60a42c94bf7bc7d0e605cf4ef1f4068f63a1fe905bc5c74"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/rbenv/blobs/sha256:dede9454bc8a665ac2b1858a0522fb77d95deebb5db7437918cfb056ff119b16",
+ "sha256": "dede9454bc8a665ac2b1858a0522fb77d95deebb5db7437918cfb056ff119b16"
+ },
+ "arm64_big_sur": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/rbenv/blobs/sha256:d5e6168ad6ab8843946273319fc6949b322c80f2d666a6bdda62466e256e6746",
+ "sha256": "d5e6168ad6ab8843946273319fc6949b322c80f2d666a6bdda62466e256e6746"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/rbenv/blobs/sha256:59a2e9120361bc20b5c3fe8122438e5e43ee00e475ea6730fe507fda2de6d7ab",
+ "sha256": "59a2e9120361bc20b5c3fe8122438e5e43ee00e475ea6730fe507fda2de6d7ab"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/rbenv/blobs/sha256:e654c2cf9b9966093b2d045cb9b12dbd32a7cd8926194838e13ee7d17184b1f5",
+ "sha256": "e654c2cf9b9966093b2d045cb9b12dbd32a7cd8926194838e13ee7d17184b1f5"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/rbenv/blobs/sha256:42657e04e2d1e8bf9abb9c5f0ba50e567df95f93a2a212491f005e4bd0ad9cee",
+ "sha256": "42657e04e2d1e8bf9abb9c5f0ba50e567df95f93a2a212491f005e4bd0ad9cee"
+ },
+ "big_sur": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/rbenv/blobs/sha256:8a1b159909d472cc461d0a9b85a192a31ab58860e34f022fcbb33175732d24aa",
+ "sha256": "8a1b159909d472cc461d0a9b85a192a31ab58860e34f022fcbb33175732d24aa"
+ },
+ "catalina": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/rbenv/blobs/sha256:a2ca52c4fe3b7000d9f84f81836ddcb9b3aea9c20ee092dd71c1e10cf3a6a19a",
+ "sha256": "a2ca52c4fe3b7000d9f84f81836ddcb9b3aea9c20ee092dd71c1e10cf3a6a19a"
+ },
+ "mojave": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/rbenv/blobs/sha256:87ca53a9f4f84aff56ccbf2f823f903d20bc6669dde548018892857cc8871936",
+ "sha256": "87ca53a9f4f84aff56ccbf2f823f903d20bc6669dde548018892857cc8871936"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/rbenv/blobs/sha256:f4be8e4efef32c1fcdaa585312b3262d33b3306d9d7d9c75abd1230227b10bb7",
+ "sha256": "f4be8e4efef32c1fcdaa585312b3262d33b3306d9d7d9c75abd1230227b10bb7"
+ }
+ }
+ }
+ },
+ "readline": {
+ "version": "8.2.10",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/readline/blobs/sha256:713fd1fa8544426b7e97eb21d13153195fea4c407db8a174bd183777b81c9192",
+ "sha256": "713fd1fa8544426b7e97eb21d13153195fea4c407db8a174bd183777b81c9192"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/readline/blobs/sha256:90351660d5ceca72a4c0a287555f2045db95f78aa5f65011b94213429f729cde",
+ "sha256": "90351660d5ceca72a4c0a287555f2045db95f78aa5f65011b94213429f729cde"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/readline/blobs/sha256:e58bc8376c36602c3cedf94075bb1097b04b77438c5a946fdbd37bf0eb6579c2",
+ "sha256": "e58bc8376c36602c3cedf94075bb1097b04b77438c5a946fdbd37bf0eb6579c2"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/readline/blobs/sha256:9796e0ff1cc29ae7e75d8fc1a3e2c5e8ae2aeade8d9d59a16363306bf6c5b8f4",
+ "sha256": "9796e0ff1cc29ae7e75d8fc1a3e2c5e8ae2aeade8d9d59a16363306bf6c5b8f4"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/readline/blobs/sha256:952e2975dffc98bd35673c86474dbb91fadc8d993c0720e4f085597f7a484af9",
+ "sha256": "952e2975dffc98bd35673c86474dbb91fadc8d993c0720e4f085597f7a484af9"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/readline/blobs/sha256:3633320dce51662036ea90acfc9adf5bb5e6f1dca7dbdb539839736129c474b0",
+ "sha256": "3633320dce51662036ea90acfc9adf5bb5e6f1dca7dbdb539839736129c474b0"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/readline/blobs/sha256:65181d2c0a9bd1d91ded6f7ec4a69b1110f65e875b332947e86a30aed7eab20f",
+ "sha256": "65181d2c0a9bd1d91ded6f7ec4a69b1110f65e875b332947e86a30aed7eab20f"
+ }
+ }
+ }
+ },
+ "redis": {
+ "version": "7.2.4",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:7840031cf7bb94c62d6e42b6e730e8447c31ae37e3564a43a772fb8e6e0e51cf",
+ "sha256": "7840031cf7bb94c62d6e42b6e730e8447c31ae37e3564a43a772fb8e6e0e51cf"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:975f8c13a24d1b0f4a8e0f3c9ea2338d209ec9bcfcebd3130d0a72c3e4809c58",
+ "sha256": "975f8c13a24d1b0f4a8e0f3c9ea2338d209ec9bcfcebd3130d0a72c3e4809c58"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:ac32435ac27d8a061ee32ba88cf842ee3dff64a85803aa4d6a65d841e32ccbbd",
+ "sha256": "ac32435ac27d8a061ee32ba88cf842ee3dff64a85803aa4d6a65d841e32ccbbd"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:09767dffd13dd62aed6bb904f35946c3b9dae2db58cc884dc179d6e12b573673",
+ "sha256": "09767dffd13dd62aed6bb904f35946c3b9dae2db58cc884dc179d6e12b573673"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:3ce1ca917e08acf3cad5023e0d7184505be327f797f18eb692711325d8c540c9",
+ "sha256": "3ce1ca917e08acf3cad5023e0d7184505be327f797f18eb692711325d8c540c9"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:357f32b7bbe42ae1323d693bebd756f1589ae38a03e25f1a217fdd870b301797",
+ "sha256": "357f32b7bbe42ae1323d693bebd756f1589ae38a03e25f1a217fdd870b301797"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:92d2c8978df576d27b03b8f806136eaa920329c0fec4f4606f1a07abaad6c353",
+ "sha256": "92d2c8978df576d27b03b8f806136eaa920329c0fec4f4606f1a07abaad6c353"
+ }
+ }
+ }
+ },
+ "ripgrep": {
+ "version": "14.1.0",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/ripgrep/blobs/sha256:22cc1f3423a7fddb550fb94bd2715ce5455076d17f2c88ef0c157749ea4b87d6",
+ "sha256": "22cc1f3423a7fddb550fb94bd2715ce5455076d17f2c88ef0c157749ea4b87d6"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/ripgrep/blobs/sha256:837aaf1b32879f1177f9599e67d73a7f474d25ad5d3ba053216b05cbf8539b2a",
+ "sha256": "837aaf1b32879f1177f9599e67d73a7f474d25ad5d3ba053216b05cbf8539b2a"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/ripgrep/blobs/sha256:9a6e788f0a35d38ed325c7880e772775fe04c61e27c3506785ce10f6095ec891",
+ "sha256": "9a6e788f0a35d38ed325c7880e772775fe04c61e27c3506785ce10f6095ec891"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/ripgrep/blobs/sha256:35ee71e72b612f0cc7748ff0e58b4cdfeec0693c83df6f553d9be1160cc7ba74",
+ "sha256": "35ee71e72b612f0cc7748ff0e58b4cdfeec0693c83df6f553d9be1160cc7ba74"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/ripgrep/blobs/sha256:42eaa5b6b69a460c31c859c44b263d651e649d6eae4478651b09e155a14faf64",
+ "sha256": "42eaa5b6b69a460c31c859c44b263d651e649d6eae4478651b09e155a14faf64"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/ripgrep/blobs/sha256:69c2e49f5d8054e1d2efb6e77aa8f83183b6bcfd6470354da30a2bfb251edd00",
+ "sha256": "69c2e49f5d8054e1d2efb6e77aa8f83183b6bcfd6470354da30a2bfb251edd00"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/ripgrep/blobs/sha256:bf2810ab20dc5006c02d9ced344bb47f1c2e5770ae051c35f81faaa34fe48d9d",
+ "sha256": "bf2810ab20dc5006c02d9ced344bb47f1c2e5770ae051c35f81faaa34fe48d9d"
+ }
+ }
+ }
+ },
+ "rsync": {
+ "version": "3.3.0",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/rsync/blobs/sha256:aedcc676dfc2ba721d7e780a852525c3a96ab045ff5225b72ca31b0248aa5abc",
+ "sha256": "aedcc676dfc2ba721d7e780a852525c3a96ab045ff5225b72ca31b0248aa5abc"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/rsync/blobs/sha256:800017d5ed8d03f6c0ff9e45830b09d5fd709ad1cf565b056782a65aef061769",
+ "sha256": "800017d5ed8d03f6c0ff9e45830b09d5fd709ad1cf565b056782a65aef061769"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/rsync/blobs/sha256:87da2d08d06cc90b4087e8ef50418a9ec3a0ceecb23e1ec75ffad2b411c5400a",
+ "sha256": "87da2d08d06cc90b4087e8ef50418a9ec3a0ceecb23e1ec75ffad2b411c5400a"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/rsync/blobs/sha256:5ef1e7552fe1fe4fcbf336c83313033a544768d4f8e7cc166ea4f91c2867c35b",
+ "sha256": "5ef1e7552fe1fe4fcbf336c83313033a544768d4f8e7cc166ea4f91c2867c35b"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/rsync/blobs/sha256:c0d2c0d516ca8ed44cdd846a9066b5b9748ce56dc26fc0be07f057f15e730460",
+ "sha256": "c0d2c0d516ca8ed44cdd846a9066b5b9748ce56dc26fc0be07f057f15e730460"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/rsync/blobs/sha256:502081be38ed754a5fce7c4a38bfb2aaad99923ffa27a9fe4b13e3e0df7635c5",
+ "sha256": "502081be38ed754a5fce7c4a38bfb2aaad99923ffa27a9fe4b13e3e0df7635c5"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/rsync/blobs/sha256:db52f38e89890b3c5b50931db4d3910984dbc8180466c250495f3e7b579a7366",
+ "sha256": "db52f38e89890b3c5b50931db4d3910984dbc8180466c250495f3e7b579a7366"
+ }
+ }
+ }
+ },
+ "ruby-build": {
+ "version": "20240319",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "all": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/ruby-build/blobs/sha256:48a2fa2d1e3a6356ed64cdc420251ce5f6e0ee33a02b9f133603f30ca6ddb952",
+ "sha256": "48a2fa2d1e3a6356ed64cdc420251ce5f6e0ee33a02b9f133603f30ca6ddb952"
+ }
+ }
+ }
+ },
+ "ruby-completion": {
+ "version": "1.0.0",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "all": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/ruby-completion/blobs/sha256:ddef10f9066b45d6d50d2795bfc16b7fbc528145db20e6a7c7714e2a26e3ac83",
+ "sha256": "ddef10f9066b45d6d50d2795bfc16b7fbc528145db20e6a7c7714e2a26e3ac83"
+ }
+ }
+ }
+ },
+ "shellcheck": {
+ "version": "0.10.0",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/shellcheck/blobs/sha256:ef742b6992cfcdcd7289718ac64b27174e421d29ce3ad9b81e1856349059b117",
+ "sha256": "ef742b6992cfcdcd7289718ac64b27174e421d29ce3ad9b81e1856349059b117"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/shellcheck/blobs/sha256:6e60ee03edb09ac5bc852b8eb813849fa654400e21ffb4c746989678172f5a26",
+ "sha256": "6e60ee03edb09ac5bc852b8eb813849fa654400e21ffb4c746989678172f5a26"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/shellcheck/blobs/sha256:d5e8407806dbf757e71930ce2cb9b0d23bae286f0c058d9ff246d851dd7aa871",
+ "sha256": "d5e8407806dbf757e71930ce2cb9b0d23bae286f0c058d9ff246d851dd7aa871"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/shellcheck/blobs/sha256:b53cf1e5464406ee49743fc2db84850b6d34d3a2098cf729e629b23f9d6dd6e0",
+ "sha256": "b53cf1e5464406ee49743fc2db84850b6d34d3a2098cf729e629b23f9d6dd6e0"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/shellcheck/blobs/sha256:15ba88c48a5ae3b08e085791e3c5e514d9d78ce88414c96bd21ed33f29fb4aca",
+ "sha256": "15ba88c48a5ae3b08e085791e3c5e514d9d78ce88414c96bd21ed33f29fb4aca"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/shellcheck/blobs/sha256:b3d14cb62e325d0f7221cd24a7fb4533936feae4ed4dce00e8983ec6e55123f8",
+ "sha256": "b3d14cb62e325d0f7221cd24a7fb4533936feae4ed4dce00e8983ec6e55123f8"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/shellcheck/blobs/sha256:6d0867f144686a5caa025cb15ecac49286654b78e7b89979a54eedc9a0cc9b6b",
+ "sha256": "6d0867f144686a5caa025cb15ecac49286654b78e7b89979a54eedc9a0cc9b6b"
+ }
+ }
+ }
+ },
+ "tree": {
+ "version": "2.1.1_1",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/tree/blobs/sha256:2d1c490c83719c983ec360085e9d0049418ff424259bc00122869f8acf68ed63",
+ "sha256": "2d1c490c83719c983ec360085e9d0049418ff424259bc00122869f8acf68ed63"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/tree/blobs/sha256:13b597dcee0eec0e8d3a7f864dfb5713d812605092bf1e417c765e788d0c0d31",
+ "sha256": "13b597dcee0eec0e8d3a7f864dfb5713d812605092bf1e417c765e788d0c0d31"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/tree/blobs/sha256:0bd195b460f491c6e71b0277efb3c4cdbab8b6d814072519ade39e5ca257b048",
+ "sha256": "0bd195b460f491c6e71b0277efb3c4cdbab8b6d814072519ade39e5ca257b048"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/tree/blobs/sha256:af3d14eb91e4bf756bb5ef5f6a489aeb33e6cf5fc4f72c99d70352bec364e282",
+ "sha256": "af3d14eb91e4bf756bb5ef5f6a489aeb33e6cf5fc4f72c99d70352bec364e282"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/tree/blobs/sha256:da304661d82c58ee3d4a14f80479d15d3405ca4c1be78b6085f7c62e67f79412",
+ "sha256": "da304661d82c58ee3d4a14f80479d15d3405ca4c1be78b6085f7c62e67f79412"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/tree/blobs/sha256:13a0875d7da74de5ccfd1c6d3bd6167d2c4c0d7d4d747cc3ebb377fad60df365",
+ "sha256": "13a0875d7da74de5ccfd1c6d3bd6167d2c4c0d7d4d747cc3ebb377fad60df365"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/tree/blobs/sha256:48bf95e7ef6c5f14db8a551b3ba22db93613f39ad04985f2edd3d34754daf89e",
+ "sha256": "48bf95e7ef6c5f14db8a551b3ba22db93613f39ad04985f2edd3d34754daf89e"
+ }
+ }
+ }
+ },
+ "vim": {
+ "version": "9.1.0250",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/vim/blobs/sha256:4dcdf1844e915117b11941a6664b0c2a673c7ee1924423b9fc6c110b326e4a7c",
+ "sha256": "4dcdf1844e915117b11941a6664b0c2a673c7ee1924423b9fc6c110b326e4a7c"
+ },
+ "arm64_ventura": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/vim/blobs/sha256:0ba2022b0a6a275cad59401ad92275c2070e22f9cb9cba2dbf721be7d2f0d28e",
+ "sha256": "0ba2022b0a6a275cad59401ad92275c2070e22f9cb9cba2dbf721be7d2f0d28e"
+ },
+ "arm64_monterey": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/vim/blobs/sha256:4f6ce058ad9c008aaf759a3de3ad6e33c0e6d251c52eec74956d878349542cde",
+ "sha256": "4f6ce058ad9c008aaf759a3de3ad6e33c0e6d251c52eec74956d878349542cde"
+ },
+ "sonoma": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/vim/blobs/sha256:6e926b97cceeaf7e51228bd810fa88b27ee988f431f3eaef84deca76d738621f",
+ "sha256": "6e926b97cceeaf7e51228bd810fa88b27ee988f431f3eaef84deca76d738621f"
+ },
+ "ventura": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/vim/blobs/sha256:1cacd2cf1a9aa26e699c04868b8df35c4f94830a3a61432194c01aba4b6253a0",
+ "sha256": "1cacd2cf1a9aa26e699c04868b8df35c4f94830a3a61432194c01aba4b6253a0"
+ },
+ "monterey": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/vim/blobs/sha256:368b2b82898d57691174ce46d530aaaf85f08d3c16605e47a5589d9261ea0425",
+ "sha256": "368b2b82898d57691174ce46d530aaaf85f08d3c16605e47a5589d9261ea0425"
+ },
+ "x86_64_linux": {
+ "cellar": "/home/linuxbrew/.linuxbrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/vim/blobs/sha256:96554443b485ade05f6177178219702d04b319f59be8ae487b3b30bcba5bb7ea",
+ "sha256": "96554443b485ade05f6177178219702d04b319f59be8ae487b3b30bcba5bb7ea"
+ }
+ }
+ }
+ },
+ "watch": {
+ "version": "4.0.4",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/watch/blobs/sha256:9d8b55c73a9b913186f6ef8dc7642e8a718b5edea93fc3301fff5f44ad42fe90",
+ "sha256": "9d8b55c73a9b913186f6ef8dc7642e8a718b5edea93fc3301fff5f44ad42fe90"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/watch/blobs/sha256:f2e3977aacd949425257bb08b9ed66125e4cdff76a6e5b2464718139bc966d8c",
+ "sha256": "f2e3977aacd949425257bb08b9ed66125e4cdff76a6e5b2464718139bc966d8c"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/watch/blobs/sha256:5f7ea5b77d12731688f4e2e72e8190f70c62763d4bdb94e8c30ea1c0625db9d6",
+ "sha256": "5f7ea5b77d12731688f4e2e72e8190f70c62763d4bdb94e8c30ea1c0625db9d6"
+ },
+ "arm64_big_sur": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/watch/blobs/sha256:3aac6404005a0953a1126687829863e19fa4d0f02acc4e58d8d099615bd9d014",
+ "sha256": "3aac6404005a0953a1126687829863e19fa4d0f02acc4e58d8d099615bd9d014"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/watch/blobs/sha256:58baecc442fe806ece26dcd2c055532f226b8a06a732d32392c0858c56a6ac67",
+ "sha256": "58baecc442fe806ece26dcd2c055532f226b8a06a732d32392c0858c56a6ac67"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/watch/blobs/sha256:80193cc3557144f620767de324af7f45bd0717496b81d8d09f811cf0e9e7397c",
+ "sha256": "80193cc3557144f620767de324af7f45bd0717496b81d8d09f811cf0e9e7397c"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/watch/blobs/sha256:f52987abe01c3e3a09c5608d02fd8a4714632f4256ae58c79d4a32f41e42669b",
+ "sha256": "f52987abe01c3e3a09c5608d02fd8a4714632f4256ae58c79d4a32f41e42669b"
+ },
+ "big_sur": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/watch/blobs/sha256:d61077f4bffe12e0132a86c138630d2c422932272a61959ab1a01e8b7c244edb",
+ "sha256": "d61077f4bffe12e0132a86c138630d2c422932272a61959ab1a01e8b7c244edb"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/watch/blobs/sha256:03aa0061c8707c4d31402f1697429c7619e08e29221de08eed00ec9a26d3bc1e",
+ "sha256": "03aa0061c8707c4d31402f1697429c7619e08e29221de08eed00ec9a26d3bc1e"
+ }
+ }
+ }
+ },
+ "wget": {
+ "version": "1.24.5",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/wget/blobs/sha256:9befdad158e59763fb0622083974a6252878019702d8c961e1bec3a5f5305339",
+ "sha256": "9befdad158e59763fb0622083974a6252878019702d8c961e1bec3a5f5305339"
+ },
+ "arm64_ventura": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/wget/blobs/sha256:ac4c0330b70dae06eaa8065bfbea78dda277699d1ae8002478017a1bd9cf1908",
+ "sha256": "ac4c0330b70dae06eaa8065bfbea78dda277699d1ae8002478017a1bd9cf1908"
+ },
+ "arm64_monterey": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/wget/blobs/sha256:02313702fc03880f221d60ce4d0b652c8b44fe68c15609329d757d031bce6bc4",
+ "sha256": "02313702fc03880f221d60ce4d0b652c8b44fe68c15609329d757d031bce6bc4"
+ },
+ "sonoma": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/wget/blobs/sha256:034528edb247df85f90997aca6a51ddb988a880af6bb571b8473de1702a887af",
+ "sha256": "034528edb247df85f90997aca6a51ddb988a880af6bb571b8473de1702a887af"
+ },
+ "ventura": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/wget/blobs/sha256:1b7e2f76c90553543a5e25dadf031c6fcfe280f52bf27d89e04006f9d33fd20b",
+ "sha256": "1b7e2f76c90553543a5e25dadf031c6fcfe280f52bf27d89e04006f9d33fd20b"
+ },
+ "monterey": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/wget/blobs/sha256:ffc49a5064a003006e69f51434ac5f7ec4f4019c161ad32fab22c32697db61cd",
+ "sha256": "ffc49a5064a003006e69f51434ac5f7ec4f4019c161ad32fab22c32697db61cd"
+ },
+ "x86_64_linux": {
+ "cellar": "/home/linuxbrew/.linuxbrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/wget/blobs/sha256:6a4642964fe5c4d1cc8cd3507541736d5b984e34a303a814ef550d4f2f8242f9",
+ "sha256": "6a4642964fe5c4d1cc8cd3507541736d5b984e34a303a814ef550d4f2f8242f9"
+ }
+ }
+ }
+ },
+ "yamlfmt": {
+ "version": "0.11.0",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/yamlfmt/blobs/sha256:e0b8c469f4ea5763210196cac0559cf4af4dc55be045fd64d601163a40b4bd7a",
+ "sha256": "e0b8c469f4ea5763210196cac0559cf4af4dc55be045fd64d601163a40b4bd7a"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/yamlfmt/blobs/sha256:47b6975bcb89b9036bd675036b5b336b60a3f87a7bf4e7c8d2bf06ca85b3d0ba",
+ "sha256": "47b6975bcb89b9036bd675036b5b336b60a3f87a7bf4e7c8d2bf06ca85b3d0ba"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/yamlfmt/blobs/sha256:b7c5e22e842081e375399c15e8e7ccf037561e2bac51b2bb259d4c9c7212165f",
+ "sha256": "b7c5e22e842081e375399c15e8e7ccf037561e2bac51b2bb259d4c9c7212165f"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/yamlfmt/blobs/sha256:e10c21d430b641d39c0798f2c890f7628b44d42ab4f99229fed936b2b38c9a9c",
+ "sha256": "e10c21d430b641d39c0798f2c890f7628b44d42ab4f99229fed936b2b38c9a9c"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/yamlfmt/blobs/sha256:30eee693734fc1f71bf2c67b1bc7e4dfaccdf64e8b1ad2997719000961fdbaea",
+ "sha256": "30eee693734fc1f71bf2c67b1bc7e4dfaccdf64e8b1ad2997719000961fdbaea"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/yamlfmt/blobs/sha256:d68f766149e173cc19dd3275c19e931d09add173f9c6e6b4c66b0679e41c77dd",
+ "sha256": "d68f766149e173cc19dd3275c19e931d09add173f9c6e6b4c66b0679e41c77dd"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/yamlfmt/blobs/sha256:7d1bfa0101976985cb399566af4e0f1b0921b484618d04dd32372d0ff308c34d",
+ "sha256": "7d1bfa0101976985cb399566af4e0f1b0921b484618d04dd32372d0ff308c34d"
+ }
+ }
+ }
+ },
+ "yamllint": {
+ "version": "1.35.1",
+ "bottle": {
+ "rebuild": 1,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/yamllint/blobs/sha256:f4c04876473720910f72e135311c05d3fefe3208bba7d90e7904cbbc2f154051",
+ "sha256": "f4c04876473720910f72e135311c05d3fefe3208bba7d90e7904cbbc2f154051"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/yamllint/blobs/sha256:55c913a5745760a8c12f537a3c67fa1e97dd8dbc8e8ceb4ef7bc87d27b116279",
+ "sha256": "55c913a5745760a8c12f537a3c67fa1e97dd8dbc8e8ceb4ef7bc87d27b116279"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/yamllint/blobs/sha256:2698561d2192e10a5566a2168f6ec3ded2bc5416970967b3667408d144b6e497",
+ "sha256": "2698561d2192e10a5566a2168f6ec3ded2bc5416970967b3667408d144b6e497"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/yamllint/blobs/sha256:b7b45b6cef591f8f02529e510315543830b33e4de4827fbffb1deed4c8e1c30c",
+ "sha256": "b7b45b6cef591f8f02529e510315543830b33e4de4827fbffb1deed4c8e1c30c"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/yamllint/blobs/sha256:9b3b376d46761939812fb46be383f62dda5370417a6968bef9043ea701dd8be9",
+ "sha256": "9b3b376d46761939812fb46be383f62dda5370417a6968bef9043ea701dd8be9"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/yamllint/blobs/sha256:abfd6d24311132e574b45598a59b44f07d48dc35f5e383eaad45f793fd49fc04",
+ "sha256": "abfd6d24311132e574b45598a59b44f07d48dc35f5e383eaad45f793fd49fc04"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/yamllint/blobs/sha256:07a1c2f2e7b0f719576acf927daf06638605e873bc72ccc862ca3e0bf28faf64",
+ "sha256": "07a1c2f2e7b0f719576acf927daf06638605e873bc72ccc862ca3e0bf28faf64"
+ }
+ }
+ }
+ },
+ "ydiff": {
+ "version": "1.3",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/ydiff/blobs/sha256:71785c20c88c11d4ad640dc8b166f23daa74a95d95c274ab5c0028a0f42499a8",
+ "sha256": "71785c20c88c11d4ad640dc8b166f23daa74a95d95c274ab5c0028a0f42499a8"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/ydiff/blobs/sha256:71785c20c88c11d4ad640dc8b166f23daa74a95d95c274ab5c0028a0f42499a8",
+ "sha256": "71785c20c88c11d4ad640dc8b166f23daa74a95d95c274ab5c0028a0f42499a8"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/ydiff/blobs/sha256:71785c20c88c11d4ad640dc8b166f23daa74a95d95c274ab5c0028a0f42499a8",
+ "sha256": "71785c20c88c11d4ad640dc8b166f23daa74a95d95c274ab5c0028a0f42499a8"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/ydiff/blobs/sha256:71785c20c88c11d4ad640dc8b166f23daa74a95d95c274ab5c0028a0f42499a8",
+ "sha256": "71785c20c88c11d4ad640dc8b166f23daa74a95d95c274ab5c0028a0f42499a8"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/ydiff/blobs/sha256:71785c20c88c11d4ad640dc8b166f23daa74a95d95c274ab5c0028a0f42499a8",
+ "sha256": "71785c20c88c11d4ad640dc8b166f23daa74a95d95c274ab5c0028a0f42499a8"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/ydiff/blobs/sha256:71785c20c88c11d4ad640dc8b166f23daa74a95d95c274ab5c0028a0f42499a8",
+ "sha256": "71785c20c88c11d4ad640dc8b166f23daa74a95d95c274ab5c0028a0f42499a8"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/ydiff/blobs/sha256:e7d5d05030eb906207ab27c47e708de68ee5b5a037e86b7f7300cafc46a69071",
+ "sha256": "e7d5d05030eb906207ab27c47e708de68ee5b5a037e86b7f7300cafc46a69071"
+ }
+ }
+ }
+ },
+ "yq": {
+ "version": "4.43.1",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/yq/blobs/sha256:bc36b4b41929e9e689befbecb557dbf7acf6c743ca17809f65a109ef23833c0b",
+ "sha256": "bc36b4b41929e9e689befbecb557dbf7acf6c743ca17809f65a109ef23833c0b"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/yq/blobs/sha256:ee8073f931c90c1caacc020a6b05cf7bee819ea7135890b7626601ad1787b4bb",
+ "sha256": "ee8073f931c90c1caacc020a6b05cf7bee819ea7135890b7626601ad1787b4bb"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/yq/blobs/sha256:1d652cf11ad65dac1d8c772168f62ca6e672ee61f69f9c47b5a46819089f1cfe",
+ "sha256": "1d652cf11ad65dac1d8c772168f62ca6e672ee61f69f9c47b5a46819089f1cfe"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/yq/blobs/sha256:3f23e27ff4f8ea8a39b07ae5b7d808d5a5cbc548124b56154c0b08585737eb23",
+ "sha256": "3f23e27ff4f8ea8a39b07ae5b7d808d5a5cbc548124b56154c0b08585737eb23"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/yq/blobs/sha256:ccbd38a9b07256344d78bd127fb66f4d2b0f4831385d7458f5e36bed8f796548",
+ "sha256": "ccbd38a9b07256344d78bd127fb66f4d2b0f4831385d7458f5e36bed8f796548"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/yq/blobs/sha256:85a5394913a5734cef1fc388eee37e4dfb21c69e4414c8c658b8e04cb9963262",
+ "sha256": "85a5394913a5734cef1fc388eee37e4dfb21c69e4414c8c658b8e04cb9963262"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/yq/blobs/sha256:8642969ca0738f0a4e632ee2877edf601e2747220460b29e8ab3368ff3e80a0e",
+ "sha256": "8642969ca0738f0a4e632ee2877edf601e2747220460b29e8ab3368ff3e80a0e"
+ }
+ }
+ }
+ },
+ "zlib": {
+ "version": "1.3.1",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/zlib/blobs/sha256:f867540472a59ab3fb1201625df546593e5fae2e98948c4c16c6154b0468b682",
+ "sha256": "f867540472a59ab3fb1201625df546593e5fae2e98948c4c16c6154b0468b682"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/zlib/blobs/sha256:9033eedbd240076116fea9fa181882e75edee7fe0c5d2e3850258a185c52792f",
+ "sha256": "9033eedbd240076116fea9fa181882e75edee7fe0c5d2e3850258a185c52792f"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/zlib/blobs/sha256:ebf10e203575beb64d6a8637ec2dc31774fa3141cfccab8ae7039f88b9efa7f6",
+ "sha256": "ebf10e203575beb64d6a8637ec2dc31774fa3141cfccab8ae7039f88b9efa7f6"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/zlib/blobs/sha256:217f4245cd1da65a3388f512530089f526cd63a38d49ee5f29a90576dfeb3bb7",
+ "sha256": "217f4245cd1da65a3388f512530089f526cd63a38d49ee5f29a90576dfeb3bb7"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/zlib/blobs/sha256:6012d7831245716d8507da3d1eb14ad274f8aa0b71b59275fe6bbbd6cebd787f",
+ "sha256": "6012d7831245716d8507da3d1eb14ad274f8aa0b71b59275fe6bbbd6cebd787f"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/zlib/blobs/sha256:56bbfa3d7bd6a5ccf17ffa53ab926e67f24e74bd64b4740b56fd96c312e37c44",
+ "sha256": "56bbfa3d7bd6a5ccf17ffa53ab926e67f24e74bd64b4740b56fd96c312e37c44"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/zlib/blobs/sha256:38f2469db2ce63b70855a98e5ee27b5b5a92874e52542cbdc0b230bba1e7195f",
+ "sha256": "38f2469db2ce63b70855a98e5ee27b5b5a92874e52542cbdc0b230bba1e7195f"
+ }
+ }
+ }
+ },
+ "v8": {
+ "version": "12.1.285.24",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/v8/blobs/sha256:f32a5a022378b428cea1b649a34fd55d8c7edc443e1526a701314a993eca4f08",
+ "sha256": "f32a5a022378b428cea1b649a34fd55d8c7edc443e1526a701314a993eca4f08"
+ },
+ "arm64_ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/v8/blobs/sha256:fc52a73f061198c928bc31a5561ad8460e948a8ea20ae45218e4f360d3c52fa0",
+ "sha256": "fc52a73f061198c928bc31a5561ad8460e948a8ea20ae45218e4f360d3c52fa0"
+ },
+ "arm64_monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/v8/blobs/sha256:4cd19a160f5ad942a30968f582ffaeb9485aad034e122c14910a4101e68aac35",
+ "sha256": "4cd19a160f5ad942a30968f582ffaeb9485aad034e122c14910a4101e68aac35"
+ },
+ "sonoma": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/v8/blobs/sha256:ca514e9dd9466c58c3fd466a11796655d8131985602b44d0c7a02d3788fb3841",
+ "sha256": "ca514e9dd9466c58c3fd466a11796655d8131985602b44d0c7a02d3788fb3841"
+ },
+ "ventura": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/v8/blobs/sha256:2de6daceddeae9d5c08d0842bdf08b9eb78a85bc3a869eae6629e4a5b883bcae",
+ "sha256": "2de6daceddeae9d5c08d0842bdf08b9eb78a85bc3a869eae6629e4a5b883bcae"
+ },
+ "monterey": {
+ "cellar": ":any",
+ "url": "https://ghcr.io/v2/homebrew/core/v8/blobs/sha256:9596dd1d33272babe4959de7869f569a00de8e62a8df7711e60b172a6a3f0c43",
+ "sha256": "9596dd1d33272babe4959de7869f569a00de8e62a8df7711e60b172a6a3f0c43"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/v8/blobs/sha256:c109bd1d47b502257784e1e28acbfdf68292f2f839feaab396d7fe71cc5a063e",
+ "sha256": "c109bd1d47b502257784e1e28acbfdf68292f2f839feaab396d7fe71cc5a063e"
+ }
+ }
+ }
+ },
+ "gpg2": {
+ "version": "2.4.5",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnupg/blobs/sha256:06ef66459900967866adbca613753707c6836c7b32b1c1f9d7a647771db88e2a",
+ "sha256": "06ef66459900967866adbca613753707c6836c7b32b1c1f9d7a647771db88e2a"
+ },
+ "arm64_ventura": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnupg/blobs/sha256:5640c700c6d704a612f849d00dfd00b1361cfb7664ce1e4be14b981044917aef",
+ "sha256": "5640c700c6d704a612f849d00dfd00b1361cfb7664ce1e4be14b981044917aef"
+ },
+ "arm64_monterey": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnupg/blobs/sha256:74cdf0e0430980129545583496f6a2d908b9f8a8b0e69a4e8484f3aee4e7647d",
+ "sha256": "74cdf0e0430980129545583496f6a2d908b9f8a8b0e69a4e8484f3aee4e7647d"
+ },
+ "sonoma": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnupg/blobs/sha256:bd0eaa9e5cb762f3426380799089831c34fd27dc608cc3bd15a86b0b43df8ce2",
+ "sha256": "bd0eaa9e5cb762f3426380799089831c34fd27dc608cc3bd15a86b0b43df8ce2"
+ },
+ "ventura": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnupg/blobs/sha256:3e1ab240be58c5267dbd3bc9cd82a19b09b96507169188a20adf710886733bd3",
+ "sha256": "3e1ab240be58c5267dbd3bc9cd82a19b09b96507169188a20adf710886733bd3"
+ },
+ "monterey": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnupg/blobs/sha256:9ea477a517f2de40c9bf7a8a335f6f2d7c1c234a31f47596f016305d175de908",
+ "sha256": "9ea477a517f2de40c9bf7a8a335f6f2d7c1c234a31f47596f016305d175de908"
+ },
+ "x86_64_linux": {
+ "cellar": "/home/linuxbrew/.linuxbrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/gnupg/blobs/sha256:3d0b4817c65315ef6457feb0e6e26672fc0a91475e64499304ebf5fc5faeb39d",
+ "sha256": "3d0b4817c65315ef6457feb0e6e26672fc0a91475e64499304ebf5fc5faeb39d"
+ }
+ }
+ }
+ },
+ "stripe/stripe-mock/stripe-mock": {
+ "version": "0.183.0",
+ "bottle": false
+ },
+ "minikube": {
+ "version": "1.32.0",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/minikube/blobs/sha256:2fa25ebc8759ff50e97f915601eeceb9f02dca260b51f9e858bdb1347b656861",
+ "sha256": "2fa25ebc8759ff50e97f915601eeceb9f02dca260b51f9e858bdb1347b656861"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/minikube/blobs/sha256:a068a5f5bd364df8e8c0b7084332800943084802c57c72560ae1d7499b8838b7",
+ "sha256": "a068a5f5bd364df8e8c0b7084332800943084802c57c72560ae1d7499b8838b7"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/minikube/blobs/sha256:e2ad2baca95b4267bc3eec3555dd3228337ce37f892adaba37df845a8ccc6499",
+ "sha256": "e2ad2baca95b4267bc3eec3555dd3228337ce37f892adaba37df845a8ccc6499"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/minikube/blobs/sha256:4a391ddf8cbc68f14cc70441db46ed7a94412475ffdf16c6a72cea353681ed80",
+ "sha256": "4a391ddf8cbc68f14cc70441db46ed7a94412475ffdf16c6a72cea353681ed80"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/minikube/blobs/sha256:8dd2f235c3886a9a3714436202fdb1638ed32b108bb7ca095357d22f5d298dbf",
+ "sha256": "8dd2f235c3886a9a3714436202fdb1638ed32b108bb7ca095357d22f5d298dbf"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/minikube/blobs/sha256:6e79f66ab867efbd36a805d92198e1f0843f3be147b94bbc0146d235753c601b",
+ "sha256": "6e79f66ab867efbd36a805d92198e1f0843f3be147b94bbc0146d235753c601b"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/minikube/blobs/sha256:dc42a6ecd1d3129b76afe2959d5a126eb4aafa34a34815483e85eb001adc1894",
+ "sha256": "dc42a6ecd1d3129b76afe2959d5a126eb4aafa34a34815483e85eb001adc1894"
+ }
+ }
+ }
+ },
+ "kubernetes-cli": {
+ "version": "1.29.3",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/kubernetes-cli/blobs/sha256:bec474887cbeb601422ba44ecdc63dbce2e1ace2b21019ac2d544cc7c7ffd912",
+ "sha256": "bec474887cbeb601422ba44ecdc63dbce2e1ace2b21019ac2d544cc7c7ffd912"
+ },
+ "arm64_ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/kubernetes-cli/blobs/sha256:91db803f66bea30247ef4ca13862df4624ee71e1f1a85af547999969655889c8",
+ "sha256": "91db803f66bea30247ef4ca13862df4624ee71e1f1a85af547999969655889c8"
+ },
+ "arm64_monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/kubernetes-cli/blobs/sha256:2fbdb8e41e96e637808c1005a20f21c4dca563ee4cd8c41d90b0b0e78dad59a8",
+ "sha256": "2fbdb8e41e96e637808c1005a20f21c4dca563ee4cd8c41d90b0b0e78dad59a8"
+ },
+ "sonoma": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/kubernetes-cli/blobs/sha256:f8cba521a8140262d304d5f83350651ba8ca24ab51fd8f02052cb42dd44740a4",
+ "sha256": "f8cba521a8140262d304d5f83350651ba8ca24ab51fd8f02052cb42dd44740a4"
+ },
+ "ventura": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/kubernetes-cli/blobs/sha256:fa258cb1fe860b0182d97619c9c16b2185267e1f46c1181a18022d3a79dd337c",
+ "sha256": "fa258cb1fe860b0182d97619c9c16b2185267e1f46c1181a18022d3a79dd337c"
+ },
+ "monterey": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/kubernetes-cli/blobs/sha256:b904d59bed196ce4debf9a23de3f20c30f5fe0ccbd4a6572a9e9cce153ff7cf0",
+ "sha256": "b904d59bed196ce4debf9a23de3f20c30f5fe0ccbd4a6572a9e9cce153ff7cf0"
+ },
+ "x86_64_linux": {
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/kubernetes-cli/blobs/sha256:761d8a1590bcdd59958e134cd4d2abb5b9a9521fd9596e16223f524180a78d2c",
+ "sha256": "761d8a1590bcdd59958e134cd4d2abb5b9a9521fd9596e16223f524180a78d2c"
+ }
+ }
+ }
+ }
+ },
+ "cask": {
+ "chromedriver": {
+ "version": "123.0.6312.105",
+ "options": {
+ "full_name": "chromedriver"
+ }
+ },
+ "github": {
+ "version": "3.3.13-1b0804db",
+ "options": {
+ "full_name": "github"
+ }
+ },
+ "font-andale-mono": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-andale-mono"
+ }
+ },
+ "font-anonymice-powerline": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-anonymice-powerline"
+ }
+ },
+ "font-arvo": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-arvo"
+ }
+ },
+ "font-aurulent-sans-mono-nerd-font": {
+ "version": "3.2.0",
+ "options": {
+ "full_name": "font-aurulent-sans-mono-nerd-font"
+ }
+ },
+ "font-awesome-terminal-fonts": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-awesome-terminal-fonts"
+ }
+ },
+ "font-bitstream-vera-sans-mono-nerd-font": {
+ "version": "3.2.0",
+ "options": {
+ "full_name": "font-bitstream-vera-sans-mono-nerd-font"
+ }
+ },
+ "font-blex-mono-nerd-font": {
+ "version": "3.2.0",
+ "options": {
+ "full_name": "font-blex-mono-nerd-font"
+ }
+ },
+ "font-cairo": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-cairo"
+ }
+ },
+ "font-cascadia-mono": {
+ "version": "2111.01",
+ "options": {
+ "full_name": "font-cascadia-mono"
+ }
+ },
+ "font-consolas-for-powerline": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-consolas-for-powerline"
+ }
+ },
+ "font-cousine": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-cousine"
+ }
+ },
+ "font-cutive-mono": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-cutive-mono"
+ }
+ },
+ "font-d2coding": {
+ "version": "1.3.2,20180524",
+ "options": {
+ "full_name": "font-d2coding"
+ }
+ },
+ "font-dejavu-sans-mono-for-powerline": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-dejavu-sans-mono-for-powerline"
+ }
+ },
+ "font-everson-mono": {
+ "version": "7.0.0",
+ "options": {
+ "full_name": "font-everson-mono"
+ }
+ },
+ "font-fantasque-sans-mono": {
+ "version": "1.8.0",
+ "options": {
+ "full_name": "font-fantasque-sans-mono"
+ }
+ },
+ "font-fontawesome": {
+ "version": "6.5.2",
+ "options": {
+ "full_name": "font-fontawesome"
+ }
+ },
+ "font-ia-writer-mono": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-ia-writer-mono"
+ }
+ },
+ "font-inconsolata": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-inconsolata"
+ }
+ },
+ "font-jetbrains-mono": {
+ "version": "2.304",
+ "options": {
+ "full_name": "font-jetbrains-mono"
+ }
+ },
+ "font-kawkab-mono": {
+ "version": "0.501",
+ "options": {
+ "full_name": "font-kawkab-mono"
+ }
+ },
+ "font-lekton-nerd-font": {
+ "version": "3.2.0",
+ "options": {
+ "full_name": "font-lekton-nerd-font"
+ }
+ },
+ "font-menlo-for-powerline": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-menlo-for-powerline"
+ }
+ },
+ "font-meslo-for-powerline": {
+ "version": "2015-12-04",
+ "options": {
+ "full_name": "font-meslo-for-powerline"
+ }
+ },
+ "font-miriam-mono-clm": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-miriam-mono-clm"
+ }
+ },
+ "font-monoid": {
+ "version": "0.61",
+ "options": {
+ "full_name": "font-monoid"
+ }
+ },
+ "font-monoisome": {
+ "version": "0.61",
+ "options": {
+ "full_name": "font-monoisome"
+ }
+ },
+ "font-mononoki": {
+ "version": "1.6",
+ "options": {
+ "full_name": "font-mononoki"
+ }
+ },
+ "font-monoton": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-monoton"
+ }
+ },
+ "font-noto-mono": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-noto-mono"
+ }
+ },
+ "font-oswald": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-oswald"
+ }
+ },
+ "font-oxygen": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-oxygen"
+ }
+ },
+ "font-oxygen-mono": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-oxygen-mono"
+ }
+ },
+ "font-powerline-symbols": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-powerline-symbols"
+ }
+ },
+ "font-pt-mono": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-pt-mono"
+ }
+ },
+ "font-roboto": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-roboto"
+ }
+ },
+ "font-roboto-mono-nerd-font": {
+ "version": "3.2.0",
+ "options": {
+ "full_name": "font-roboto-mono-nerd-font"
+ }
+ },
+ "font-share-tech-mono": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-share-tech-mono"
+ }
+ },
+ "font-sometype-mono": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-sometype-mono"
+ }
+ },
+ "font-titillium": {
+ "version": "2.0,258e06fe34c35320321f0458e6625bba",
+ "options": {
+ "full_name": "font-titillium"
+ }
+ },
+ "font-ubuntu": {
+ "version": "latest",
+ "options": {
+ "full_name": "font-ubuntu"
+ }
+ },
+ "font-victor-mono": {
+ "version": "1.5.6",
+ "options": {
+ "full_name": "font-victor-mono"
+ }
+ }
+ }
+ },
+ "system": {
+ "macos": {
+ "sonoma": {
+ "HOMEBREW_VERSION": "4.2.17",
+ "HOMEBREW_PREFIX": "/opt/homebrew",
+ "Homebrew/homebrew-core": "api",
+ "CLT": "15.3.0.0.1.1708646388",
+ "Xcode": "15.3",
+ "macOS": "14.3.1"
+ }
+ }
+ }
+}
diff --git a/Dockerfile b/Dockerfile
index 2d04767c..6e5fa353 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,43 +1,94 @@
-FROM docker.io/ruby:2.6-bullseye
+# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
+ARG RUBY_VERSION=3.2.3
+
+FROM docker.io/ruby:${RUBY_VERSION}-bookworm
-# Needed by Ruby to process UTF8-encoded files
ENV LANG C.UTF-8
RUN set -eus; \
apt-get update -qq; \
apt-get install -y --no-install-recommends \
- git-all \
- nodejs \
- shared-mime-info \
build-essential \
+ git-all \
+ htop \
+ iputils-ping \
+ libjemalloc2 \
libpq-dev \
+ libvips \
libxml2-dev \
libxslt1-dev \
- libjemalloc2 \
- postgresql-client \
- iputils-ping \
net-tools \
- netcat \
- htop \
- strace \
+ node-gyp \
+ nodejs \
pg-activity \
+ pkg-config \
+ postgresql-client \
+ python-is-python3 \
+ shared-mime-info \
+ strace \
; \
apt-get clean; \
rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/*;
+WORKDIR /rails
+
# @see https://engineering.binti.com/jemalloc-with-ruby-and-docker/
-ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2
-WORKDIR /app
-COPY Gemfile Gemfile.lock ./
-ENV RAILS_ENV="${RAIL_ENV:-"production"}"
+# ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2 \
+ENV NODE_VERSION=20.12.1 \
+ YARN_VERSION=latest \
+ PATH=/usr/local/node/bin:$PATH
+
RUN gem update --system -N
-RUN gem install bundler -N --version 1.17.3
-RUN bundle install -j 12 --deployment --without "test development"
-COPY . /app
+RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \
+ /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node
+
+RUN npm install -g npm@10.5.2 && \
+ npm install -g yarn@$YARN_VERSION && \
+ rm -rf /tmp/node-build-master
+
+# Install application gems
+COPY Gemfile Gemfile.lock ./
+RUN bundle install && \
+ rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
+ bundle exec bootsnap precompile --gemfile
+
+# Install node modules
+COPY package.json yarn.lock ./
+RUN yarn install
+
+# Create storage dir
+RUN mkdir storage
+
+# Copy application code
+COPY . .
+
+# Precompile bootsnap code for faster boot times
+RUN bundle exec bootsnap precompile app/ lib/
-RUN SECRET_KEY_BASE=1 bundle exec rake assets:precompile
+# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
+RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
+
+# Clean up installation packages to reduce image size
+RUN rm -rf /var/lib/apt/lists /var/cache/apt/archives
+
+# Copy built artifacts: gems, application
+COPY "${BUNDLE_PATH}" "${BUNDLE_PATH}"
+
+# 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
+
+RUN chown -R rails:rails db log storage tmp /rails
+
+USER 1000:1000
EXPOSE 3000
-CMD "./entrypoint.sh"
+
+RUN chmod 755 /rails/bin/docker-entrypoint
+
+ENV RAILS_ENV=production
+
+# Entrypoint prepares the database.
+ENTRYPOINT ["/rails/bin/docker-entrypoint"]
diff --git a/Dockerfile.main b/Dockerfile.main
new file mode 100644
index 00000000..36eebb39
--- /dev/null
+++ b/Dockerfile.main
@@ -0,0 +1,46 @@
+FROM docker.io/ruby:3.2
+
+# Needed by Ruby to process UTF8-encoded files
+ENV LANG C.UTF-8
+
+RUN sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
+RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
+
+RUN set -eus; \
+ apt-get update -qq; \
+ apt-get install -y --no-install-recommends \
+ netcat \
+ libpq-dev \
+ postgresql-client-16 \
+ git-all \
+ nodejs \
+ shared-mime-info \
+ build-essential \
+ libxml2-dev \
+ libxslt1-dev \
+ libjemalloc2 \
+ iputils-ping \
+ net-tools \
+ htop \
+ strace \
+ pg-activity ; \
+ apt-get clean; \
+ rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/*;
+
+# @see https://engineering.binti.com/jemalloc-with-ruby-and-docker/
+ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2
+
+WORKDIR /app
+COPY Gemfile Gemfile.lock ./
+ENV RAILS_ENV="${RAIL_ENV:-"production"}"
+RUN gem update --system -N
+RUN gem install bundler -N --version 1.17.3
+RUN bundle install -j 12 --deployment --without "test development"
+
+COPY . /app
+
+RUN SECRET_KEY_BASE=1 bundle exec rake assets:precompile
+
+EXPOSE 3000
+CMD "./entrypoint.sh"
+
diff --git a/Gemfile b/Gemfile
index 681f3b55..4b42a719 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,53 +2,103 @@
source 'https://rubygems.org'
-gem 'rails', '~> 4.2'
+ruby File.read('.ruby-version')
-gem 'attribute_normalizer'
-gem 'awesome_print'
-gem 'carrierwave'
-gem 'coffee-rails'
-gem 'colored2'
-gem 'commonjs'
+# Use main development branch of Rails
+gem 'rails', '=7.1.3.2'
+
+# Unclear if we need to require it explicitly
+# gem 'activesupport', '=7.1.3.2'
+
+# Use postgresql as the database for Active Record
+gem 'pg'
+# Use the Puma web server [https://github.com/puma/puma]
+gem 'puma', '>= 6'
+gem 'puma-status'
+
+# replace sprockets with propshaft
+gem 'propshaft'
+
+# Bundle and transpile JavaScript [https://github.com/rails/jsbundling-rails]
+gem 'jsbundling-rails'
+# Hotwire"s SPA-like page accelerator [https://turbo.hotwired.dev]
+gem 'turbo-rails'
+# Hotwire"s modest JavaScript framework [https://stimulus.hotwired.dev]
+gem 'stimulus-rails'
+# Bundle and process CSS [https://github.com/rails/cssbundling-rails]
+gem 'cssbundling-rails'
+# Build JSON APIs with ease [https://github.com/rails/jbuilder]
+gem 'jbuilder'
+
+# Redis
+gem 'redis', '>= 4.0.1'
+
+# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
+gem 'bcrypt', '~> 3.1.7'
+
+# Report APM info to NewRelic
+gem 'newrelic_rpm'
+
+# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
+gem 'tzinfo-data', platforms: %i[windows jruby]
+
+# Reduces oot times through caching; required in config/boot.rb
+gem 'bootsnap', require: false
+
+# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
+# gem "kredis"
+
+# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
+# gem "image_processing", "~> 1.2"
+# gem 'twitter-bootstrap-rails'
+# gem 'bootstrap', '~> 5.1'
+# gem 'jquery-rails', '~> 4.4'
+# gem 'commonjs'
+
+# Modern Date Picker
gem 'country_select'
-gem 'devise'
+gem 'flatpickr'
+
+gem 'colorize'
+
gem 'haml'
-gem 'jquery-rails'
-gem 'less'
-gem 'less-rails'
-gem 'libv8'
+gem 'haml-rails'
+
+gem 'annotate'
+gem 'attribute_normalizer'
+gem 'carrierwave'
+gem 'dalli'
+gem 'devise', git: 'https://github.com/heartcombo/devise.git'
gem 'mini_magick'
-gem 'newrelic_rpm'
-gem 'pg', '~> 0.20'
-gem 'protected_attributes'
-gem 'psych', '< 4'
-gem 'puma'
-gem 'puma-status'
+gem 'mini_racer'
+gem 'protected_attributes_continued'
+gem 'psych'
gem 'rake'
-gem 'rollbar'
-gem 'sass-rails'
-gem 'sentry-raven'
gem 'stripe'
-gem 'therubyracer', platforms: :ruby
-gem 'twitter-bootstrap-rails'
-gem 'uglifier'
-gem 'ventable'
-gem 'yard'
+gem 'ventable', '>= 1.3'
group :development, :test do
+ gem 'awesome_print'
+ gem 'brakeman', require: false
gem 'codecov'
+ gem 'debug', platforms: %i[mri windows]
+ gem 'faker'
+ gem 'foreman'
gem 'relaxed-rubocop'
gem 'rubocop'
gem 'rubocop-rails'
+ gem 'rubocop-rails-omakase', require: false
gem 'rubocop-rake'
gem 'rubocop-rspec'
gem 'stripe-ruby-mock', '~> 2.5.0', require: 'stripe_mock'
+ gem 'yard'
end
group :development do
- gem 'annotate'
gem 'asciidoctor'
- gem 'capistrano', '< 3'
+ gem 'capistrano'
+ # gem 'rack-mini-profiler'
+ gem 'web-console'
end
group :test do
@@ -57,5 +107,6 @@ group :test do
gem 'rspec-its'
gem 'rspec-rails'
gem 'simplecov'
+ gem 'timecop'
gem 'timeout'
end
diff --git a/Gemfile.lock b/Gemfile.lock
index 10d0bdc7..720fd524 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,318 +1,443 @@
+GIT
+ remote: https://github.com/heartcombo/devise.git
+ revision: a259ff3c28912a27329727f4a3c2623d3f5cb6f2
+ specs:
+ devise (5.0.0.beta)
+ bcrypt (~> 3.0)
+ orm_adapter (~> 0.1)
+ railties (>= 6.0.0)
+ responders
+ warden (~> 1.2.3)
+
GEM
remote: https://rubygems.org/
specs:
- accept_values_for (0.7.5)
- activemodel (>= 4.2, < 6.0)
- rspec (>= 2.0, < 4.0)
- actionmailer (4.2.11.1)
- actionpack (= 4.2.11.1)
- actionview (= 4.2.11.1)
- activejob (= 4.2.11.1)
+ accept_values_for (0.9.3)
+ activemodel (>= 6.1, < 8.0)
+ rspec (>= 3.10, < 4.0)
+ actioncable (7.1.3.2)
+ actionpack (= 7.1.3.2)
+ activesupport (= 7.1.3.2)
+ nio4r (~> 2.0)
+ websocket-driver (>= 0.6.1)
+ zeitwerk (~> 2.6)
+ actionmailbox (7.1.3.2)
+ actionpack (= 7.1.3.2)
+ activejob (= 7.1.3.2)
+ activerecord (= 7.1.3.2)
+ activestorage (= 7.1.3.2)
+ activesupport (= 7.1.3.2)
+ mail (>= 2.7.1)
+ net-imap
+ net-pop
+ net-smtp
+ actionmailer (7.1.3.2)
+ actionpack (= 7.1.3.2)
+ actionview (= 7.1.3.2)
+ activejob (= 7.1.3.2)
+ activesupport (= 7.1.3.2)
mail (~> 2.5, >= 2.5.4)
- rails-dom-testing (~> 1.0, >= 1.0.5)
- actionpack (4.2.11.1)
- actionview (= 4.2.11.1)
- activesupport (= 4.2.11.1)
- rack (~> 1.6)
- rack-test (~> 0.6.2)
- rails-dom-testing (~> 1.0, >= 1.0.5)
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (4.2.11.1)
- activesupport (= 4.2.11.1)
+ net-imap
+ net-pop
+ net-smtp
+ rails-dom-testing (~> 2.2)
+ actionpack (7.1.3.2)
+ actionview (= 7.1.3.2)
+ activesupport (= 7.1.3.2)
+ nokogiri (>= 1.8.5)
+ racc
+ rack (>= 2.2.4)
+ rack-session (>= 1.0.1)
+ rack-test (>= 0.6.3)
+ rails-dom-testing (~> 2.2)
+ rails-html-sanitizer (~> 1.6)
+ actiontext (7.1.3.2)
+ actionpack (= 7.1.3.2)
+ activerecord (= 7.1.3.2)
+ activestorage (= 7.1.3.2)
+ activesupport (= 7.1.3.2)
+ globalid (>= 0.6.0)
+ nokogiri (>= 1.8.5)
+ actionview (7.1.3.2)
+ activesupport (= 7.1.3.2)
builder (~> 3.1)
- erubis (~> 2.7.0)
- rails-dom-testing (~> 1.0, >= 1.0.5)
- rails-html-sanitizer (~> 1.0, >= 1.0.3)
- activejob (4.2.11.1)
- activesupport (= 4.2.11.1)
- globalid (>= 0.3.0)
- activemodel (4.2.11.1)
- activesupport (= 4.2.11.1)
- builder (~> 3.1)
- activerecord (4.2.11.1)
- activemodel (= 4.2.11.1)
- activesupport (= 4.2.11.1)
- arel (~> 6.0)
- activesupport (4.2.11.1)
- i18n (~> 0.7)
- minitest (~> 5.1)
- thread_safe (~> 0.3, >= 0.3.4)
- tzinfo (~> 1.1)
- annotate (3.1.1)
- activerecord (>= 3.2, < 7.0)
+ erubi (~> 1.11)
+ rails-dom-testing (~> 2.2)
+ rails-html-sanitizer (~> 1.6)
+ activejob (7.1.3.2)
+ activesupport (= 7.1.3.2)
+ globalid (>= 0.3.6)
+ activemodel (7.1.3.2)
+ activesupport (= 7.1.3.2)
+ activerecord (7.1.3.2)
+ activemodel (= 7.1.3.2)
+ activesupport (= 7.1.3.2)
+ timeout (>= 0.4.0)
+ activestorage (7.1.3.2)
+ actionpack (= 7.1.3.2)
+ activejob (= 7.1.3.2)
+ activerecord (= 7.1.3.2)
+ activesupport (= 7.1.3.2)
+ marcel (~> 1.0)
+ activesupport (7.1.3.2)
+ base64
+ bigdecimal
+ concurrent-ruby (~> 1.0, >= 1.0.2)
+ connection_pool (>= 2.2.5)
+ drb
+ i18n (>= 1.6, < 2)
+ minitest (>= 5.1)
+ mutex_m
+ tzinfo (~> 2.0)
+ addressable (2.8.6)
+ public_suffix (>= 2.0.2, < 6.0)
+ airbrussh (1.5.2)
+ sshkit (>= 1.6.1, != 1.7.0)
+ annotate (3.2.0)
+ activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0)
- arel (6.0.4)
- asciidoctor (2.0.17)
+ asciidoctor (2.0.22)
ast (2.4.2)
attribute_normalizer (1.2.0)
awesome_print (1.9.2)
- bcrypt (3.1.13)
+ base64 (0.2.0)
+ bcrypt (3.1.20)
+ bigdecimal (3.1.7)
+ bindex (0.8.1)
+ bootsnap (1.18.3)
+ msgpack (~> 1.2)
+ brakeman (6.1.2)
+ racc
builder (3.2.4)
- capistrano (2.15.9)
- highline
- net-scp (>= 1.0.0)
- net-sftp (>= 2.0.0)
- net-ssh (>= 2.0.14)
- net-ssh-gateway (>= 1.1.0)
- carrierwave (1.3.2)
- activemodel (>= 4.0.0)
- activesupport (>= 4.0.0)
- mime-types (>= 1.16)
+ capistrano (3.18.1)
+ airbrussh (>= 1.0.0)
+ i18n
+ rake (>= 10.0.0)
+ sshkit (>= 1.9.0)
+ carrierwave (3.0.7)
+ activemodel (>= 6.0.0)
+ activesupport (>= 6.0.0)
+ addressable (~> 2.6)
+ image_processing (~> 1.1)
+ marcel (~> 1.0.0)
ssrf_filter (~> 1.0)
- codecov (0.6.0)
- simplecov (>= 0.15, < 0.22)
- coffee-rails (4.2.2)
- coffee-script (>= 2.2.0)
- railties (>= 4.0.0)
- coffee-script (2.4.1)
- coffee-script-source
- execjs
- coffee-script-source (1.12.2)
- colored2 (3.1.2)
+ codecov (0.2.12)
+ json
+ simplecov
colorize (0.8.1)
- commonjs (0.2.7)
- concurrent-ruby (1.1.10)
- countries (2.1.4)
- i18n_data (~> 0.8.0)
- money (~> 6.9)
- sixarm_ruby_unaccent (~> 1.1)
- unicode_utils (~> 1.4)
- country_select (3.1.1)
- countries (~> 2.0)
- sort_alphabetical (~> 1.0)
- crass (1.0.5)
+ concurrent-ruby (1.2.3)
+ connection_pool (2.4.1)
+ countries (6.0.0)
+ unaccent (~> 0.3)
+ country_select (9.0.0)
+ countries (> 5.0, < 7.0)
+ crass (1.0.6)
+ cssbundling-rails (1.4.0)
+ railties (>= 6.0.0)
+ dalli (3.2.8)
dante (0.2.0)
- devise (4.7.1)
- bcrypt (~> 3.0)
- orm_adapter (~> 0.1)
- railties (>= 4.1.0)
- responders
- warden (~> 1.2.3)
- diff-lcs (1.3)
- docile (1.3.2)
- erubis (2.7.0)
- execjs (2.7.0)
- factory_bot (4.8.2)
- activesupport (>= 3.0.0)
- factory_bot_rails (4.8.2)
- factory_bot (~> 4.8.2)
- railties (>= 3.0.0)
- faraday (0.15.1)
- multipart-post (>= 1.2, < 3)
- ffi (1.10.0)
- globalid (0.4.2)
- activesupport (>= 4.2.0)
- grease (0.3.1)
- haml (5.0.4)
- temple (>= 0.8.0)
+ date (3.3.4)
+ debug (1.9.2)
+ irb (~> 1.10)
+ reline (>= 0.3.8)
+ diff-lcs (1.5.1)
+ docile (1.4.0)
+ drb (2.2.1)
+ erubi (1.12.0)
+ factory_bot (6.4.6)
+ activesupport (>= 5.0.0)
+ factory_bot_rails (6.4.3)
+ factory_bot (~> 6.4)
+ railties (>= 5.0.0)
+ faker (3.3.1)
+ i18n (>= 1.8.11, < 2)
+ ffi (1.16.3)
+ flatpickr (4.6.13.1)
+ foreman (0.88.1)
+ globalid (1.2.1)
+ activesupport (>= 6.1)
+ haml (6.3.0)
+ temple (>= 0.8.2)
+ thor
tilt
- highline (1.7.10)
- i18n (0.9.5)
+ haml-rails (2.1.0)
+ actionpack (>= 5.1)
+ activesupport (>= 5.1)
+ haml (>= 4.0.6)
+ railties (>= 5.1)
+ i18n (1.14.4)
concurrent-ruby (~> 1.0)
- i18n_data (0.8.0)
- jquery-rails (4.3.3)
- rails-dom-testing (>= 1, < 3)
- railties (>= 4.2.0)
- thor (>= 0.14, < 2.0)
- json (2.6.1)
- less (2.6.0)
- commonjs (~> 0.2.7)
- less-rails (3.0.0)
- actionpack (>= 4.0)
- grease
- less (~> 2.6.0)
- sprockets (> 2, < 4)
- tilt
- libv8 (3.16.14.19)
- loofah (2.3.1)
+ image_processing (1.12.2)
+ mini_magick (>= 4.9.5, < 5)
+ ruby-vips (>= 2.0.17, < 3)
+ io-console (0.7.2)
+ irb (1.12.0)
+ rdoc
+ reline (>= 0.4.2)
+ jbuilder (2.11.5)
+ actionview (>= 5.0.0)
+ activesupport (>= 5.0.0)
+ jsbundling-rails (1.3.0)
+ railties (>= 6.0.0)
+ json (2.7.2)
+ language_server-protocol (3.17.0.3)
+ libv8-node (18.19.0.0)
+ libv8-node (18.19.0.0-aarch64-linux)
+ libv8-node (18.19.0.0-aarch64-linux-musl)
+ libv8-node (18.19.0.0-arm64-darwin)
+ libv8-node (18.19.0.0-x86_64-darwin)
+ libv8-node (18.19.0.0-x86_64-linux)
+ libv8-node (18.19.0.0-x86_64-linux-musl)
+ loofah (2.22.0)
crass (~> 1.0.2)
- nokogiri (>= 1.5.9)
- mail (2.7.1)
+ nokogiri (>= 1.12.0)
+ mail (2.8.1)
mini_mime (>= 0.1.1)
- mime-types (3.4.1)
- mime-types-data (~> 3.2015)
- mime-types-data (3.2022.0105)
- mini_magick (4.9.5)
- mini_mime (1.0.1)
- mini_portile2 (2.4.0)
- minitest (5.15.0)
- money (6.11.3)
- i18n (>= 0.6.4, < 1.1)
+ net-imap
+ net-pop
+ net-smtp
+ marcel (1.0.4)
+ mini_magick (4.12.0)
+ mini_mime (1.1.5)
+ mini_racer (0.9.0)
+ libv8-node (~> 18.19.0.0)
+ minitest (5.22.3)
+ msgpack (1.7.2)
multi_json (1.15.0)
- multipart-post (2.0.0)
- net-scp (1.2.1)
- net-ssh (>= 2.6.5)
- net-sftp (2.1.2)
- net-ssh (>= 2.6.5)
- net-ssh (4.2.0)
- net-ssh-gateway (2.0.0)
- net-ssh (>= 4.0.0)
+ mutex_m (0.2.0)
+ net-imap (0.4.10)
+ date
+ net-protocol
+ net-pop (0.1.2)
+ net-protocol
+ net-protocol (0.2.2)
+ timeout
+ net-scp (4.0.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.0)
+ net-protocol
+ net-ssh (7.2.3)
net_http_unix (0.2.2)
- newrelic_rpm (8.6.0)
- nio4r (2.5.9)
- nokogiri (1.10.8)
- mini_portile2 (~> 2.4.0)
+ newrelic_rpm (9.9.0)
+ nio4r (2.7.1)
+ nokogiri (1.16.4-aarch64-linux)
+ racc (~> 1.4)
+ nokogiri (1.16.4-arm-linux)
+ racc (~> 1.4)
+ nokogiri (1.16.4-arm64-darwin)
+ racc (~> 1.4)
+ nokogiri (1.16.4-x86-linux)
+ racc (~> 1.4)
+ nokogiri (1.16.4-x86_64-darwin)
+ racc (~> 1.4)
+ nokogiri (1.16.4-x86_64-linux)
+ racc (~> 1.4)
orm_adapter (0.5.0)
- parallel (1.22.1)
- parser (3.1.2.0)
+ parallel (1.24.0)
+ parser (3.3.0.5)
ast (~> 2.4.1)
- pg (0.21.0)
- protected_attributes (1.1.4)
- activemodel (>= 4.0.1, < 5.0)
- psych (3.3.2)
- puma (5.6.7)
+ racc
+ pg (1.5.6)
+ propshaft (0.8.0)
+ actionpack (>= 7.0.0)
+ activesupport (>= 7.0.0)
+ rack
+ railties (>= 7.0.0)
+ protected_attributes_continued (1.9.0)
+ activemodel (>= 5.0)
+ psych (5.1.2)
+ stringio
+ public_suffix (5.0.5)
+ puma (6.4.2)
nio4r (~> 2.0)
- puma-status (1.4)
+ puma-status (1.6)
colorize (~> 0.8)
net_http_unix (~> 0.2)
parallel (~> 1)
- rack (1.6.12)
- rack-test (0.6.3)
- rack (>= 1.0)
- rails (4.2.11.1)
- actionmailer (= 4.2.11.1)
- actionpack (= 4.2.11.1)
- actionview (= 4.2.11.1)
- activejob (= 4.2.11.1)
- activemodel (= 4.2.11.1)
- activerecord (= 4.2.11.1)
- activesupport (= 4.2.11.1)
- bundler (>= 1.3.0, < 2.0)
- railties (= 4.2.11.1)
- sprockets-rails
- rails-deprecated_sanitizer (1.0.3)
- activesupport (>= 4.2.0.alpha)
- rails-dom-testing (1.0.9)
- activesupport (>= 4.2.0, < 5.0)
- nokogiri (~> 1.6)
- rails-deprecated_sanitizer (>= 1.0.1)
- rails-html-sanitizer (1.0.4)
- loofah (~> 2.2, >= 2.2.2)
- railties (4.2.11.1)
- actionpack (= 4.2.11.1)
- activesupport (= 4.2.11.1)
- rake (>= 0.8.7)
- thor (>= 0.18.1, < 2.0)
+ racc (1.7.3)
+ rack (3.0.10)
+ rack-session (2.0.0)
+ rack (>= 3.0.0)
+ rack-test (2.1.0)
+ rack (>= 1.3)
+ rackup (2.1.0)
+ rack (>= 3)
+ webrick (~> 1.8)
+ rails (7.1.3.2)
+ actioncable (= 7.1.3.2)
+ actionmailbox (= 7.1.3.2)
+ actionmailer (= 7.1.3.2)
+ actionpack (= 7.1.3.2)
+ actiontext (= 7.1.3.2)
+ actionview (= 7.1.3.2)
+ activejob (= 7.1.3.2)
+ activemodel (= 7.1.3.2)
+ activerecord (= 7.1.3.2)
+ activestorage (= 7.1.3.2)
+ activesupport (= 7.1.3.2)
+ bundler (>= 1.15.0)
+ railties (= 7.1.3.2)
+ rails-dom-testing (2.2.0)
+ activesupport (>= 5.0.0)
+ minitest
+ nokogiri (>= 1.6)
+ rails-html-sanitizer (1.6.0)
+ loofah (~> 2.21)
+ nokogiri (~> 1.14)
+ railties (7.1.3.2)
+ actionpack (= 7.1.3.2)
+ activesupport (= 7.1.3.2)
+ irb
+ rackup (>= 1.0.0)
+ rake (>= 12.2)
+ thor (~> 1.0, >= 1.2.2)
+ zeitwerk (~> 2.6)
rainbow (3.1.1)
- rake (13.0.1)
- rb-fsevent (0.10.3)
- rb-inotify (0.9.10)
- ffi (>= 0.5.0, < 2)
- ref (2.0.0)
- regexp_parser (2.3.0)
+ rake (13.2.1)
+ rdoc (6.6.3.1)
+ psych (>= 4.0.0)
+ redis (5.2.0)
+ redis-client (>= 0.22.0)
+ redis-client (0.22.1)
+ connection_pool
+ regexp_parser (2.9.0)
relaxed-rubocop (2.5)
- responders (2.4.1)
- actionpack (>= 4.2.0, < 6.0)
- railties (>= 4.2.0, < 6.0)
- rexml (3.2.5)
- rollbar (3.3.0)
- rspec (3.7.0)
- rspec-core (~> 3.7.0)
- rspec-expectations (~> 3.7.0)
- rspec-mocks (~> 3.7.0)
- rspec-core (3.7.1)
- rspec-support (~> 3.7.0)
- rspec-expectations (3.7.0)
+ reline (0.5.2)
+ io-console (~> 0.5)
+ responders (3.1.1)
+ actionpack (>= 5.2)
+ railties (>= 5.2)
+ rexml (3.2.6)
+ rspec (3.13.0)
+ rspec-core (~> 3.13.0)
+ rspec-expectations (~> 3.13.0)
+ rspec-mocks (~> 3.13.0)
+ rspec-core (3.13.0)
+ rspec-support (~> 3.13.0)
+ rspec-expectations (3.13.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.7.0)
+ rspec-support (~> 3.13.0)
rspec-its (1.3.0)
rspec-core (>= 3.0.0)
rspec-expectations (>= 3.0.0)
- rspec-mocks (3.7.0)
+ rspec-mocks (3.13.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.7.0)
- rspec-rails (3.7.2)
- actionpack (>= 3.0)
- activesupport (>= 3.0)
- railties (>= 3.0)
- rspec-core (~> 3.7.0)
- rspec-expectations (~> 3.7.0)
- rspec-mocks (~> 3.7.0)
- rspec-support (~> 3.7.0)
- rspec-support (3.7.1)
- rubocop (1.27.0)
+ rspec-support (~> 3.13.0)
+ rspec-rails (6.1.2)
+ actionpack (>= 6.1)
+ activesupport (>= 6.1)
+ railties (>= 6.1)
+ rspec-core (~> 3.13)
+ rspec-expectations (~> 3.13)
+ rspec-mocks (~> 3.13)
+ rspec-support (~> 3.13)
+ rspec-support (3.13.1)
+ rubocop (1.63.2)
+ json (~> 2.3)
+ language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
- parser (>= 3.1.0.0)
+ parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
- rexml
- rubocop-ast (>= 1.16.0, < 2.0)
+ rexml (>= 3.2.5, < 4.0)
+ rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7)
- unicode-display_width (>= 1.4.0, < 3.0)
- rubocop-ast (1.17.0)
- parser (>= 3.1.1.0)
- rubocop-rails (2.14.2)
+ unicode-display_width (>= 2.4.0, < 3.0)
+ rubocop-ast (1.31.2)
+ parser (>= 3.3.0.4)
+ rubocop-capybara (2.20.0)
+ rubocop (~> 1.41)
+ rubocop-factory_bot (2.25.1)
+ rubocop (~> 1.41)
+ rubocop-minitest (0.35.0)
+ rubocop (>= 1.61, < 2.0)
+ rubocop-ast (>= 1.31.1, < 2.0)
+ rubocop-performance (1.21.0)
+ rubocop (>= 1.48.1, < 2.0)
+ rubocop-ast (>= 1.31.1, < 2.0)
+ rubocop-rails (2.24.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
- rubocop (>= 1.7.0, < 2.0)
+ rubocop (>= 1.33.0, < 2.0)
+ rubocop-ast (>= 1.31.1, < 2.0)
+ rubocop-rails-omakase (1.0.0)
+ rubocop
+ rubocop-minitest
+ rubocop-performance
+ rubocop-rails
rubocop-rake (0.6.0)
rubocop (~> 1.0)
- rubocop-rspec (2.10.0)
- rubocop (~> 1.19)
- ruby-progressbar (1.11.0)
- sass (3.5.6)
- sass-listen (~> 4.0.0)
- sass-listen (4.0.0)
- rb-fsevent (~> 0.9, >= 0.9.4)
- rb-inotify (~> 0.9, >= 0.9.7)
- sass-rails (5.0.7)
- railties (>= 4.0.0, < 6)
- sass (~> 3.1)
- sprockets (>= 2.8, < 4.0)
- sprockets-rails (>= 2.0, < 4.0)
- tilt (>= 1.1, < 3)
- sentry-raven (2.7.3)
- faraday (>= 0.7.6, < 1.0)
- simplecov (0.17.1)
+ rubocop-rspec (2.29.1)
+ rubocop (~> 1.40)
+ rubocop-capybara (~> 2.17)
+ rubocop-factory_bot (~> 2.22)
+ rubocop-rspec_rails (~> 2.28)
+ rubocop-rspec_rails (2.28.3)
+ rubocop (~> 1.40)
+ ruby-progressbar (1.13.0)
+ ruby-vips (2.2.1)
+ ffi (~> 1.12)
+ simplecov (0.22.0)
docile (~> 1.1)
- json (>= 1.8, < 3)
- simplecov-html (~> 0.10.0)
- simplecov-html (0.10.2)
- sixarm_ruby_unaccent (1.2.0)
- sort_alphabetical (1.1.0)
- unicode_utils (>= 1.2.2)
- sprockets (3.7.2)
- concurrent-ruby (~> 1.0)
- rack (> 1, < 3)
- sprockets-rails (3.2.1)
- actionpack (>= 4.0)
- activesupport (>= 4.0)
- sprockets (>= 3.0.0)
- ssrf_filter (1.0.7)
- stripe (3.15.0)
- faraday (~> 0.10)
+ simplecov-html (~> 0.11)
+ simplecov_json_formatter (~> 0.1)
+ simplecov-html (0.12.3)
+ simplecov_json_formatter (0.1.4)
+ sshkit (1.22.1)
+ base64
+ mutex_m
+ net-scp (>= 1.1.2)
+ net-sftp (>= 2.1.2)
+ net-ssh (>= 2.8.0)
+ ssrf_filter (1.1.2)
+ stimulus-rails (1.3.3)
+ railties (>= 6.0.0)
+ stringio (3.1.0)
+ stripe (11.1.0)
stripe-ruby-mock (2.5.8)
dante (>= 0.2.0)
multi_json (~> 1.0)
stripe (>= 2.0.3)
- temple (0.8.0)
- therubyracer (0.12.3)
- libv8 (~> 3.16.14.15)
- ref
- thor (0.20.3)
- thread_safe (0.3.6)
- tilt (2.0.8)
- timeout (0.3.0)
- twitter-bootstrap-rails (2.2.8)
- actionpack (>= 3.1)
- execjs
- rails (>= 3.1)
- railties (>= 3.1)
- tzinfo (1.2.10)
- thread_safe (~> 0.1)
- uglifier (4.1.10)
- execjs (>= 0.3.0, < 3)
- unicode-display_width (2.1.0)
- unicode_utils (1.4.0)
- ventable (1.2.0)
- warden (1.2.7)
- rack (>= 1.0)
- webrick (1.7.0)
- yard (0.9.27)
- webrick (~> 1.7.0)
+ temple (0.10.3)
+ thor (1.3.1)
+ tilt (2.3.0)
+ timecop (0.9.8)
+ timeout (0.4.1)
+ turbo-rails (2.0.5)
+ actionpack (>= 6.0.0)
+ activejob (>= 6.0.0)
+ railties (>= 6.0.0)
+ tzinfo (2.0.6)
+ concurrent-ruby (~> 1.0)
+ unaccent (0.4.0)
+ unicode-display_width (2.5.0)
+ ventable (1.3.1)
+ activesupport (>= 5)
+ warden (1.2.9)
+ rack (>= 2.0.9)
+ web-console (4.2.1)
+ actionview (>= 6.0.0)
+ activemodel (>= 6.0.0)
+ bindex (>= 0.4.0)
+ railties (>= 6.0.0)
+ webrick (1.8.1)
+ websocket-driver (0.7.6)
+ websocket-extensions (>= 0.1.0)
+ websocket-extensions (0.1.5)
+ yard (0.9.36)
+ zeitwerk (2.6.13)
PLATFORMS
- ruby
+ aarch64-linux
+ aarch64-linux-musl
+ arm-linux
+ arm64-darwin
+ x86-linux
+ x86_64-darwin
+ x86_64-linux
+ x86_64-linux-musl
DEPENDENCIES
accept_values_for
@@ -320,48 +445,60 @@ DEPENDENCIES
asciidoctor
attribute_normalizer
awesome_print
- capistrano (< 3)
+ bcrypt (~> 3.1.7)
+ bootsnap
+ brakeman
+ capistrano
carrierwave
codecov
- coffee-rails
- colored2
- commonjs
+ colorize
country_select
- devise
+ cssbundling-rails
+ dalli
+ debug
+ devise!
factory_bot_rails
+ faker
+ flatpickr
+ foreman
haml
- jquery-rails
- less
- less-rails
- libv8
+ haml-rails
+ jbuilder
+ jsbundling-rails
mini_magick
+ mini_racer
newrelic_rpm
- pg (~> 0.20)
- protected_attributes
- psych (< 4)
- puma
+ pg
+ propshaft
+ protected_attributes_continued
+ psych
+ puma (>= 6)
puma-status
- rails (~> 4.2)
+ rails (= 7.1.3.2)
rake
+ redis (>= 4.0.1)
relaxed-rubocop
- rollbar
rspec-its
rspec-rails
rubocop
rubocop-rails
+ rubocop-rails-omakase
rubocop-rake
rubocop-rspec
- sass-rails
- sentry-raven
simplecov
+ stimulus-rails
stripe
stripe-ruby-mock (~> 2.5.0)
- therubyracer
+ timecop
timeout
- twitter-bootstrap-rails
- uglifier
- ventable
+ turbo-rails
+ tzinfo-data
+ ventable (>= 1.3)
+ web-console
yard
+RUBY VERSION
+ ruby 3.2.3p157
+
BUNDLED WITH
- 1.17.3
+ 2.5.6
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index e7ea3bf9..00000000
--- a/LICENSE
+++ /dev/null
@@ -1,22 +0,0 @@
-Copyright © 2016-2022 Konstantin Gredeskoul, Shane de Silva, FnF Engineers, All Rights Reserved.
-
-Distributed under MIT License
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
index 6571fcb9..eda0f287 100755
--- a/Makefile
+++ b/Makefile
@@ -28,6 +28,7 @@ MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST)))
CURRENT_DIR := $(shell ( cd .; pwd -P ) )
BASHMATIC_HOME := $(shell echo $(CURRENT_DIR)/dev/bashmatic)
MAKE_ENV := .make.env
+DEV_DB := $(shell grep database config/database.yml | grep development | awk '{print $$2}' | sed 's/^$$/ticketing_app_development/g')
help: ## Prints help message auto-generated from the comments.
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' | sort
@@ -65,4 +66,33 @@ docker-image: ## Builds a docker image named 'tickets'
docker build -t tickets .
shellcheck: ## Run shellcheck on the shell files
- $(CURRENT_DIR)/bin/shchk
+ $(CURRENT_DIR)/bin/shchk
+
+dev-install: ## Optional install of VIM configuration and other dev tools
+ $(CURRENT_DIR)/development/dev-install
+
+rebuild-dev-db: development ## Rebuild and re-seed the dev database
+ @printf "\n$(bg_purple) 👉 $(purple)$(clear) $(yellow)Dropping dev database $(DEV_DB)...$(clear)\n"
+ @dropdb $(DEV_DB) || true
+ @printf "\n$(bg_purple) 👉 $(purple)$(clear) $(yellow)Creating dev database...$(clear)\n"
+ @rails db:create
+ @printf "\n$(bg_purple) 👉 $(purple)$(clear) $(yellow)Migrating dev database...$(clear)\n"
+ @rails db:migrate
+ @printf "\n$(bg_purple) 👉 $(purple)$(clear) $(yellow)Seeding dev database...$(clear)\n"
+ rails db:seed
+
+assets: ## Build JS & CSS assets
+ @yarn install
+ @yarn run build
+ @yarn run build:css
+
+dev: development assets ## Start the development environment
+ @bash -c "source $(MAKE_ENV); bundle exec foreman start -f Procfile.dev"
+
+
+ci: ## Run all tests and linters as if on CI
+ bin/rails db:migrate
+ bin/rails db:test:prepare
+ bundle exec rspec
+ bundle exec rubocop
+ bin/shchk
diff --git a/Procfile.dev b/Procfile.dev
new file mode 100644
index 00000000..12e6ec56
--- /dev/null
+++ b/Procfile.dev
@@ -0,0 +1,5 @@
+web: env RUBY_DEBUG_OPEN=true bin/rails server
+js: yarn build --watch
+css: yarn watch:css
+browser: sleep 10 && open http://localhost:5000/ && while true; do sleep 100; echo .; done
+css: yarn watch:css
diff --git a/README.adoc b/README.adoc
index dfd3c279..70e91a67 100644
--- a/README.adoc
+++ b/README.adoc
@@ -6,46 +6,140 @@
:icons: font
:license: MIT
-image:https://github.com/fnf-org/ticket-booth/actions/workflows/ruby.yml/badge.svg[RSpec, link=https://github.com/fnf-org/ticket-booth/actions/workflows/ruby.yml] image:https://github.com/fnf-org/ticket-booth/actions/workflows/rubocop.yml/badge.svg[Rubocop, link=https://github.com/fnf-org/ticket-booth/actions/workflows/rubocop.yml] image:https://github.com/fnf-org/TicketBooth/actions/workflows/build.yaml/badge.svg[Docker Image Build,link="https://github.com/fnf-org/TicketBooth/actions/workflows/build.yaml"]
+====
+image::https://github.com/fnf-org/TicketBooth/actions/workflows/rspec.yml/badge.svg[TicketBooth CI: RSpec,link=https://github.com/fnf-org/TicketBooth/actions/workflows/rspec.yml]
----
+image::https://github.com/fnf-org/TicketBooth/actions/workflows/lint.yml/badge.svg[TicketBooth CI: RuboCop,link=https://github.com/fnf-org/TicketBooth/actions/workflows/lint.yml]
+
+image:https://github.com/fnf-org/TicketBooth/actions/workflows/build.yaml/badge.svg[Docker Image Build,link="https://github.com/fnf-org/TicketBooth/actions/workflows/build.yaml"]
+====
NOTE: This app is formerly known as **Helping Culture**, which in turn was originally conceived and inspired by Tracy Page. This project was originally written by https://github.com/sds[Shane de Silva]. It is currently maintained by the https://github.com/fnf-org[FnF] org, and within it specifically https://github.com/kigster[Konstantin Gredeskoul] for any application issues, and https://github.com/mike-matera[Mike Matera] for any issues related to deployment to the Google Public Cloud. Please use labels to tag any reported issues.
+NOTE: Please see the xref:README.pdf[following link] for a PDF version of this README.
== Welcome to the *Ticket Booth*!
-=== Ticket Booth is an open source Rails App meant to be self-hosted
-
The goal of the app is to make ticket and volunteer management for community events easier and automated.
-=== Initialization
+== Development Environment Setup
+
+The following walks through a local setup on OS-X M1.
+
+=== Streamlined Setup
-Please make sure you have PostgreSQL running locally, or install it via Homebrew:
+If you installed https://brew.sh[Homebrew] on your laptop, you should be able to boot the app.
+
+You can run the following setup script to attempt a complete set up of the development environment, as well as the installation of the Rubies, Gems and Database:
[source,bash]
----
bin/boot-up
----
-After that:
+This should automatically open the browser at the `http://localhost:3000` URL, if all the steps succeed.
+
+The `bin/boot-up` script will start the Rails server, or show an error that needs to be fixed.
+
+After you stop it with `Ctrl-C`, you can restart the server using the following shortcut:
+
+[source,bash]
+make dev
+
+This actually starts Foreman via `bundle exec foreman -f Procfile.dev` — this is required to start CSS and JS just-in-time compilcation in addition to the Rails server.
+
+CAUTION: Running `rails s` is no longer sufficient to start the application.
+
+==== Running Tests and Linters
+
+To verify that your local environment is working, run the following:
[source,bash]
----
-brew install rbenv ruby-build
+make ci
+----
+
+This will run DB Migrations, followed by RSpec, Rubocop, and ShellCheck.
+
+==== Additional Information
+
+We dedicated a separate document to the xref:DEVELOPERS.pdf[developer setup], which helps you get the application running locally.
+
+Alternatively, keep reading for step-by-step manual instructions.
+
+
+=== Optional Manual Setup
+
+If you prefer to run all the steps manually, then follow the guide below.
+
+==== Manual 1: Services
+
+Please make sure you have PostgreSQL and running locally, or install it via Homebrew:
+
+[source,bash]
+----
+brew install direnv
+
+brew install postgresql@16
+brew services postgresql@16 start
+
+brew install memcached
+brew services memcached start
+----
+
+==== Manual 2: Direnv Setup
+
+Before you can start the Ruby Server, you need to configure `direnv` so that the environment in the file `.envrc` is loaded on OS-X.
+
+To do that follow the instructions for setting direnv on https://direnv.net/docs/hook.html#bash[bash] or https://direnv.net/docs/hook.html#zsh[zsh] depending on what you are running. To find out, run `echo $SHELL`.
+
+After you setup the shell initialization file, restart your terminal or reload the shell configuration.
+
+Once you are back in the project's folder, run:
+
+[source,bash]
+direnv allow .
+
+This will load the environment variables from the `.envrc` file.
+
+==== Manual 3: Ruby Setup
+
+[source,bash]
+----
+# install brew from https://brew.sh
+brew bundle 2>/dev/null
+
+# ensure the following packages exist
+brew install rbenv ruby-build direnv volta
+
eval "$(rbenv init -)"
+eval "$(direnv hook ${SHELL/*\/})"
+
rbenv install -s $(cat .ruby-version)
rbenv local $(cat .ruby-version)
-bundle install
-bundle exec rake db:create
-bundle exec rake db:migrate db:seed
-bundle exec rake db:test:prepare
+volta install node@lts
+
+bundle install -j 12
+rails db:create
+rails db:migrate db:seed
+rails db:test:prepare
+
+# Run Specs at the end:
bundle exec rspec
----
-=== Starting the Server
+==== Manual 4: Starting the Server
+
+To start the server post-setup, run:
+
+[source,bash]
+----
+bin/rails s
+# or just
+rails s
+----
-We recommend that you use the `Makefile`:
+You can also use the `Makefile`:
[source,bash]
----
@@ -87,22 +181,41 @@ To generate the HTML (we'll use the CSV file checked into the fixtures):
[source,bash]
----
-bin/music-submission-links spec/fixtures/chill_sets.csv | pbcopy
+# eg, using the fixture file:
+$ bin/music-submission-links spec/fixtures/chill_sets.csv > chill_set.html
+
+# or, to include the simple CSS into the header:
+$ bin/music-submission-links spec/fixtures/chill_sets.csv --simple-css > chill_set.html
+open chill_set.html
----
-Now you can open WordPress, create a two-column layout and paste the contents into one of the two columns.
+====
+WARNING: If you add `--simple-css` to the arguments, the generated HTML will include `
` element with the https://simplecss.org/[Simple CSS Stylesheet]. Do not use this flag if you plan to paste the output into the WordPress text box. Use this flag if you simply want to verify the resulting HTML in a browser by running `open chill_set.html`.
+====
-To verify that the script is working, install a handy tool called `highlight`:
+To verify that the script is working and generating correct HTML, you might want to install a handy tool called `bat`, eg using Homebrew on Mac OS-X:
[source,bash]
----
-npm i -g highlight
-bin/music-submission-links spec/fixtures/chill_sets.csv | highlight -l html
+$ brew install bat
+$ bin/music-submission-links spec/fixtures/chill_sets.csv | bat
----
-=== Developer Setup
+===== Adding Submissions to WordPress
-We dedicated a separate document to the xref:DEVELOPERS.pdf[developer setup], which helps you get the application running locally.
+Now you can open WordPress, create a two-column layout on the submissions page and paste the contents into one of the two columns, typically:
+
+ 1. Night time / Peak Hour
+ 2. Chill / Daytime
+
+First, let's copy the resulting HTML into clipboard:
+
+[source,bash]
+----
+$ bin/music-submission-links chill_sets.csv | pbcopy
+----
+
+Now we can paste it into WordPress directly.
== API Documentation
@@ -110,10 +223,9 @@ Yard-generated documentation is available via running:
[source,bash]
----
-bundle exec rake doc
+$ bundle exec rake doc
# this will automatically open the index.html
----
-b
diff --git a/README.pdf b/README.pdf
index d0d96e6f..9b645a18 100644
Binary files a/README.pdf and b/README.pdf differ
diff --git a/Rakefile b/Rakefile
old mode 100755
new mode 100644
index cbca03e4..d4b5e256
--- a/Rakefile
+++ b/Rakefile
@@ -1,12 +1,13 @@
-#!/usr/bin/env rake
# frozen_string_literal: true
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
-require File.expand_path('config/application', __dir__)
+require_relative 'config/application'
require 'timeout'
+Rails.application.load_tasks
+
is_dev_test = %w[development test].include?(ENV.fetch('RAILS_ENV', 'development'))
if is_dev_test
@@ -15,11 +16,11 @@ if is_dev_test
require 'yard'
namespace :todolist do
- task :statsetup do
+ task statsetup: :environment do
require 'rails/code_statistics'
- ::STATS_DIRECTORIES << %w[Uploaders app/uploaders]
+ STATS_DIRECTORIES << %w[Classes app/classes]
# For test folders not defined in CodeStatistics::TEST_TYPES (ie: spec/)
- ::STATS_DIRECTORIES << %w[Specs spec]
+ STATS_DIRECTORIES << %w[Specs spec]
CodeStatistics::TEST_TYPES << 'Specs'
end
end
@@ -30,5 +31,3 @@ if is_dev_test
RuboCop::RakeTask.new
task default: %i[spec rubocop]
end
-
-TicketBooth::Application.load_tasks
diff --git a/app/mailers/.gitkeep b/app/assets/builds/.keep
similarity index 100%
rename from app/mailers/.gitkeep
rename to app/assets/builds/.keep
diff --git a/app/models/.gitkeep b/app/assets/images/.keep
similarity index 100%
rename from app/models/.gitkeep
rename to app/assets/images/.keep
diff --git a/app/assets/images/fnf-bw.png b/app/assets/images/logos/fnf-bw.png
similarity index 100%
rename from app/assets/images/fnf-bw.png
rename to app/assets/images/logos/fnf-bw.png
diff --git a/app/assets/images/logos/fnf-large.png b/app/assets/images/logos/fnf-large.png
new file mode 100644
index 00000000..514794f0
Binary files /dev/null and b/app/assets/images/logos/fnf-large.png differ
diff --git a/app/assets/images/logos/fnf-transparent.png b/app/assets/images/logos/fnf-transparent.png
new file mode 100644
index 00000000..04681108
Binary files /dev/null and b/app/assets/images/logos/fnf-transparent.png differ
diff --git a/app/assets/images/fnf.png b/app/assets/images/logos/fnf.png
similarity index 100%
rename from app/assets/images/fnf.png
rename to app/assets/images/logos/fnf.png
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
deleted file mode 100644
index e876480f..00000000
--- a/app/assets/javascripts/application.js
+++ /dev/null
@@ -1,17 +0,0 @@
-// This is a manifest file that'll be compiled into application.js, which will include all the files
-// listed below.
-//
-// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
-// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
-//
-// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-// the compiled file.
-//
-// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
-// GO AFTER THE REQUIRES BELOW.
-//
-//= require jquery
-//= require jquery_ujs
-//= require twitter/bootstrap
-//= require bootstrap-datetimepicker.min
-//= require_tree .
diff --git a/app/assets/javascripts/bootstrap.js.coffee b/app/assets/javascripts/bootstrap.js.coffee
deleted file mode 100644
index c9404a8e..00000000
--- a/app/assets/javascripts/bootstrap.js.coffee
+++ /dev/null
@@ -1,4 +0,0 @@
-jQuery ->
- $("a[rel=popover]").popover()
- $(".tooltip").tooltip()
- $("a[rel=tooltip]").tooltip()
\ No newline at end of file
diff --git a/app/assets/javascripts/datetimepicker.js b/app/assets/javascripts/datetimepicker.js
deleted file mode 100644
index c6a980c2..00000000
--- a/app/assets/javascripts/datetimepicker.js
+++ /dev/null
@@ -1,17 +0,0 @@
-// Initial all datetimepickers on the page, if there are any
-$('.input-datetimepicker').each(function(index, el) {
- var $el = $(el),
- date = $el.data('date');
-
- $el.datetimepicker({
- format: 'dd/MM/yyyy hh:mm:ss',
- pick12HourFormat: true,
- pickSeconds: false,
- format: 'yyyy/MM/dd hh:mm:ss'
- });
-
- // Rails returns a UTC date, so need to convert it to local time
- if (date) {
- $el.data('datetimepicker').setLocalDate(new Date(date));
- }
-});
diff --git a/app/assets/javascripts/payments.js.coffee b/app/assets/javascripts/payments.js.coffee
deleted file mode 100644
index 973bead2..00000000
--- a/app/assets/javascripts/payments.js.coffee
+++ /dev/null
@@ -1,74 +0,0 @@
-jQuery ->
- if Stripe? # Load if we're on a page that requires Stripe
- Stripe.setPublishableKey($('meta[name="stripe-key"]').attr('content'))
- payment.setupForm()
-
-payment =
- setupForm: ->
- $('#new_payment').submit ->
- $('input[type=submit]').attr('disabled', true)
- if $('#card_number').length
- payment.processCard()
- false
- else
- true
-
- $('.donation-option input[type=radio]').on('change', ( ->
- $totalPrice = $('#total_price')
- $totalFee = $('#total_fee')
- amount = parseFloat($(this).val(), 10) +
- parseFloat($totalPrice.data('price'), 10)
- $totalPrice.text('$' + amount.toFixed(2))
- $totalFee.text('$' + payment.extraFee(amount).toFixed(2))
- ))
-
- # EALD Form is separate
- $('#new_eald_payment').submit ->
- console.log('Submitting...')
- $('input[type=submit]').attr('disabled', true)
- console.log('What')
- console.log($('#card_number').length)
- if $('#card_number').length
- payment.processCard()
- false
- else
- true
-
- $('#eald_payment_early_arrival_passes, #eald_payment_late_departure_passes').on('change mouseup', ( ->
- $ealdPrice= $('#eald_total_price')
- $ealdFee = $('#eald_fee')
- $earlyPasses = $('#eald_payment_early_arrival_passes')
- $latePasses = $('#eald_payment_late_departure_passes')
-
- price = parseFloat($earlyPasses.val(), 10) * parseFloat($earlyPasses.data('default-price')) +
- parseFloat($latePasses.val(), 10) * parseFloat($latePasses.data('default-price'))
-
- $ealdPrice.text('$' + price.toFixed(2))
- $ealdFee.text('$' + payment.extraFee(price).toFixed(2))
- ))
-
- extraFee: (amount) ->
- stripeRate = 0.029 # 2.9% per transaction
- stripeFee = 0.30 # +30 cents per transaction
-
- if amount == 0
- 0
- else
- fee = (amount * stripeRate + stripeFee) / (1 - stripeRate)
- Math.ceil(fee * 100) / 100 # Round up to the nearest cent
-
- processCard: ->
- card =
- number: $('#card_number').val()
- cvc: $('#card_code').val()
- expMonth: $('#card_month').val()
- expYear: $('#card_year').val()
- Stripe.createToken(card, payment.handleStripeResponse)
-
- handleStripeResponse: (status, response) ->
- if status == 200
- $('#stripe_card_token').val(response.id)
- $('form')[0].submit()
- else
- $('#stripe-error').text(response.error.message)
- $('input[type=submit]').attr('disabled', false)
diff --git a/app/assets/javascripts/popovers.js b/app/assets/javascripts/popovers.js
deleted file mode 100644
index 0210527f..00000000
--- a/app/assets/javascripts/popovers.js
+++ /dev/null
@@ -1,11 +0,0 @@
-(function($) {
- var $popupMarkers = $('.help-popover');
-
- $popupMarkers.popover({
- trigger: 'manual'
- }).on('click', function(e) {
- var $popover = $(this);
- $popover.popover('toggle');
- e.preventDefault();
- });
-})(jQuery);
diff --git a/app/assets/javascripts/ticket_requests.js b/app/assets/javascripts/ticket_requests.js
deleted file mode 100644
index b4e3f2c0..00000000
--- a/app/assets/javascripts/ticket_requests.js
+++ /dev/null
@@ -1,100 +0,0 @@
-$('#ticket_request_user_attributes_email')
- .on('change', function(evt) {
- var $emailField = $(this),
- email = $emailField.val(),
- lookupPath = $emailField.data('lookup-url');
-
- $.get(lookupPath + '?email=' + email)
- .success(function() {
- $('#email-warning').removeClass('hidden');
- })
- .fail(function() {
- $('#email-warning').addClass('hidden');
- }).
- always(function() {
- $('#email-reset-sent').addClass('hidden');
- });
- });
-
-$('#password-reset-link')
- .on('click', function(evt) {
- var resetUrl = $(this).data('reset-url'),
- $emailField = $('#ticket_request_user_attributes_email'),
- email = $emailField.val();
- $.get(resetUrl + '?email=' + email)
- .success(function() {
- $('#email-warning').addClass('hidden');
- $('#email-reset-sent').removeClass('hidden');
- });
- evt.preventDefault();
- });
-
-$('#ticket_request_adults, #ticket_request_kids, #ticket_request_cabins, #ticket_request_early_arrival_passes, #ticket_request_late_departure_passes')
- .on('change keyup mouseup', function(evt) {
- var $numberField = $(this),
- $priceDisplay = $numberField.find('+ .inline-price'),
- quantity = $numberField.val() || 0,
- prices = $numberField.data('custom-prices') || {},
- price = prices[quantity] || $numberField.data('default-price') * quantity,
- text_price = price ? '$' + price : '';
-
- $priceDisplay.text(text_price);
- })
- .each(function(idx, el) {
- var $el = $(el);
- // Force update so prices reflect default values
- // (don't update disabled elements as they already have text)
- if (!$el.is(':disabled')) {
- $el.change();
- }
- });
-
-$('input[name="ticket_request[role]"]')
- .on('change', function(evt) {
- var $radioBtn = $(this),
- role = $radioBtn.val(),
- $roleRadioBtn = $('#ticket_request_role_' + role),
- maxTickets = $roleRadioBtn.data('max-tickets'),
- $roleExplanationField = $('textarea[name="ticket_request[role_explanation]"]'),
- $roleExplanations = $('.role-explanation'),
- $selectedRoleExplanation = $('.role-explanation.' + role),
- $ticketsField = $('input[name="ticket_request[adults]"]');
-
- // Set max # of tickets to limit imposed by role
- $ticketsField.attr('max', maxTickets);
-
- // Reduce tickets if changing role causes maximum to be exceeded
- if ($ticketsField.val() > maxTickets) {
- $ticketsField.val(maxTickets);
- $ticketsField.change();
- }
-
- // Show the explanation matching the selected role
- $roleExplanations.addClass('hidden');
- $selectedRoleExplanation.removeClass('hidden');
-
- $roleExplanationField.toggleClass('hidden', role == 'volunteer');
-
- // Don't require explanation if role is "Volunteer"
- $roleExplanationField.attr('required', role != 'volunteer');
- });
-
-$('input[name="ticket_request[car_camping]"]')
- .on('change', function(evt) {
- var $carCampingChkbx = $(this),
- carCamping = $carCampingChkbx.is(':checked'),
- $carCampingExplanationField = $('textarea[name="ticket_request[car_camping_explanation]"]');
-
- $carCampingExplanationField.toggleClass('hidden', !carCamping);
-
- // Don't require explanation if not requesting anything
- $carCampingExplanationField.attr('required', carCamping);
- });
-
-$('input[name="ticket_request[agrees_to_terms]"]')
- .on('change', function(evt) {
- var $agreesChkbx = $(this),
- agrees = $agreesChkbx.is(':checked');
-
- $('#submit-request').attr('disabled', !agrees);
- });
diff --git a/app/assets/javascripts/tooltips.js b/app/assets/javascripts/tooltips.js
deleted file mode 100644
index 936fdcdc..00000000
--- a/app/assets/javascripts/tooltips.js
+++ /dev/null
@@ -1 +0,0 @@
-$('.hover-tooltip').tooltip();
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
deleted file mode 100644
index 08be496a..00000000
--- a/app/assets/stylesheets/application.css
+++ /dev/null
@@ -1,16 +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 vendor/assets/stylesheets of plugins, if any, 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 top of the
- * compiled file, but it's generally better to create a new file per style scope.
- *
- *= require_self
- *= require_tree .
- *
- *= require bootstrap_and_overrides
- *= require bootstrap-datetimepicker.min
- */
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
new file mode 100644
index 00000000..1d28a571
--- /dev/null
+++ b/app/assets/stylesheets/application.scss
@@ -0,0 +1,31 @@
+/*
+/*
+ * 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 vendor/assets/stylesheets of plugins, if any, 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 top of the
+ * compiled file, but it's generally better to create a new file per style scope.
+ *
+ *= require_self
+ */
+
+@import 'bootstrap/scss/bootstrap';
+@import 'bootstrap-icons/font/bootstrap-icons';
+
+@import 'flatpickr/dist/flatpickr';
+@import 'flatpickr/dist/themes/material_orange';
+
+//@import '../../../node_modules/flatpickr/dist/flatpickr.min';
+//@import '../../../node_modules/flatpickr/dist/themes/material_orange';
+
+@import './events.css.scss';
+@import './home.css.scss';
+@import './jobs.css.scss';
+@import './payments.css.scss';
+@import './shifts.css.scss';
+@import './time_slots.css.scss';
+@import './links.css.scss';
+@import './global.css.scss';
diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/assets/stylesheets/bootstrap_and_overrides.css.less
deleted file mode 100644
index 0729ddbf..00000000
--- a/app/assets/stylesheets/bootstrap_and_overrides.css.less
+++ /dev/null
@@ -1,30 +0,0 @@
-@import "twitter/bootstrap/bootstrap";
-@import "twitter/bootstrap/responsive";
-
-// Set the correct sprite paths
-@iconSpritePath: asset-path("twitter/bootstrap/glyphicons-halflings");
-@iconWhiteSpritePath: asset-path("twitter/bootstrap/glyphicons-halflings-white");
-
-// Set the Font Awesome (Font Awesome is default. You can disable by commenting below lines)
-@fontAwesomeEotPath: asset-url("fontawesome-webfont.eot");
-@fontAwesomeEotPath_iefix: asset-url("fontawesome-webfont.eot#iefix");
-@fontAwesomeWoffPath: asset-url("fontawesome-webfont.woff");
-@fontAwesomeTtfPath: asset-url("fontawesome-webfont.ttf");
-@fontAwesomeSvgPath: asset-url("fontawesome-webfont.svg#fontawesomeregular");
-
-// Font Awesome
-@import "fontawesome/font-awesome";
-
-// Glyphicons
-//@import "twitter/bootstrap/sprites.less";
-
-// Your custom LESS stylesheets goes here
-//
-// Since bootstrap was imported above you have access to its mixins which
-// you may use and inherit here
-//
-// If you'd like to override bootstrap's own variables, you can do so here as well
-// See http://twitter.github.com/bootstrap/customize.html#variables for their names and documentation
-//
-// Example:
-// @linkColor: #ff0000;
diff --git a/app/assets/stylesheets/events.css.scss b/app/assets/stylesheets/events.css.scss
index 43423e73..6dce2fc2 100644
--- a/app/assets/stylesheets/events.css.scss
+++ b/app/assets/stylesheets/events.css.scss
@@ -17,6 +17,9 @@
float: right;
}
-.event-preview {
- float: right;
+img.event-preview {
+ max-width: 200px;
+ max-height: 200px;
+ margin: 0 30px;
+ float: right !important;
}
diff --git a/app/assets/stylesheets/global.css.scss b/app/assets/stylesheets/global.css.scss
index 10db7433..3c94c60f 100644
--- a/app/assets/stylesheets/global.css.scss
+++ b/app/assets/stylesheets/global.css.scss
@@ -1,8 +1,83 @@
-a {
+.container {
+ padding-top: 20px;
+}
+
+.clear-right {
+ clear: right;
+}
+
+.container-fluid, .container {
+ a {
+ color: #ff7800 !important;
+ text-decoration: none !important;
+ padding: 6px 15px !important;
+ margin-left: -15px;
+
+ &:focus,
+ &:hover {
+ color: #000 !important;
+ background-color: #ff7800 !important;
+ }
+ }
+}
+
+.muted {
+ font-size: 10pt;
+ color: #888;
+ margin-left: 22px;
+}
+
+a.btn {
+ color: #ff7800 !important;
+ text-decoration: none !important;
+ padding: 6px 15px !important;
+ margin-right: 10px;
+ margin-left: 0;
+ border: 1px solid #8f4603;
+ border-radius: 10px !important;
+ background-color: #462301;
+
&:focus,
&:hover {
- color: #ffbd24;
- text-decoration: none;
+ color: #000 !important;
+ background-color: #ff7800 !important;
+ }
+}
+
+
+nav.navbar {
+ width: 100%;
+ padding-left: 20px;
+
+ a.nav-brand {
+ display: inline-block;
+ border-width: 0 !important;
+ padding: 0 !important;
+ background-color: transparent !important;
+ border-radius: 0;
+ margin-left: 30px;
+ }
+
+ a.nav-brand img {
+ display: inline-block;
+ position: relative;
+ margin-left: 50px !important;
+ margin-right: 30px !important;
+ padding: 20px !important;
+ }
+
+ a.nav-link {
+ color: #ff7800 !important;
+ text-decoration: none !important;
+ padding: 6px 15px !important;
+ margin: 0 10px;
+ background-color: #462301;
+
+ &:focus,
+ &:hover {
+ color: #000 !important;
+ background-color: #ff7800 !important;
+ }
}
}
@@ -15,6 +90,92 @@ a {
}
}
+h1, h2, h3, h4, h5, legend {
+ margin-top: 20px;
+}
+
+.alert {
+ h3 {
+ margin: 0;
+ text-align: center;
+ }
+}
+
+.field-group {
+ position: relative;
+ width: 100%;
+ display: block;
+ border: 1px solid red;
+}
+
+.vertical-20 {
+ display: block;
+ width: 100%;
+ height: 20px;
+}
+
+.nowrap {
+ white-space: nowrap;
+}
+
+dl.dl-horizontal {
+ margin-top: 20px;
+
+ dt {
+ color: #fa2;
+ font-weight: 900;
+ }
+}
+
+form {
+ label {
+ margin-top: 25px;
+ margin-bottom: 2px;
+ display: block;
+ min-width: auto;
+ font-size: 12pt;
+ vertical-align: top;
+
+ }
+
+ input {
+ background-color: #efe;
+ color: #000;
+ border: 0.5px solid #777;
+ border-radius: 5px;
+ padding: 5px 0;
+ margin-top: 0;
+ margin-right: 5px;
+ margin-bottom: 0;
+ display: inline-block !important;
+ max-width: 80%;
+ }
+
+ input[type=text] {
+ min-width: 100px;
+ text-align: left;
+ padding-left: 10px;
+ width: 300px;
+ }
+
+ input[type=submit] {
+ margin-top: 20px;
+ margin-bottom: 20px;
+ }
+
+ input[type=number] {
+ text-align: right;
+ width: 80px;
+ min-width: 30px;
+ }
+
+ input[type=email], input[type=password] {
+ padding: 9px !important;
+ width: 300px;
+ min-width: 100px;
+ }
+}
+
.help-inline {
line-height: 30px;
vertical-align: top;
@@ -50,11 +211,9 @@ body {
}
// Hide navbar text for small screens
-@media (max-width: 410px) {
- .user-name,
- .sign-in-text,
- .sign-out-text {
- display: none;
+@media (max-width: 600px) {
+ small.nowrap {
+ white-space: normal;
}
}
diff --git a/app/assets/stylesheets/home.css.scss b/app/assets/stylesheets/home.css.scss
index 1be075fa..6b09c6f0 100644
--- a/app/assets/stylesheets/home.css.scss
+++ b/app/assets/stylesheets/home.css.scss
@@ -1,5 +1,4 @@
.hero-unit {
- background-color: #fff;
text-align: center;
}
diff --git a/app/assets/stylesheets/links.css.scss b/app/assets/stylesheets/links.css.scss
new file mode 100644
index 00000000..6fb1d7a6
--- /dev/null
+++ b/app/assets/stylesheets/links.css.scss
@@ -0,0 +1,11 @@
+ul.shared-user-links {
+ list-style-type: none;
+ margin-top: 40px !important;
+ padding-left: 0 !important;
+ p {
+ margin-left: 0;
+ a {
+ margin-left: 0;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/users/shared/links.css.scss b/app/assets/stylesheets/users/shared/links.css.scss
deleted file mode 100644
index 3ce933d7..00000000
--- a/app/assets/stylesheets/users/shared/links.css.scss
+++ /dev/null
@@ -1,4 +0,0 @@
-.shared-user-links {
- list-style-type: none;
- margin-left: 0;
-}
diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb
new file mode 100644
index 00000000..9aec2305
--- /dev/null
+++ b/app/channels/application_cable/channel.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+module ApplicationCable
+ class Channel < ActionCable::Channel::Base
+ end
+end
diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb
new file mode 100644
index 00000000..8d6c2a1b
--- /dev/null
+++ b/app/channels/application_cable/connection.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+module ApplicationCable
+ class Connection < ActionCable::Connection::Base
+ end
+end
diff --git a/lib/fnf/event_reporter.rb b/app/classes/fnf/event_reporter.rb
similarity index 93%
rename from lib/fnf/event_reporter.rb
rename to app/classes/fnf/event_reporter.rb
index 036e9c91..13115ee8 100644
--- a/lib/fnf/event_reporter.rb
+++ b/app/classes/fnf/event_reporter.rb
@@ -2,6 +2,7 @@
require 'newrelic_rpm'
require 'singleton'
+
module FnF
class EventReporter
class << self
@@ -15,6 +16,10 @@ def metric(event, value)
::NewRelic::Agent.record_metric(event_name(event), value)
end
+ def handle_event(event)
+ metric(event, 1)
+ end
+
protected
def event_name(event)
diff --git a/app/classes/fnf/events.rb b/app/classes/fnf/events.rb
new file mode 100644
index 00000000..995d5aa2
--- /dev/null
+++ b/app/classes/fnf/events.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'ventable'
+
+require_relative 'events/abstract_event'
+require_relative 'events/ticket_request_event'
+require_relative 'events/ticket_request_approved_event'
+require_relative 'events/ticket_request_declined_event'
+
+require_relative '../../mailers/ticket_request_mailer'
+
+module FnF
+ module Events
+ class << self
+ attr_accessor :initialized
+
+ def initialize_events!
+ return if initialized == true
+
+ TicketRequestEvent.configure { notifies ::TicketRequestMailer }
+
+ TicketRequestDeclinedEvent.configure { notifies ::TicketRequestMailer }
+
+ TicketRequestApprovedEvent.configure { notifies ::TicketRequestMailer }
+
+ self.initialized = true
+ end
+ end
+ end
+end
diff --git a/app/classes/fnf/events/abstract_event.rb b/app/classes/fnf/events/abstract_event.rb
new file mode 100644
index 00000000..f03a4e88
--- /dev/null
+++ b/app/classes/fnf/events/abstract_event.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'ventable'
+require_relative '../event_reporter'
+
+module FnF
+ module Events
+ class AbstractEvent
+ attr_accessor :target, :user
+
+ def initialize(user: nil, target: nil)
+ self.target = target
+ self.user = user
+ end
+
+ class << self
+ # @param [Object] subclass
+ def inherited(subclass)
+ # noinspection RubyMismatchedArgumentType
+ super(subclass)
+
+ subclass.include Ventable::Event
+
+ subclass.notifies(EventReporter)
+
+ subclass.instance_eval do
+ # For eg FnF::Events::TicketRequestEvent this should return :ticket_request
+ # For eg FnF::Events::TicketRequestDeclinedEvent this should return :ticket_request_declined
+ # For eg FnF::Events::TicketRequestApprovedEvent this should return :ticket_request_approved
+ def ventable_callback_method_name
+ ActiveSupport::Inflector.underscore(name)
+ .gsub(%r{^fnf/events/}, '')
+ .gsub(/_event$/, '')
+ .to_sym
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/fnf/events/ticket_request_approved_event.rb b/app/classes/fnf/events/ticket_request_approved_event.rb
similarity index 99%
rename from lib/fnf/events/ticket_request_approved_event.rb
rename to app/classes/fnf/events/ticket_request_approved_event.rb
index 3b91cdab..f1ffa9bd 100644
--- a/lib/fnf/events/ticket_request_approved_event.rb
+++ b/app/classes/fnf/events/ticket_request_approved_event.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require_relative 'abstract_event'
-
module FnF
module Events
# This event fires whenever the ticket request is approved
diff --git a/lib/fnf/events/ticket_request_declined_event.rb b/app/classes/fnf/events/ticket_request_declined_event.rb
similarity index 83%
rename from lib/fnf/events/ticket_request_declined_event.rb
rename to app/classes/fnf/events/ticket_request_declined_event.rb
index da8ec109..815a67cf 100644
--- a/lib/fnf/events/ticket_request_declined_event.rb
+++ b/app/classes/fnf/events/ticket_request_declined_event.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require_relative 'abstract_event'
-
+require_relative 'ticket_request_event'
module FnF
module Events
# This event fires whenever the ticket request is declined
diff --git a/lib/fnf/events/ticket_request_event.rb b/app/classes/fnf/events/ticket_request_event.rb
similarity index 100%
rename from lib/fnf/events/ticket_request_event.rb
rename to app/classes/fnf/events/ticket_request_event.rb
diff --git a/app/concerns/routing.rb b/app/concerns/routing.rb
new file mode 100644
index 00000000..fcac8a59
--- /dev/null
+++ b/app/concerns/routing.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Routing
+ extend ActiveSupport::Concern
+
+ included do
+ include Rails.application.routes.url_helpers
+ end
+
+ def default_url_options
+ Rails.application.config.action_mailer.default_url_options
+ end
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index a92c4994..c64a53ae 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -10,6 +10,8 @@ class ApplicationController < ActionController::Base
# Allow additional parameters to be passed to Devise-managed controllers
before_action :configure_permitted_parameters, if: :devise_controller?
+ add_flash_types :info, :error, :warning
+
protected
def require_site_admin
@@ -20,17 +22,29 @@ def require_event_admin
redirect_to new_event_ticket_request_path(@event) unless @event.admin?(current_user)
end
+ def require_logged_in_user
+ unless current_user&.id
+ redirect_to new_user_session_path
+ end
+ end
+
def configure_permitted_parameters
- devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
+ attributes = %i[name first last email avatar]
+ devise_parameter_sanitizer.permit(:sign_up, keys: attributes)
+ devise_parameter_sanitizer.permit(:account_update, keys: attributes)
end
def authenticate_user_from_token!
user_id = params[:user_id].presence
- user = User.find_by_id(user_id)
+ user = user_id ? User.find_by(id: user_id) : nil
if user && Devise.secure_compare(user.authentication_token, params[:user_token])
user.update_attribute(:authentication_token, nil) # One-time use
sign_in user
end
end
+
+ def render_flash
+ render turbo_stream: turbo_stream.update('flash', partial: 'shared/flash')
+ end
end
diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep
new file mode 100644
index 00000000..e69de29b
diff --git a/app/controllers/eald_payments_controller.rb b/app/controllers/eald_payments_controller.rb
index 39304e3b..f0227edb 100644
--- a/app/controllers/eald_payments_controller.rb
+++ b/app/controllers/eald_payments_controller.rb
@@ -22,10 +22,10 @@ def new
early_arrival_passes = params.fetch(:early_arrival_passes, 1)
late_departure_passes = params.fetch(:late_departure_passes, 1)
@eald_payment = EaldPayment.new(event_id: @event.id,
- email: email,
+ email:,
name: full_name,
- early_arrival_passes: early_arrival_passes,
- late_departure_passes: late_departure_passes)
+ early_arrival_passes:,
+ late_departure_passes:)
end
def create
diff --git a/app/controllers/emails_controller.rb b/app/controllers/emails_controller.rb
index c2462738..ab078b62 100644
--- a/app/controllers/emails_controller.rb
+++ b/app/controllers/emails_controller.rb
@@ -4,7 +4,7 @@ class EmailsController < ApplicationController
def index
respond_to do |format|
format.any do
- if User.where(email: params[:email]).first
+ if User.where(email: email_params[:email]).first
head :ok
else
head :not_found
@@ -12,4 +12,10 @@ def index
end
end
end
+
+ private
+
+ def email_params
+ params.permit(:email)
+ end
end
diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb
index 3a8b50b3..58c5b740 100644
--- a/app/controllers/events_controller.rb
+++ b/app/controllers/events_controller.rb
@@ -27,28 +27,37 @@ def new
end
def edit
+ params_symbolized_hash[:event]&.each_pair do |key, value|
+ @event.send("#{key}=", value) if @event.respond_to?("#{key}=")
+ end
render
end
def create
- populate_params_event(params)
+ create_params = params_symbolized_hash[:event].dup
+ convert_event_times_for_db(create_params)
- @event = Event.new(params[:event])
+ @event = Event.new(create_params)
if @event.save
redirect_to @event
else
- render action: 'new'
+ Rails.logger.error("Can't create event: #{@event.errors.full_messages}")
+ flash.now[:error] = "There was a problem creating the event: #{@event.errors.full_messages.join('. ')}"
+ render_flash
end
end
def update
- populate_params_event(params)
+ update_params = params_symbolized_hash[:event].dup
+ convert_event_times_for_db(update_params)
- if @event.update_attributes(params[:event])
- redirect_to @event
+ if @event.update(update_params)
+ redirect_to @event, notice: 'The event has been updated.'
else
- render action: 'edit'
+ Rails.logger.error "UPDATE ERROR: There was a problem updating the event: #{@event.errors.full_messages}"
+ flash.now[:error] = "There was a problem updating the event: #{@event.errors.full_messages}"
+ render_flash
end
end
@@ -61,8 +70,8 @@ def destroy
def add_admin
return redirect_to :back unless @event.admin?(current_user)
- email = params[:user_email]
- user = User.find_by_email(email)
+ email = permitted_params[:user_email]
+ user = User.find_by(email:)
return redirect_to :back, notice: "No user with email '#{email}' exists" unless user
@event.admins << user
@@ -78,8 +87,8 @@ def add_admin
def remove_admin
return redirect_to :back unless @event.admin?(current_user)
- user_id = params[:user_id]
- event_admin = EventAdmin.where(event_id: @event, user_id: user_id).first
+ user_id = permitted_params[:user_id]
+ event_admin = EventAdmin.where(event_id: @event, user_id:).first
return redirect_to :back, notice: "No user with id #{user_id} exists" unless event_admin
event_admin.destroy
@@ -111,12 +120,16 @@ def download_guest_list
private
- def populate_params_event(params)
- params[:event][:start_time] = Time.from_picker(params.delete(:start_time))
- params[:event][:end_time] = Time.from_picker(params.delete(:end_time))
- params[:event][:ticket_sales_start_time] = Time.from_picker(params.delete(:ticket_sales_start_time))
- params[:event][:ticket_sales_end_time] = Time.from_picker(params.delete(:ticket_sales_end_time))
- params[:event][:ticket_requests_end_time] = Time.from_picker(params.delete(:ticket_requests_end_time))
+ def params_symbolized_hash
+ @params_symbolized_hash ||= permitted_params.to_h.tap(&:symbolize_keys!)
+ end
+
+ def convert_event_times_for_db(event_hash)
+ converted_times = {}
+ event_hash.keys.grep(/_time$/).each do |key|
+ converted_times[key] = TimeHelper.to_datetime_from_picker(event_hash[key])
+ end
+ event_hash.merge!(converted_times)
end
def completed_ticket_requests
@@ -129,6 +142,42 @@ def completed_ticket_requests
end
def set_event
- @event = Event.find(params[:id])
+ @event = Event.find(permitted_params[:id])
+ end
+
+ def permitted_params
+ params.permit(
+ :id,
+ :user_email,
+ :user_id,
+ :_method,
+ :authenticity_token,
+ :commit,
+ event: %i[
+ start_time
+ end_time
+ ticket_sales_start_time
+ ticket_sales_end_time
+ ticket_requests_end_time
+ adult_ticket_price
+ allow_donations
+ allow_financial_assistance
+ cabin_price
+ early_arrival_price
+ end_time
+ kid_ticket_price
+ late_departure_price
+ max_adult_tickets_per_request
+ max_cabin_requests
+ max_cabins_per_request
+ max_kid_tickets_per_request
+ name
+ require_mailing_address
+ start_time
+ tickets_require_approval
+ ]
+ )
+ .to_hash
+ .with_indifferent_access
end
end
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
index 69be221b..aefae36e 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -19,11 +19,13 @@ def index
most_recent_event = Event.order(id: :desc).first
redirect_to new_event_ticket_request_path(event_id: most_recent_event.id)
end
+ else
+ render
end
end
def oops
- flash[:error] = 'No events exist at the moment, please have your Site Administrator create a public event first.'
+ flash.now[:error] = 'No events exist at the moment, please have your Site Administrator create a public event first.'
render
end
end
diff --git a/app/controllers/jobs_controller.rb b/app/controllers/jobs_controller.rb
index 779b8e51..08f37ca7 100644
--- a/app/controllers/jobs_controller.rb
+++ b/app/controllers/jobs_controller.rb
@@ -37,7 +37,7 @@ def create
def update
@job = Job.find(params[:id])
- if @job.update_attributes(params[:job])
+ if @job.update(params[:job])
redirect_to event_job_path(@event, @job),
notice: 'Job was successfully updated.'
else
@@ -65,8 +65,8 @@ def set_event
def create_time_slots
return unless params[:start_time]
- start_time = Time.from_picker(params.delete(:start_time))
- end_time = Time.from_picker(params.delete(:end_time))
+ start_time = TimeHelper.to_datetime_from_picker(params.delete(:start_time))
+ end_time = TimeHelper.to_datetime_from_picker(params.delete(:end_time))
return if end_time < start_time # Prevent infinite loop
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
index d2cf95be..77899352 100644
--- a/app/controllers/passwords_controller.rb
+++ b/app/controllers/passwords_controller.rb
@@ -4,10 +4,10 @@
class PasswordsController < ApplicationController
def reset
email = params[:email].presence
- if email && user = User.where(email: email).first
+ if email && user = User.where(email:).first
user.send_reset_password_instructions
end
- redirect_to request.referrer, alert: 'A password reset link has been sent to your email!'
+ redirect_to request.referer, alert: 'A password reset link has been sent to your email!'
end
end
diff --git a/app/controllers/payments_controller.rb b/app/controllers/payments_controller.rb
index 751e8b2a..91487aac 100644
--- a/app/controllers/payments_controller.rb
+++ b/app/controllers/payments_controller.rb
@@ -8,7 +8,7 @@ def show
@charge = Stripe::Charge.retrieve(@payment.stripe_charge_id) if @payment.stripe_charge_id
@ticket_request = @payment.ticket_request
@event = @ticket_request.event
- return redirect_to root_path unless @payment.can_view?(current_user)
+ redirect_to root_path unless @payment.can_view?(current_user)
end
def new
@@ -37,7 +37,7 @@ def create
return redirect_to root_path unless @payment.can_view?(current_user)
if @payment.save_and_charge!
- PaymentMailer.payment_received(@payment).deliver_now
+ PaymentReceivedMailer.payment_received(@payment).deliver_now
@payment.ticket_request.mark_complete
redirect_to @payment, notice: 'Payment was successfully received.'
else
diff --git a/app/controllers/shifts_controller.rb b/app/controllers/shifts_controller.rb
index 600ba25d..1e1a47df 100644
--- a/app/controllers/shifts_controller.rb
+++ b/app/controllers/shifts_controller.rb
@@ -6,7 +6,7 @@ class ShiftsController < ApplicationController
def index
@ticket_request = @event.ticket_requests.where(user_id: current_user).first
- if !@ticket_request.present? && !current_user.site_admin?
+ if @ticket_request.blank? && !current_user.site_admin?
flash[:error] = 'The user you are logged in with has not requested a ticket for this event'
return redirect_to :root
end
@@ -22,8 +22,8 @@ def create
if @shift.save
redirect_to event_shifts_path(@event),
- notice: "Successfully volunteered for #{@shift.time_slot.job.name}" \
- " for #{@shift.time_slot.start_time.localtime.to_s(:dhmm)}"
+ notice: "Successfully volunteered for #{@shift.time_slot.job.name} " \
+ "for #{@shift.time_slot.start_time.localtime.to_fs(:dhmm)}"
else
render action: 'index'
end
@@ -34,8 +34,8 @@ def destroy
@shift.destroy
redirect_to event_shifts_url(@event),
- notice: "Unvolunteered from #{@shift.time_slot.job.name}" \
- " for #{@shift.time_slot.start_time.localtime.to_s(:dhmm)}"
+ notice: "Unvolunteered from #{@shift.time_slot.job.name} " \
+ "for #{@shift.time_slot.start_time.localtime.to_fs(:dhmm)}"
end
private
diff --git a/app/controllers/ticket_requests_controller.rb b/app/controllers/ticket_requests_controller.rb
index 609a72e1..fe1f6036 100644
--- a/app/controllers/ticket_requests_controller.rb
+++ b/app/controllers/ticket_requests_controller.rb
@@ -4,16 +4,14 @@
require 'csv'
# Manage all pages related to ticket requests.
+# rubocop: disable Metrics/ClassLength
class TicketRequestsController < ApplicationController
before_action :authenticate_user!, except: %i[new create]
+
before_action :set_event
- before_action :require_event_admin, except: %i[new create show edit update]
- before_action :set_ticket_request, except: %i[index new create download]
- # Uncomment this if we start getting too many requests
- # http_basic_authenticate_with name: Rails.application.secrets.ticket_request_username,
- # password: Rails.application.secrets.ticket_request_password,
- # only: :new
+ before_action :require_event_admin, except: %i[new create show edit update]
+ before_action :set_ticket_request, except: %i[new create index download]
def index
@ticket_requests = TicketRequest
@@ -45,7 +43,7 @@ def index
def download
temp_csv = Tempfile.new('csv')
- raise(ArgumentError, 'Blank temp_csv') unless temp_csv && temp_csv&.path
+ raise ArgumentError('Tempfile is nil') if temp_csv.nil? || temp_csv.path.nil?
CSV.open(temp_csv.path, 'wb') do |csv|
csv << (%w[name email] + TicketRequest.columns.map(&:name))
@@ -69,18 +67,20 @@ def show
def new
if signed_in?
- existing_request = TicketRequest.where(user_id: current_user, event_id: @event).first
+ existing_request = TicketRequest.where(user_id: current_user, event_id: @event).order(:created_at).first
return redirect_to event_ticket_request_path(@event, existing_request) if existing_request
end
- @user = current_user if signed_in?
+ @user = current_user if signed_in?
@ticket_request = TicketRequest.new
- last_ticket_request = TicketRequest.where(user_id: @user).order(:created_at).last
- if last_ticket_request
- %w[address_line1 address_line2 city state zip_code country_code].each do |field|
- @ticket_request.send(:"#{field}=", last_ticket_request.send(field))
+ if @user
+ last_ticket_request = TicketRequest.where(user_id: @user&.id).order(:created_at).last
+ if last_ticket_request
+ %w[address_line1 address_line2 city state zip_code country_code].each do |field|
+ @ticket_request.send(:"#{field}=", last_ticket_request.send(field))
+ end
end
end
end
@@ -96,36 +96,66 @@ def edit
def create
unless @event.ticket_sales_open?
- flash[:error] = 'Sorry, but ticket sales have closed'
+ flash.now[:error] = 'Sorry, but ticket sales have closed'
return render action: 'new'
end
- params[:ticket_request][:user] = current_user if signed_in?
- @ticket_request = TicketRequest.new(params[:ticket_request])
+ tr_params = permitted_params[:ticket_request].to_h || {}
+
+ ticket_request_user = if signed_in? && current_user.present?
+ current_user
+ else
+ User.build(email: permitted_params[:email],
+ name: permitted_params[:name],
+ password: permitted_params[:password],
+ password_confirmation: permitted_params[:password]).tap do |user|
+ if user.valid?
+ user.save! && sign_in(user)
+ else
+ flash.now[:error] = user.errors.full_messages.join('. ')
+ @ticket_request = TicketRequest.new(tr_params, user:, event: @event)
+ return render action: 'new'
+ end
+ end
+ end
+
+ if tr_params.empty?
+ flash.now[:error] = 'Please fill out the form below to request tickets.'
+ redirect_to new_event_ticket_request_path(@event)
+ end
+
+ tr_params[:user_id] = ticket_request_user.id
+
+ @ticket_request = TicketRequest.new(tr_params, user_id: ticket_request_user.id, event_id: @event.id)
+
+ Rails.logger.info("Newly created request: #{@ticket_request.inspect}")
+
+ begin
+ @ticket_request.save!
+ Rails.logger.info("Saved Ticket Request, ID = #{@ticket_request.id}")
- if @ticket_request.save
FnF::Events::TicketRequestEvent.new(
- user: current_user,
+ user: ticket_request_user,
target: @ticket_request
).fire!
- sign_in(@ticket_request.user) unless signed_in?
-
if @event.tickets_require_approval || @ticket_request.free?
redirect_to event_ticket_request_path(@event, @ticket_request)
else
redirect_to new_payment_url(ticket_request_id: @ticket_request)
end
- else
- render action: 'new'
+ rescue StandardError => e
+ Rails.logger.error("Error saving request: #{e.message}\n\n#{@ticket_request.errors.full_messages.join(', ')}")
+ flash.now[:error] = "Error saving request: #{e.message}#{@ticket_request.errors.full_messages.join("\n")}"
+ render_flash
end
end
def update
# Allow ticket request to edit guests and nothing else
- params[:ticket_request].slice!(:guests) unless @event.admin?(current_user)
+ permitted_params[:ticket_request].slice!(:guests) unless @event.admin?(current_user)
- if @ticket_request.update_attributes(params[:ticket_request])
+ if @ticket_request.update(permitted_params[:ticket_request])
redirect_to event_ticket_request_path(@event, @ticket_request)
else
render action: 'edit'
@@ -147,7 +177,7 @@ def approve
end
def decline
- if @ticket_request.update_attributes(status: TicketRequest::STATUS_DECLINED)
+ if @ticket_request.update(status: TicketRequest::STATUS_DECLINED)
::FnF::Events::TicketRequestDeclinedEvent.new(
user: current_user,
target: @ticket_request
@@ -185,11 +215,24 @@ def refund
private
def set_event
- @event = Event.find(params[:event_id])
+ @event = Event.find(permitted_params[:event_id])
end
def set_ticket_request
- @ticket_request = TicketRequest.find(params[:id])
+ @ticket_request = TicketRequest.find(permitted_params[:id])
redirect_to @event unless @ticket_request.event == @event
end
+
+ def permitted_params
+ params.permit(:event_id, :id, :email, :name, :password, :authenticity_token, :commit,
+ ticket_request: %i[user_id adults kids cabins needs_assistance
+ notes special_price event_id
+ user donation role role_explanation
+ car_camping car_camping_explanation previous_contribution
+ address_line1 address_line2 city state zip_code
+ country_code admin_notes agrees_to_terms
+ early_arrival_passes late_departure_passes guests])
+ end
end
+
+# rubocop: enable Metrics/ClassLength
diff --git a/app/controllers/time_slots_controller.rb b/app/controllers/time_slots_controller.rb
index f8bb3ec3..970cb6f2 100644
--- a/app/controllers/time_slots_controller.rb
+++ b/app/controllers/time_slots_controller.rb
@@ -17,9 +17,9 @@ def new
slots = last_time_slot.slots
end
- @time_slot = TimeSlot.new start_time: start_time,
- end_time: end_time,
- slots: slots
+ @time_slot = TimeSlot.new start_time:,
+ end_time:,
+ slots:
end
def edit
@@ -27,8 +27,8 @@ def edit
end
def create
- params[:time_slot][:start_time] = Time.from_picker(params.delete(:start_time))
- params[:time_slot][:end_time] = Time.from_picker(params.delete(:end_time))
+ params[:time_slot][:start_time] = TimeHelper.to_datetime_from_picker(params.delete(:start_time))
+ params[:time_slot][:end_time] = TimeHelper.to_datetime_from_picker(params.delete(:end_time))
@time_slot = TimeSlot.new(params[:time_slot])
@@ -40,10 +40,10 @@ def create
end
def update
- params[:time_slot][:start_time] = Time.from_picker(params.delete(:start_time))
- params[:time_slot][:end_time] = Time.from_picker(params.delete(:end_time))
+ params[:time_slot][:start_time] = TimeHelper.to_datetime_from_picker(params.delete(:start_time))
+ params[:time_slot][:end_time] = TimeHelper.to_datetime_from_picker(params.delete(:end_time))
- if @time_slot.update_attributes(params[:time_slot])
+ if @time_slot.update(params[:time_slot])
redirect_to event_job_path(@event, @job)
else
render action: 'edit'
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index d03190e2..b7675621 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -2,10 +2,9 @@
module ApplicationHelper
def datetimepicker(name, datetime)
- content_tag :div, class: 'input-datetimepicker input-append date',
- data: { date: datetime.try(:rfc2822) } do
- text_field_tag(name, nil) +
- content_tag(:span, nil, class: 'add-on icon-calendar')
+ content_tag :div, class: 'date', data: { date: datetime.try(:rfc2822) } do
+ text_field_tag(name, datetime, class: 'flatpickr', placeholder: 'Select Date...') +
+ content_tag(:i, nil, class: 'icon-calendar')
end
end
@@ -16,4 +15,17 @@ def help_mark(help_text, options = {})
content_tag :i, nil, class: 'icon-question-sign'
end
end
+
+ def stripe_publishable_api_key
+ TicketBooth::Application.config.x.stripe.public_key
+ end
+
+ def alert_class(alert_type)
+ case alert_type
+ when 'notice' then 'alert-success'
+ when 'error', 'alert' then 'alert-danger'
+ when 'warning' then 'alert-warning'
+ else 'alert-primary'
+ end
+ end
end
diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb
deleted file mode 100644
index 03620959..00000000
--- a/app/helpers/home_helper.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# frozen_string_literal: true
-
-module HomeHelper
-end
diff --git a/app/helpers/shifts_helper.rb b/app/helpers/shifts_helper.rb
index fe1fc206..e27d84cb 100644
--- a/app/helpers/shifts_helper.rb
+++ b/app/helpers/shifts_helper.rb
@@ -23,17 +23,17 @@ def schedule_matrix
.inject(nil) do |last_time_slot, time_slot|
offset = (time_slot.start_time - earliest) / interval
duration = (time_slot.end_time - time_slot.start_time) / interval
- rows[offset] << { offset: offset, time_slot: time_slot, rowspan: duration.to_i }
+ rows[offset] << { offset:, time_slot:, rowspan: duration.to_i }
# Fill in empty gaps
if last_time_slot && last_time_slot.end_time < time_slot.start_time
offset = (last_time_slot.end_time - earliest) / interval
duration = (time_slot.start_time - last_time_slot.end_time) / interval
- rows[offset] << { offset: offset, rowspan: duration.to_i }
+ rows[offset] << { offset:, rowspan: duration.to_i }
elsif !last_time_slot && earliest < time_slot.start_time
offset = 0
duration = (time_slot.start_time - earliest) / interval
- rows[offset] << { offset: offset, rowspan: duration.to_i }
+ rows[offset] << { offset:, rowspan: duration.to_i }
end
# TODO: last_time_slot is always nil, since it's not set anywhere, right? -- @kigster
@@ -44,7 +44,7 @@ def schedule_matrix
if last_time_slot && last_time_slot.end_time < latest
offset = (last_time_slot.end_time - earliest) / interval
duration = (latest - last_time_slot.end_time) / interval
- rows[offset] << { offset: offset, rowspan: duration.to_i }
+ rows[offset] << { offset:, rowspan: duration.to_i }
elsif !last_time_slot
rows[0] << { offset: 0, rowspan: blocks }
end
diff --git a/app/helpers/ticket_requests_helper.rb b/app/helpers/ticket_requests_helper.rb
index d6c201aa..1be5d9d7 100644
--- a/app/helpers/ticket_requests_helper.rb
+++ b/app/helpers/ticket_requests_helper.rb
@@ -58,9 +58,9 @@ def eald_paid?(ticket_request)
end
def price_rules_to_json(event)
- event.price_rules.map do |price_rule|
+ event.price_rules.to_h do |price_rule|
[price_rule.trigger_value, price_rule.price.to_i]
- end.to_h.to_json
+ end.to_json
end
def help_text_for(sym)
diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb
new file mode 100644
index 00000000..d85f433a
--- /dev/null
+++ b/app/helpers/time_helper.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'date'
+require 'active_support/core_ext/date_and_time/compatibility'
+
+module TimeHelper
+ TIME_FORMAT = '%m/%d/%Y, %H:%M %p %Z'
+ DISPLAY_FORMAT = '%A, %d %B %Y, %h:%M %p'
+
+ class << self
+ def for_display(datetime)
+ return nil if datetime.nil?
+
+ datetime.strftime(DISPLAY_FORMAT) if [Date, DateTime, Time, ActiveSupport::TimeWithZone].include?(datetime.class)
+ end
+
+ def to_string_for_flatpickr(datetime)
+ return nil if datetime.nil?
+
+ datetime.strftime(TIME_FORMAT) if [Date, DateTime, Time, ActiveSupport::TimeWithZone].include?(datetime.class)
+ end
+
+ def to_datetime_from_picker(datetime_string)
+ return nil if datetime_string.nil?
+
+ ::DateTime.strptime("#{datetime_string.upcase} #{Time.current.strftime('%z')}Z", TIME_FORMAT).to_time
+ end
+ end
+end
diff --git a/app/javascript/add_jquery.js b/app/javascript/add_jquery.js
new file mode 100644
index 00000000..efa5f9f7
--- /dev/null
+++ b/app/javascript/add_jquery.js
@@ -0,0 +1,3 @@
+import jquery from "jquery";
+window.jQuery = jquery;
+window.$ = jquery;
diff --git a/app/javascript/application.js b/app/javascript/application.js
new file mode 100644
index 00000000..91db04e3
--- /dev/null
+++ b/app/javascript/application.js
@@ -0,0 +1,18 @@
+//*= require_tree .
+
+import "@hotwired/turbo-rails"
+import "@hotwired/stimulus"
+import "@popperjs/core";
+import "bootstrap";
+
+import 'flatpickr/dist/flatpickr.min.js';
+
+import "./add_jquery"
+
+import "./controllers"
+import "./payments"
+import "./popovers"
+import "./ticket_requests"
+import "./datepicker"
+
+
diff --git a/app/javascript/controllers/application.js b/app/javascript/controllers/application.js
new file mode 100644
index 00000000..7b847b68
--- /dev/null
+++ b/app/javascript/controllers/application.js
@@ -0,0 +1,11 @@
+import {Application} from "@hotwired/stimulus"
+
+const application = Application.start()
+
+// Configure Stimulus development experience
+application.debug = false
+window.Stimulus = application
+
+export {application}
+
+
diff --git a/app/javascript/controllers/flatpickr_controller.js b/app/javascript/controllers/flatpickr_controller.js
new file mode 100644
index 00000000..46f8ba28
--- /dev/null
+++ b/app/javascript/controllers/flatpickr_controller.js
@@ -0,0 +1,22 @@
+import {Controller} from "@hotwired/stimulus";
+import flatpickr from "flatpickr";
+
+// Connects to data-controller="flatpickr"
+export default class flatpickrController extends Controller {
+ connect() {
+ flatpickr(".flatpickr-date-time", {
+ enableTime: true,
+ altInput: false,
+ dateFormat: "m/d/Y, h:i K",
+ minDate: "today",
+ }
+ );
+
+ flatpickr(".flatpickr-date", {
+ altInput: false,
+ enableTime: false,
+ dateFormat: "m/d/Y",
+ }
+ );
+ }
+}
diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js
new file mode 100644
index 00000000..1ccc4c88
--- /dev/null
+++ b/app/javascript/controllers/index.js
@@ -0,0 +1,17 @@
+// This file is auto-generated by ./bin/rails stimulus:manifest:update
+// Run that command whenever you add a new controller or create them with
+// ./bin/rails generate stimulus controllerName
+
+import { application } from "./application"
+
+import FlatpickrController from "./flatpickr_controller"
+application.register("flatpickr", FlatpickrController)
+
+import PaymentsController from "./payments_controller"
+application.register("payments", PaymentsController)
+
+import PopoversController from "./popovers_controller"
+application.register("popovers", PopoversController)
+
+import TicketRequestsController from "./ticket_requests_controller"
+application.register("ticket-requests", TicketRequestsController)
diff --git a/app/javascript/controllers/payments_controller.ts b/app/javascript/controllers/payments_controller.ts
new file mode 100644
index 00000000..a703f791
--- /dev/null
+++ b/app/javascript/controllers/payments_controller.ts
@@ -0,0 +1,123 @@
+import {Controller} from "@hotwired/stimulus"
+import {loadStripe, Stripe} from "@stripe/stripe-js"
+
+// Connects to data-controller="payments"
+export default class PaymentsController extends Controller {
+ declare stripe: Promise;
+ declare stripeKey: string;
+ declare controller: Controller;
+
+ connect() {
+ this.stripeKey = $('meta[name="stripe-key"]').attr('content') || this.element.getAttribute('data-stripe-publishable-key');
+ this.stripe = loadStripe(this.stripeKey);
+ this.controller = this;
+ }
+
+ setupForm(): boolean {
+ const controller = this;
+
+ $('#new_payment').submit(function (): boolean {
+ $('input[type=submit]').attr('disabled', "true");
+ if ($('#card_number').length) {
+ controller.processCard();
+ return false;
+ } else {
+ return true;
+ }
+ });
+
+ $('.donation-option input[type=radio]').on('change', (function (): void {
+ let totalFee: HTMLElement
+ let totalPrice: HTMLElement
+ let amount: number;
+
+
+ totalPrice = $('#total_price').get(0)
+ totalFee = $('#total_fee').get(0);
+ amount = parseFloat($(this).val().toString()) + parseFloat(totalPrice.attributes["data"]('price'));
+ totalPrice.text('$' + amount.toFixed(2));
+ totalFee.text('$' + controller.extraFee(amount).toFixed(2));
+ }));
+
+ $('#new_eald_payment').submit(function (): boolean {
+ console.log('Submitting...');
+ $('input[type=submit]').attr('disabled', "true");
+ console.log('What');
+ if ($('#card_number').length) {
+ controller.processCard();
+ return false;
+ } else {
+ return true;
+ }
+ });
+
+ return $('#eald_payment_early_arrival_passes, #eald_payment_late_departure_passes').on('change mouseup', this.handlePasses());
+ }
+
+ handlePasses(): boolean {
+ const controller = this;
+
+ let ealdFee: HTMLElement;
+ let ealdPrice: HTMLElement;
+ let earlyPasses: HTMLElement;
+ let latePasses: HTMLElement;
+ let price: number;
+
+ ealdPrice = $('#eald_total_price');
+ ealdFee = $('#eald_fee');
+ earlyPasses = $('#eald_payment_early_arrival_passes');
+ latePasses = $('#eald_payment_late_departure_passes');
+
+ price =
+ parseFloat(earlyPasses.val().toString()) * parseFloat(earlyPasses.data('default-price')) +
+ parseFloat(latePasses.val().toString()) * parseFloat(latePasses.data('default-price'));
+
+ ealdPrice.text('$' + price.toFixed(2));
+
+ ealdFee.text('$' + controller.extraFee(price).toFixed(2));
+
+ return true;
+ }
+
+ extraFee(amount: number): number {
+ let fee: number, stripeFee: number, stripeRate: number;
+ stripeRate = 0.029;
+ stripeFee = 0.30;
+ if (amount === 0) {
+ return 0;
+ } else {
+ fee = (amount * stripeRate + stripeFee) / (1 - stripeRate);
+ return Math.ceil(fee * 100) / 100;
+ }
+ }
+
+ processCard(): void {
+ const controller = this;
+
+ let card: {
+ number: string,
+ cvc: string,
+ expMonth: string,
+ expYear: string
+ };
+
+ card = {
+ number: $('#card_number').val().toString(),
+ cvc: $('#card_code').val().toString(),
+ expMonth: $('#card_month').val().toString(),
+ expYear: $('#card_year').val().toString()
+ };
+
+ return controller.stripe.createToken(card, controller.handleStripeResponse);
+ }
+
+ handleStripeResponse(status: number, response: any): void {
+ if (status === 200) {
+ $('#stripe_card_token').val(response.id);
+ return $('form')[0].submit();
+ } else {
+ $('#stripe-error').text(response.error.message);
+ return $('input[type=submit]').attr('disabled', false);
+ }
+ }
+}
diff --git a/app/javascript/controllers/popovers_controller.js b/app/javascript/controllers/popovers_controller.js
new file mode 100644
index 00000000..a7acf04f
--- /dev/null
+++ b/app/javascript/controllers/popovers_controller.js
@@ -0,0 +1,7 @@
+import { Controller } from "@hotwired/stimulus"
+
+// Connects to data-controller="popovers"
+export default class extends Controller {
+ connect() {
+ }
+}
diff --git a/app/javascript/controllers/stripe_controller.ts b/app/javascript/controllers/stripe_controller.ts
new file mode 100644
index 00000000..4a7ea7b4
--- /dev/null
+++ b/app/javascript/controllers/stripe_controller.ts
@@ -0,0 +1,97 @@
+import {Controller} from "@hotwired/stimulus"
+import {loadStripe, Stripe} from "@stripe/stripe-js"
+
+// Connects to data-controller="stripe"
+export default class extends Controller {
+ connect() {
+
+ const stripeKey: string = $('meta[name="stripe-key"]').attr('content') || this.element.getAttribute('data-stripe-publishable-key');
+
+ let stripe: Promise;
+
+ stripe = loadStripe(stripeKey);
+
+ let elements;
+
+ initialize(stripeKey);
+
+ checkStatus();
+
+ this.element.addEventListener("submit", handleSubmit);
+
+ async function initialize(client_secret) {
+ elements = stripe.elements({ clientSecret: client_secret });
+
+ const paymentElement = elements.create("payment");
+ paymentElement.mount("#payment-element");
+ }
+
+ async function handleSubmit(e) {
+ e.preventDefault();
+ setLoading(true);
+
+ const { error } = await stripe.confirmPayment({
+ elements,
+ confirmParams: {
+ return_url: "http://localhost:3000/orders/"
+ },
+ });
+
+ if (error.type === "card_error" || error.type === "validation_error") {
+ showMessage(error.message);
+ } else {
+ showMessage("An unexpected error occurred.");
+ }
+
+ setLoading(false);
+ }
+
+ async function checkStatus() {
+ const clientSecret = new URLSearchParams(window.location.search).get(
+ "payment_intent_client_secret"
+ );
+
+ if (!clientSecret) {
+ return;
+ }
+
+ const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret);
+
+ switch (paymentIntent.status) {
+ case "succeeded":
+ showMessage("Payment succeeded!");
+ break;
+ case "processing":
+ showMessage("Your payment is processing.");
+ break;
+ case "requires_payment_method":
+ showMessage("Your payment was not successful, please try again.");
+ break;
+ default:
+ showMessage("Something went wrong.");
+ break;
+ }
+ }
+
+ function showMessage(messageText) {
+ const messageContainer = document.querySelector("#payment-message");
+
+ messageContainer.classList.remove("hidden");
+ messageContainer.classList.add("alert");
+ messageContainer.classList.add("alert-danger");
+ messageContainer.textContent = messageText;
+ }
+
+ function setLoading(isLoading) {
+ if (isLoading) {
+ document.querySelector("#submit").disabled = true;
+ document.querySelector("#spinner").classList.remove("hidden");
+ document.querySelector("#button-text").classList.add("hidden");
+ } else {
+ document.querySelector("#submit").disabled = false;
+ document.querySelector("#spinner").classList.add("hidden");
+ document.querySelector("#button-text").classList.remove("hidden");
+ }
+ }
+ }
+}
diff --git a/app/javascript/controllers/ticket_requests_controller.js b/app/javascript/controllers/ticket_requests_controller.js
new file mode 100644
index 00000000..d7b75d96
--- /dev/null
+++ b/app/javascript/controllers/ticket_requests_controller.js
@@ -0,0 +1,7 @@
+import { Controller } from "@hotwired/stimulus"
+
+// Connects to data-controller="ticket-requests"
+export default class extends Controller {
+ connect() {
+ }
+}
diff --git a/app/javascript/datepicker.js b/app/javascript/datepicker.js
new file mode 100644
index 00000000..0a2b8cfa
--- /dev/null
+++ b/app/javascript/datepicker.js
@@ -0,0 +1,6 @@
+
+// $(".flatpickr-date-time").flatpickr({
+// mode: "single",
+// enableTime: true,
+// dateFormat: "Y/m/d H:M",
+// });
diff --git a/app/javascript/payments.js b/app/javascript/payments.js
new file mode 100644
index 00000000..e69de29b
diff --git a/app/javascript/popovers.js b/app/javascript/popovers.js
new file mode 100644
index 00000000..b9c94748
--- /dev/null
+++ b/app/javascript/popovers.js
@@ -0,0 +1,22 @@
+import jQuery from 'jquery';
+
+(function ($) {
+ const popover = $('.help-popover');
+ popover({
+ trigger: 'manual'
+ }).on('click', function (e) {
+ const popoverClicked = $(this);
+ popoverClicked('toggle');
+ e.preventDefault();
+ });
+})(jQuery);
+
+(function () {
+ jQuery(function () {
+ $("a[rel~=popover], .has-popover").popover;
+ return $("a[rel~=tooltip], .has-tooltip").tooltip;
+ });
+
+}).call(this);
+
+$('.hover-tooltip').tooltip();
diff --git a/app/javascript/ticket_requests.js b/app/javascript/ticket_requests.js
new file mode 100644
index 00000000..ad96e068
--- /dev/null
+++ b/app/javascript/ticket_requests.js
@@ -0,0 +1,101 @@
+window.addEventListener("load", function () {
+ $('#ticket_request_user_attributes_email')
+ .on('change', function (evt) {
+ var $emailField = $(this),
+ email = $emailField.val(),
+ lookupPath = $emailField.data('lookup-url');
+
+ $.get(lookupPath + '?email=' + email)
+ .success(function () {
+ $('#email-warning').removeClass('hidden');
+ })
+ .fail(function () {
+ $('#email-warning').addClass('hidden');
+ }).always(function () {
+ $('#email-reset-sent').addClass('hidden');
+ });
+ });
+
+ $('#password-reset-link')
+ .on('click', function (evt) {
+ var resetUrl = $(this).data('reset-url'),
+ $emailField = $('#ticket_request_user_attributes_email'),
+ email = $emailField.val();
+ $.get(resetUrl + '?email=' + email)
+ .success(function () {
+ $('#email-warning').addClass('hidden');
+ $('#email-reset-sent').removeClass('hidden');
+ });
+ evt.preventDefault();
+ });
+
+ $('#ticket_request_adults, #ticket_request_kids, #ticket_request_cabins, #ticket_request_early_arrival_passes, #ticket_request_late_departure_passes')
+ .on('change keyup mouseup', function (evt) {
+ var $numberField = $(this),
+ $priceDisplay = $numberField.find('+ .inline-price'),
+ quantity = $numberField.val() || 0,
+ prices = $numberField.data('custom-prices') || {},
+ price = prices[quantity] || $numberField.data('default-price') * quantity,
+ text_price = price ? '$' + price : '';
+
+ $priceDisplay.text(text_price);
+ })
+ .each(function (idx, el) {
+ var $el = $(el);
+ // Force update so prices reflect default values
+ // (don't update disabled elements as they already have text)
+ if (!$el.is(':disabled')) {
+ $el.change();
+ }
+ });
+
+ $('input[name="ticket_request[role]"]')
+ .on('change', function (evt) {
+ var $radioBtn = $(this),
+ role = $radioBtn.val(),
+ $roleRadioBtn = $('#ticket_request_role_' + role),
+ maxTickets = $roleRadioBtn.data('max-tickets'),
+ $roleExplanationField = $('textarea[name="ticket_request[role_explanation]"]'),
+ $roleExplanations = $('.role-explanation'),
+ $selectedRoleExplanation = $('.role-explanation.' + role),
+ $ticketsField = $('input[name="ticket_request[adults]"]');
+
+ // Set max # of tickets to limit imposed by role
+ $ticketsField.attr('max', maxTickets);
+
+ // Reduce tickets if changing role causes maximum to be exceeded
+ if ($ticketsField.val() > maxTickets) {
+ $ticketsField.val(maxTickets);
+ $ticketsField.change();
+ }
+
+ // Show the explanation matching the selected role
+ $roleExplanations.addClass('hidden');
+ $selectedRoleExplanation.removeClass('hidden');
+
+ $roleExplanationField.toggleClass('hidden', role == 'volunteer');
+
+ // Don't require explanation if role is "Volunteer"
+ $roleExplanationField.attr('required', role != 'volunteer');
+ });
+
+ $('input[name="ticket_request[car_camping]"]')
+ .on('change', function (evt) {
+ var $carCampingChkbx = $(this),
+ carCamping = $carCampingChkbx.is(':checked'),
+ $carCampingExplanationField = $('textarea[name="ticket_request[car_camping_explanation]"]');
+
+ $carCampingExplanationField.toggleClass('hidden', !carCamping);
+
+ // Don't require explanation if not requesting anything
+ $carCampingExplanationField.attr('required', carCamping);
+ });
+
+ $('input[name="ticket_request[agrees_to_terms]"]')
+ .on('change', function (evt) {
+ var $agreesChkbx = $(this),
+ agrees = $agreesChkbx.is(':checked');
+
+ $('#submit-request').attr('disabled', !agrees);
+ });
+});
diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb
new file mode 100644
index 00000000..bef39599
--- /dev/null
+++ b/app/jobs/application_job.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class ApplicationJob < ActiveJob::Base
+ # Automatically retry jobs that encountered a deadlock
+ # retry_on ActiveRecord::Deadlocked
+
+ # Most jobs are safe to ignore if the underlying records are no longer available
+ # discard_on ActiveJob::DeserializationError
+end
diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb
new file mode 100644
index 00000000..2341e009
--- /dev/null
+++ b/app/mailers/application_mailer.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class ApplicationMailer < ActionMailer::Base
+ DEFAULT_SENDER_EMAIL = 'tickets@fnf.org'
+ DEFAULT_REPLY_TO_EMAIL = 'tickets@fnf.org'
+
+ layout 'email'
+
+ abstract!
+
+ protected
+
+ def from_email
+ "#{@event.name} <#{DEFAULT_SENDER_EMAIL}>" if defined?(:@event)
+ end
+
+ def reply_to_email
+ "#{@event.name} Ticketing <#{DEFAULT_REPLY_TO_EMAIL}>" if defined?(:@event)
+ end
+end
diff --git a/app/mailers/eald_payment_mailer.rb b/app/mailers/eald_payment_mailer.rb
index e9cd6854..99b4ce2e 100644
--- a/app/mailers/eald_payment_mailer.rb
+++ b/app/mailers/eald_payment_mailer.rb
@@ -1,13 +1,6 @@
# frozen_string_literal: true
-class EaldPaymentMailer < ActionMailer::Base
- DEFAULT_SENDER_EMAIL = 'tickets@fnf.org'
- DEFAULT_REPLY_TO_EMAIL = 'tickets@fnf.org'
-
- add_template_helper(PaymentsHelper)
-
- layout 'email'
-
+class EaldPaymentMailer < ApplicationMailer
def eald_payment_received(eald_payment)
@eald_payment = eald_payment
@event = @eald_payment.event
@@ -16,14 +9,4 @@ def eald_payment_received(eald_payment)
reply_to: reply_to_email,
subject: "Your payment for #{@event.name} Early Arrival/Late Departure passes has been received"
end
-
- private
-
- def from_email
- "#{@event.name} <#{DEFAULT_SENDER_EMAIL}>"
- end
-
- def reply_to_email
- "#{@event.name} Ticketing <#{DEFAULT_REPLY_TO_EMAIL}>"
- end
end
diff --git a/app/mailers/payment_mailer.rb b/app/mailers/payment_mailer.rb
index c6e8a206..fdc3ea8f 100644
--- a/app/mailers/payment_mailer.rb
+++ b/app/mailers/payment_mailer.rb
@@ -1,18 +1,11 @@
# frozen_string_literal: true
-class PaymentMailer < ActionMailer::Base
- DEFAULT_SENDER_EMAIL = 'tickets@fnf.org'
- DEFAULT_REPLY_TO_EMAIL = 'tickets@fnf.org'
-
- add_template_helper(PaymentsHelper)
-
- layout 'email'
+class PaymentMailer < ApplicationMailer
+ include PaymentsHelper
def payment_received(payment)
- @payment = payment
- @ticket_request = @payment.ticket_request
- @event = @ticket_request.event
- @user = @ticket_request.user
+ self.payment = payment
+
mail to: "#{@user.name} <#{@user.email}>",
from: from_email,
reply_to: reply_to_email,
@@ -21,11 +14,10 @@ def payment_received(payment)
private
- def from_email
- "#{@event.name} <#{DEFAULT_SENDER_EMAIL}>"
- end
-
- def reply_to_email
- "#{@event.name} Ticketing <#{DEFAULT_REPLY_TO_EMAIL}>"
+ def payment=(payment)
+ @payment = payment
+ @ticket_request = @payment.ticket_request
+ @event = @ticket_request&.event
+ @user = @ticket_request&.user
end
end
diff --git a/app/mailers/ticket_request_mailer.rb b/app/mailers/ticket_request_mailer.rb
index 32e679c9..5fe12e44 100644
--- a/app/mailers/ticket_request_mailer.rb
+++ b/app/mailers/ticket_request_mailer.rb
@@ -1,64 +1,103 @@
# frozen_string_literal: true
require 'awesome_print'
-class TicketRequestMailer < ActionMailer::Base
- DEFAULT_SENDER_EMAIL = 'tickets@fnf.org'
- DEFAULT_REPLY_TO_EMAIL = 'tickets@fnf.org'
- layout 'email'
+require_relative 'application_mailer'
+require_relative '../concerns/routing'
+class TicketRequestMailer < ApplicationMailer
+ include ::Routing
+
+ # The `request_received` method is used to send an email when a ticket request is received.
+ # It sets the ticket request and then sends an email to the user who made the request.
+ #
+ # @param ticket_request [TicketRequest] The ticket request that has been received.
+ # @return [Mail::Message] The email that has been prepared to be sent.
def request_received(ticket_request)
- @ticket_request = ticket_request
- @event = @ticket_request.event
- mail to: to_email,
- from: from_email,
- reply_to: reply_to_email,
- subject: "#{@event.name} ticket request confirmation"
+ # Set the ticket request
+ self.ticket_request = ticket_request
+
+ @ticket_request_url = event_ticket_request_url(event_id: @event.id, id: @ticket_request.id)
+
+ # Prepare the email to be sent
+ mail to: to_email, # The recipient of the email
+ from: from_email, # The sender of the email
+ reply_to: reply_to_email, # The email address that will receive replies
+ subject: "#{@event.name} ticket request confirmation" # The subject of the email
end
+ # The `request_approved` method is used to send an email when a ticket request is approved.
+ # It sets the ticket request, generates an authentication token for the user, and then sends an email to the user.
+ #
+ # @param ticket_request [TicketRequest] The ticket request that has been approved.
+ #
+ # @return [Mail::Message, nil] The email that has been prepared to be sent, or nil if the authentication token is blank.
def request_approved(ticket_request)
- @auth_token = ticket_request.user.generate_auth_token!
- @ticket_request = ticket_request
- @event = @ticket_request.event
- mail to: to_email,
- from: from_email,
- reply_to: reply_to_email,
- subject: "Your #{@event.name} ticket request has been approved!"
- end
+ # Set the ticket request
+ self.ticket_request = ticket_request
- private
+ # Generate an authentication token for the user
+ @auth_token = ticket_request&.user&.generate_auth_token!
- def to_email
- "#{@ticket_request.user.name} <#{@ticket_request.user.email}>"
+ @payment_url = new_payment_url(ticket_request: @ticket_request,
+ user: @ticket_request.user,
+ user_token: @auth_token)
+
+ if @event.eald?
+ @extra_params = {}.tap do |params|
+ params[:early_arrival_passes] = @ticket_request.early_arrival_passes
+ params[:late_departure_passes] = @ticket_request.late_departure_passes
+ params[:email] = @ticket_request.user.email
+ params[:name] = @ticket_request.user.name
+ end
+
+ @eald_url = new_event_eald_payment_path(@event, @extra_params)
+ end
+
+ # Return if the authentication token is blank
+ return if @auth_token.blank?
+
+ # Prepare the email to be sent
+ mail to: to_email, # The recipient of the email
+ from: from_email, # The sender of the email
+ reply_to: reply_to_email, # The email address that will ¬receive replies
+ subject: "Your #{@event.name} ticket request has been approved!" # The subject of the email
end
- def from_email
- "#{@event.name} <#{DEFAULT_SENDER_EMAIL}>"
+ private
+
+ def ticket_request=(ticket_request)
+ @ticket_request = ticket_request
+ @event = @ticket_request&.event
end
- def reply_to_email
- "#{@event.name} Ticketing <#{DEFAULT_REPLY_TO_EMAIL}>"
+ def to_email
+ "#{@ticket_request.user.name} <#{@ticket_request.user.email}>"
end
class << self
def mail_config
- TicketBooth::Application.configure do
+ TicketBooth::Application.configure do |config|
return config.action_mailer
end
end
- def ticket_request_event(event)
+ def ticket_request(event)
request_received(event.ticket_request).tap do |mail|
Rails.logger.info("delivering mail #{mail.inspect}")
Rails.logger.info("mail config: #{mail_config.inspect}")
end.deliver_now
end
- def ticket_request_approved_event(event)
+ def ticket_request_approved(event)
request_approved(event.ticket_request).tap do |mail|
Rails.logger.info("delivering mail #{mail.inspect}")
Rails.logger.info("mail config: #{mail_config.inspect}")
end.deliver_now
end
+
+ def ticket_request_declined(_)
+ # Not yet implemented
+ end
end
end
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
new file mode 100644
index 00000000..10a4cba8
--- /dev/null
+++ b/app/models/application_record.rb
@@ -0,0 +1,3 @@
+class ApplicationRecord < ActiveRecord::Base
+ self.abstract_class = true
+end
diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep
new file mode 100644
index 00000000..e69de29b
diff --git a/app/models/eald_payment.rb b/app/models/eald_payment.rb
index 50e51110..b13cd5a5 100644
--- a/app/models/eald_payment.rb
+++ b/app/models/eald_payment.rb
@@ -1,23 +1,38 @@
# frozen_string_literal: true
-# Payment for early arrival/late departures passes.
+# == Schema Information
#
-# We only associate this with an event, as we want to treat these separately
-# from ticket requests.
-class EaldPayment < ActiveRecord::Base
+# Table name: eald_payments
+#
+# id :bigint not null, primary key
+# amount_charged_cents :integer not null
+# early_arrival_passes :integer default(0), not null
+# email :string(255) not null
+# late_departure_passes :integer default(0), not null
+# name :string(255) not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# event_id :bigint
+# stripe_charge_id :string not null
+#
+# Indexes
+#
+# index_eald_payments_on_event_id (event_id)
+#
+class EaldPayment < ApplicationRecord
include PaymentsHelper
belongs_to :event
attr_accessible :event_id, :name, :email, :early_arrival_passes, :late_departure_passes, :stripe_card_token
- validates :event_id, presence: true
-
- validates :early_arrival_passes, presence: true,
- numericality: { only_integer: true, greater_than_or_equal_to: 0 }
+ validates :early_arrival_passes,
+ presence: true,
+ numericality: { only_integer: true, greater_than_or_equal_to: 0 }
- validates :late_departure_passes, presence: true,
- numericality: { only_integer: true, greater_than_or_equal_to: 0 }
+ validates :late_departure_passes,
+ presence: true,
+ numericality: { only_integer: true, greater_than_or_equal_to: 0 }
attr_accessor :stripe_card_token
diff --git a/app/models/event.rb b/app/models/event.rb
index ce4fd663..ff70c420 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -1,7 +1,34 @@
# frozen_string_literal: true
-# Core object containing all information related to an actual event being held.
-class Event < ActiveRecord::Base
+# == Schema Information
+#
+# Table name: events
+#
+# id :bigint not null, primary key
+# adult_ticket_price :decimal(8, 2)
+# allow_donations :boolean default(FALSE), not null
+# allow_financial_assistance :boolean default(FALSE), not null
+# cabin_price :decimal(8, 2)
+# early_arrival_price :decimal(8, 2) default(0.0)
+# end_time :datetime
+# kid_ticket_price :decimal(8, 2)
+# late_departure_price :decimal(8, 2) default(0.0)
+# max_adult_tickets_per_request :integer
+# max_cabin_requests :integer
+# max_cabins_per_request :integer
+# max_kid_tickets_per_request :integer
+# name :string
+# photo :string
+# require_mailing_address :boolean default(FALSE), not null
+# start_time :datetime
+# ticket_requests_end_time :datetime
+# ticket_sales_end_time :datetime
+# ticket_sales_start_time :datetime
+# tickets_require_approval :boolean default(TRUE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+class Event < ApplicationRecord
has_many :event_admins
has_many :admins, through: :event_admins, source: :user
has_many :jobs, dependent: :destroy
@@ -45,7 +72,13 @@ class Event < ActiveRecord::Base
:ensure_prices_set_if_maximum_specified
def admin?(user)
- user && (user.site_admin? || admins.where(id: user).exists?)
+ user && (user.site_admin? || admins.exists?(id: user))
+ end
+
+ def make_admin(user)
+ return false if admin?(user)
+
+ EventAdmin.create(user_id: user.id, event_id: id)
end
def cabins_available?
@@ -56,18 +89,18 @@ def cabins_available?
end
def ticket_sales_open?
- return false if ticket_sales_start_time && Time.now < ticket_sales_start_time
- return Time.now < ticket_sales_end_time if ticket_sales_end_time
- return false if Time.now >= end_time
+ return false if ticket_sales_start_time && Time.zone.now < ticket_sales_start_time
+ return Time.zone.now < ticket_sales_end_time if ticket_sales_end_time
+ return false if Time.zone.now >= end_time
true
end
def ticket_requests_open?
- return false if Time.now >= end_time
+ return false if Time.zone.now >= end_time
return true unless ticket_requests_end_time
- ticket_requests_end_time > Time.now
+ ticket_requests_end_time > Time.zone.now
end
def eald?
diff --git a/app/models/event_admin.rb b/app/models/event_admin.rb
index a4ec91b3..4cef97f4 100644
--- a/app/models/event_admin.rb
+++ b/app/models/event_admin.rb
@@ -1,13 +1,31 @@
# frozen_string_literal: true
-class EventAdmin < ActiveRecord::Base
+# == Schema Information
+#
+# Table name: event_admins
+#
+# id :bigint not null, primary key
+# created_at :datetime not null
+# updated_at :datetime not null
+# event_id :bigint
+# user_id :bigint
+#
+# Indexes
+#
+# index_event_admins_on_event_id_and_user_id (event_id,user_id) UNIQUE
+# index_event_admins_on_user_id (user_id)
+#
+class EventAdmin < ApplicationRecord
attr_accessible :event_id, :user_id
belongs_to :event
belongs_to :user
- validates :event_id, presence: true
+ validates :user_id,
+ uniqueness: { scope: :event_id, message: 'already admin for this event' }
- validates :user_id, presence: true,
- uniqueness: { scope: :event_id, message: 'already admin for this event' }
+ validate do |record|
+ raise ArgumentError, 'event_id is nil' if record.event_id.nil?
+ raise ArgumentError, 'user_id is nil' if record.user_id.nil?
+ end
end
diff --git a/app/models/job.rb b/app/models/job.rb
index eb405560..26f3557f 100644
--- a/app/models/job.rb
+++ b/app/models/job.rb
@@ -1,13 +1,28 @@
# frozen_string_literal: true
-class Job < ActiveRecord::Base
+# == Schema Information
+#
+# Table name: jobs
+#
+# id :bigint not null, primary key
+# description :string(512) not null
+# name :string(100) not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# event_id :bigint not null
+#
+# Indexes
+#
+# index_jobs_on_event_id (event_id)
+#
+class Job < ApplicationRecord
belongs_to :event
has_many :time_slots, dependent: :destroy
attr_accessible :description, :event_id, :name
- validates :event_id, presence: true,
- numericality: { only_integer: true, greater_than: 0 }
+ validates :event_id,
+ numericality: { only_integer: true, greater_than: 0 }
validates :name, presence: true, length: { maximum: 100 }
validates :description, presence: true, length: { maximum: 512 }
diff --git a/app/models/payment.rb b/app/models/payment.rb
index 63284af9..61b95272 100644
--- a/app/models/payment.rb
+++ b/app/models/payment.rb
@@ -1,6 +1,18 @@
# frozen_string_literal: true
-class Payment < ActiveRecord::Base
+# == Schema Information
+#
+# Table name: payments
+#
+# id :bigint not null, primary key
+# explanation :string
+# status :string(1) default("P"), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# stripe_charge_id :string(255)
+# ticket_request_id :integer not null
+#
+class Payment < ApplicationRecord
include PaymentsHelper
STATUSES = [
@@ -18,8 +30,8 @@ class Payment < ActiveRecord::Base
attr_accessor :stripe_card_token
- validates :ticket_request, presence: true,
- uniqueness: { message: 'ticket request has already been paid' }
+ validates :ticket_request,
+ uniqueness: { message: 'ticket request has already been paid' }
validates :status, presence: true, inclusion: { in: STATUSES }
def save_and_charge!
@@ -50,9 +62,7 @@ def mark_received
save!
end
- def can_view?(user)
- ticket_request.can_view?(user)
- end
+ delegate :can_view?, to: :ticket_request
def due_date
(created_at + 2.weeks).to_date
@@ -75,7 +85,7 @@ def via_credit_card?
def modifying_forbidden_attributes?(attributed)
# Only allow donation field to be updated
attributed.any? do |attribute, _value|
- !%w[donation id].include?(attribute.to_s)
+ %w[donation id].exclude?(attribute.to_s)
end
end
end
diff --git a/app/models/price_rule.rb b/app/models/price_rule.rb
index ffd59ec6..f5ba5445 100644
--- a/app/models/price_rule.rb
+++ b/app/models/price_rule.rb
@@ -1,11 +1,26 @@
# frozen_string_literal: true
-class PriceRule < ActiveRecord::Base
+# == Schema Information
+#
+# Table name: price_rules
+#
+# id :bigint not null, primary key
+# price :decimal(8, 2)
+# trigger_value :integer
+# type :string
+# created_at :datetime not null
+# updated_at :datetime not null
+# event_id :bigint
+#
+# Indexes
+#
+# index_price_rules_on_event_id (event_id)
+#
+class PriceRule < ApplicationRecord
belongs_to :event
attr_accessible :event, :price, :trigger_value, :type
- validates :event_id, presence: true
validates :price,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :trigger_value, presence: true,
diff --git a/app/models/price_rule/kids_equal_to.rb b/app/models/price_rule/kids_equal_to.rb
index 3f90457d..ba9306b4 100644
--- a/app/models/price_rule/kids_equal_to.rb
+++ b/app/models/price_rule/kids_equal_to.rb
@@ -1,9 +1,23 @@
# frozen_string_literal: true
-# Applied when a ticket request has indicated that they are bringing a specific
-# number of kids.
+# == Schema Information
+#
+# Table name: price_rules
+#
+# id :bigint not null, primary key
+# price :decimal(8, 2)
+# trigger_value :integer
+# type :string
+# created_at :datetime not null
+# updated_at :datetime not null
+# event_id :bigint
+#
+# Indexes
+#
+# index_price_rules_on_event_id (event_id)
+#
class PriceRule
- class KidsEqualTo < PriceRule
+ class KidsEqualTo < ::PriceRule
def calc_price(ticket_request)
price if ticket_request.kids == trigger_value
end
diff --git a/app/models/shift.rb b/app/models/shift.rb
index 0939cde0..867e45e4 100644
--- a/app/models/shift.rb
+++ b/app/models/shift.rb
@@ -1,6 +1,22 @@
# frozen_string_literal: true
-class Shift < ActiveRecord::Base
+# == Schema Information
+#
+# Table name: shifts
+#
+# id :bigint not null, primary key
+# name :string(70)
+# created_at :datetime not null
+# updated_at :datetime not null
+# time_slot_id :bigint not null
+# user_id :bigint not null
+#
+# Indexes
+#
+# index_shifts_on_time_slot_id (time_slot_id)
+# index_shifts_on_user_id (user_id)
+#
+class Shift < ApplicationRecord
belongs_to :time_slot
belongs_to :user
@@ -9,11 +25,11 @@ class Shift < ActiveRecord::Base
validates :name, length: { in: 3..User::MAX_NAME_LENGTH },
allow_nil: true, allow_blank: false
- validates :time_slot_id, presence: true,
- numericality: { only_integer: true, greater_than: 0 }
+ validates :time_slot_id,
+ numericality: { only_integer: true, greater_than: 0 }
- validates :user_id, presence: true,
- numericality: { only_integer: true, greater_than: 0 }
+ validates :user_id,
+ numericality: { only_integer: true, greater_than: 0 }
def volunteer_name
self[:name] || user.name
diff --git a/app/models/site_admin.rb b/app/models/site_admin.rb
index bf0ea445..186995d2 100644
--- a/app/models/site_admin.rb
+++ b/app/models/site_admin.rb
@@ -1,10 +1,16 @@
# frozen_string_literal: true
-class SiteAdmin < ActiveRecord::Base
- attr_accessible :user_id
-
+# == Schema Information
+#
+# Table name: site_admins
+#
+# id :bigint not null, primary key
+# created_at :datetime not null
+# updated_at :datetime not null
+# user_id :integer not null
+#
+class SiteAdmin < ApplicationRecord
belongs_to :user
- validates :user_id, presence: true,
- uniqueness: { message: 'already site admin' }
+ validates :user, uniqueness: { message: 'already site admin' }
end
diff --git a/app/models/ticket_request.rb b/app/models/ticket_request.rb
index c4d3eedf..870b280c 100644
--- a/app/models/ticket_request.rb
+++ b/app/models/ticket_request.rb
@@ -1,9 +1,46 @@
# frozen_string_literal: true
-# Individual request for one or more tickets.
-# This is intended to capture as much information as possible about the ticket,
-# as well as the state of the request.
-class TicketRequest < ActiveRecord::Base
+#
+# rubocop: disable Metrics/ClassLength
+
+# == Schema Information
+#
+# Table name: ticket_requests
+#
+# id :bigint not null, primary key
+# address_line1 :string(200)
+# address_line2 :string(200)
+# admin_notes :string(512)
+# adults :integer default(1), not null
+# agrees_to_terms :boolean
+# cabins :integer default(0), not null
+# car_camping :boolean
+# car_camping_explanation :string(200)
+# city :string(50)
+# country_code :string(4)
+# donation :decimal(8, 2) default(0.0)
+# early_arrival_passes :integer default(0), not null
+# guests :text
+# kids :integer default(0), not null
+# late_departure_passes :integer default(0), not null
+# needs_assistance :boolean default(FALSE), not null
+# notes :string(500)
+# previous_contribution :string(250)
+# role :string default("volunteer"), not null
+# role_explanation :string(200)
+# special_price :decimal(8, 2)
+# state :string(50)
+# status :string(1) not null
+# zip_code :string(32)
+# created_at :datetime not null
+# updated_at :datetime not null
+# event_id :integer not null
+# user_id :integer not null
+#
+class TicketRequest < ApplicationRecord
+ include ActiveModel::Validations
+ include ActiveModel::Validations::Callbacks
+
STATUSES = [
STATUS_PENDING = 'P',
STATUS_AWAITING_PAYMENT = 'A',
@@ -28,10 +65,14 @@ class TicketRequest < ActiveRecord::Base
ROLE_OTHER => 'Other'
}.freeze
- belongs_to :user
- belongs_to :event
- has_one :payment
- serialize :guests, Array
+ belongs_to :user, inverse_of: :ticket_requests
+ belongs_to :event, inverse_of: :ticket_requests
+
+ has_one :payment, inverse_of: :ticket_request
+
+ # Serialize guest emails as an array in a text field.
+ # TODO: This should probably be switched to a separate table or at least a JSONB format column.
+ serialize :guests, coder: JSON, type: Array
attr_accessible :user_id, :adults, :kids, :cabins, :needs_assistance,
:notes, :status, :special_price, :event_id,
@@ -46,9 +87,11 @@ class TicketRequest < ActiveRecord::Base
accepts_nested_attributes_for :user
+ before_validation :set_defaults
+
validates :user, presence: { unless: -> { user.try(:new_record?) } }
- validates :event_id, presence: true
+ validates :event, presence: { unless: -> { event.try(:new_record?) } }
validates :status, presence: true, inclusion: { in: STATUSES }
@@ -90,20 +133,7 @@ class TicketRequest < ActiveRecord::Base
scope :awaiting_payment, -> { where(status: STATUS_AWAITING_PAYMENT) }
scope :approved, -> { awaiting_payment }
scope :declined, -> { where(status: STATUS_DECLINED) }
- scope :not_declined, -> { where('status != ?', STATUS_DECLINED) }
-
- before_validation do
- if event
- self.status ||= if event.tickets_require_approval
- STATUS_PENDING
- else
- STATUS_AWAITING_PAYMENT
- end
- end
-
- # Remove empty guests
- self.guests = Array(guests).map { |guest| guest.strip.presence }.compact
- end
+ scope :not_declined, -> { where.not(status: STATUS_DECLINED) }
def can_view?(user)
self.user == user || event.admin?(user)
@@ -114,7 +144,7 @@ def completed?
end
def mark_complete
- update_attributes status: STATUS_COMPLETED
+ update status: STATUS_COMPLETED
end
def pending?
@@ -130,7 +160,7 @@ def awaiting_payment?
end
def approve
- update_attributes status: (free? ? STATUS_COMPLETED : STATUS_AWAITING_PAYMENT)
+ update status: (free? ? STATUS_COMPLETED : STATUS_AWAITING_PAYMENT)
end
def declined?
@@ -159,7 +189,7 @@ def refund
begin
TicketRequest.transaction do
Stripe::Charge.retrieve(payment.stripe_charge_id).refund
- return update_attributes(status: STATUS_REFUNDED)
+ return update(status: STATUS_REFUNDED)
end
rescue Stripe::StripeError => e
errors.add(:base, "Cannot refund ticket: #{e.message}")
@@ -212,4 +242,25 @@ def all_guests_specified?
def country_name
ISO3166::Country[country_code]
end
+
+ private
+
+ def set_defaults
+ self.status = nil if status.blank?
+ self.status ||= if event
+ if event.tickets_require_approval
+ STATUS_PENDING
+ else
+ STATUS_AWAITING_PAYMENT
+ end
+ else
+ STATUS_PENDING
+ end
+
+ # Remove empty guests
+ # Note that guests are serialized as an array field.
+ self.guests = Array(guests).map { |guest| guest&.strip }.select(&:present?).compact
+ end
end
+
+# rubocop: enable Metrics/ClassLength
diff --git a/app/models/time_slot.rb b/app/models/time_slot.rb
index bc81f6bb..a1b22068 100644
--- a/app/models/time_slot.rb
+++ b/app/models/time_slot.rb
@@ -1,6 +1,22 @@
# frozen_string_literal: true
-class TimeSlot < ActiveRecord::Base
+# == Schema Information
+#
+# Table name: time_slots
+#
+# id :bigint not null, primary key
+# end_time :datetime not null
+# slots :integer not null
+# start_time :datetime not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# job_id :bigint not null
+#
+# Indexes
+#
+# index_time_slots_on_job_id (job_id)
+#
+class TimeSlot < ApplicationRecord
belongs_to :job
has_many :shifts, dependent: :destroy
@@ -10,7 +26,7 @@ class TimeSlot < ActiveRecord::Base
validates :end_time, presence: true
validate :end_time_after_start_time
- validates :job_id, presence: true, numericality: { only_integer: true },
+ validates :job_id, numericality: { only_integer: true },
allow_nil: false
validates :slots, presence: true,
numericality: { only_integer: true, greater_than: 0 }
diff --git a/app/models/user.rb b/app/models/user.rb
index c2ed7c00..efd202a9 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,31 +1,83 @@
# frozen_string_literal: true
+# == Schema Information
+#
+# Table name: users
+#
+# id :bigint not null, primary key
+# authentication_token :string(64)
+# confirmation_sent_at :datetime
+# confirmation_token :string
+# confirmed_at :datetime
+# current_sign_in_at :datetime
+# current_sign_in_ip :string
+# email :string not null
+# encrypted_password :string not null
+# failed_attempts :integer default(0)
+# first :text
+# last :text
+# last_sign_in_at :datetime
+# last_sign_in_ip :string
+# locked_at :datetime
+# name :string(70) not null
+# remember_created_at :datetime
+# reset_password_sent_at :datetime
+# reset_password_token :string
+# sign_in_count :integer default(0)
+# unconfirmed_email :string
+# unlock_token :string
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# index_users_on_confirmation_token (confirmation_token) UNIQUE
+# index_users_on_email (email) UNIQUE
+# index_users_on_reset_password_token (reset_password_token) UNIQUE
+# index_users_on_unlock_token (unlock_token) UNIQUE
+#
require 'securerandom'
# A real-life living breathing human.
-class User < ActiveRecord::Base
- # Include default devise modules. Others available are:
- # :token_authenticatable, :timeoutable and :omniauthable
- devise :database_authenticatable, :registerable, :lockable,
- :recoverable, :rememberable, :trackable, :validatable
+class User < ApplicationRecord
+ # @see https://dev.to/kevinluo201/introduction-to-devise-modules-and-enable-all-of-them-4p25
+ DEVISE_MODULES = %i[
+ database_authenticatable
+ registerable
+ lockable
+ recoverable
+ rememberable
+ trackable
+ validatable
+ ].freeze
+
+ devise(*DEVISE_MODULES)
+
+ class << self
+ define_method 'has_devise_module?' do |module_name|
+ DEVISE_MODULES.include?(module_name)
+ end
+ end
has_many :event_admins, dependent: :destroy
has_many :events_administrated, through: :event_admins, source: :event
has_many :ticket_requests
has_one :site_admin, dependent: :destroy
- MAX_NAME_LENGTH = 70
- MAX_EMAIL_LENGTH = 254 # Based on RFC 3696; see http://isemail.info/about
- MAX_PASSWORD_LENGTH = 255
+ MAX_NAME_LENGTH = 40
+ MAX_EMAIL_LENGTH = 80 # Based on RFC 3696; see http://isemail.info/about
+ MAX_PASSWORD_LENGTH = 40
- # Setup accessible (or protected) attributes for your model
- attr_accessible :name, :email, :password, :remember_me
+ before_validation :canonize_full_name!
normalize_attributes :name, :email
+ validates :first, presence: true
+ validates :last, presence: true
+
validates :name, presence: true,
length: { maximum: MAX_NAME_LENGTH },
- format: { with: /\A[^\s]+\s[^\s]+(\s[^\s]+)*\z/i,
+ format: { with: /\A\S+\s\S+(\s\S+)*\z/i,
message: 'must contain first and last name' }
validates :email, presence: true,
@@ -33,15 +85,19 @@ class User < ActiveRecord::Base
length: { maximum: MAX_EMAIL_LENGTH }
def first_name
- name.split.first
+ first
+ end
+
+ def last_name
+ last
end
def site_admin?
- !site_admin.nil?
+ site_admin.present?
end
def event_admin?
- event_admins.exists?
+ event_admins.present? && !event_admins.empty?
end
def generate_auth_token!
@@ -49,4 +105,15 @@ def generate_auth_token!
update_attribute(:authentication_token, token)
token
end
+
+ private
+
+ def canonize_full_name!
+ if name && first.nil? && last.nil?
+ self.first, self.last = *name.split(/\s+/).map(&:strip)
+ elsif name.nil? && first.present?
+ self.name = first if first
+ self.name += " #{last}" if name && last
+ end
+ end
end
diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml
new file mode 100644
index 00000000..9697b2ae
--- /dev/null
+++ b/app/views/devise/confirmations/new.html.haml
@@ -0,0 +1,18 @@
+%h2 Resend Confirmation Instructions
+
+= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f|
+ = render "devise/shared/error_messages", resource: resource
+
+ %h2 Didn't receive a confirmation email?
+
+ %p.lead
+ Fill out the form below with the email you used to register an account and we'll resend the confirmation email.
+
+ .field
+ %p= f.label :email
+ %p= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email)
+
+ .actions
+ = f.submit "Resend Confirmation Instructions", class: 'btn btn-primary btn-large'
+
+= render "devise/shared/links"
diff --git a/app/views/devise/mailer/confirmation_instructions.html.haml b/app/views/devise/mailer/confirmation_instructions.html.haml
new file mode 100644
index 00000000..99326d7e
--- /dev/null
+++ b/app/views/devise/mailer/confirmation_instructions.html.haml
@@ -0,0 +1,8 @@
+%p
+ Hi #{@resource.first}!
+
+%p
+ Thanks for registering an account. You can confirm your email through the following link:
+
+%p
+ = link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token)
diff --git a/app/views/devise/mailer/email_changed.html.haml b/app/views/devise/mailer/email_changed.html.haml
new file mode 100644
index 00000000..94ebba05
--- /dev/null
+++ b/app/views/devise/mailer/email_changed.html.haml
@@ -0,0 +1,8 @@
+%p
+ Hello #{@email}!
+- if @resource.try(:unconfirmed_email?)
+ %p
+ We're contacting you to notify you that your email is being changed to #{@resource.unconfirmed_email}.
+- else
+ %p
+ We're contacting you to notify you that your email has been changed to #{@resource.email}.
diff --git a/app/views/devise/mailer/password_change.html.haml b/app/views/devise/mailer/password_change.html.haml
new file mode 100644
index 00000000..ab7c04c4
--- /dev/null
+++ b/app/views/devise/mailer/password_change.html.haml
@@ -0,0 +1,3 @@
+%p
+ Hello #{@resource.email}!
+%p We're contacting you to notify you that your password has been changed.
diff --git a/app/views/devise/mailer/reset_password_instructions.html.haml b/app/views/devise/mailer/reset_password_instructions.html.haml
new file mode 100644
index 00000000..0711cd5a
--- /dev/null
+++ b/app/views/devise/mailer/reset_password_instructions.html.haml
@@ -0,0 +1,6 @@
+%p
+ Hello #{@resource.email}!
+%p Someone has requested a link to change your password. You can do this through the link below.
+%p= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token)
+%p If you didn't request this, please ignore this email.
+%p Your password won't change until you access the link above and create a new one.
diff --git a/app/views/devise/mailer/unlock_instructions.html.haml b/app/views/devise/mailer/unlock_instructions.html.haml
new file mode 100644
index 00000000..282c98a2
--- /dev/null
+++ b/app/views/devise/mailer/unlock_instructions.html.haml
@@ -0,0 +1,5 @@
+%p
+ Hello #{@resource.email}!
+%p Your account has been locked due to an excessive number of unsuccessful sign in attempts.
+%p Click the link below to unlock your account:
+%p= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token)
diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml
new file mode 100644
index 00000000..9ea4080b
--- /dev/null
+++ b/app/views/devise/passwords/edit.html.haml
@@ -0,0 +1,17 @@
+%h2 Change your password
+= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f|
+ = render "devise/shared/error_messages", resource: resource
+ = f.hidden_field :reset_password_token
+ .field
+ %p= f.label :password, "New password"
+ - if @minimum_password_length
+ %p
+ %em
+ (#{@minimum_password_length} characters minimum)
+ %p= f.password_field :password, autofocus: true, autocomplete: "new-password"
+ .field
+ %p= f.label :password_confirmation, "Confirm new password"
+ %p= f.password_field :password_confirmation, autocomplete: "new-password"
+ .actions
+ = f.submit "Change my password", class: 'btn btn-primary btn-large'
+= render "devise/shared/links"
diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml
new file mode 100644
index 00000000..7ad92bed
--- /dev/null
+++ b/app/views/devise/passwords/new.html.haml
@@ -0,0 +1,14 @@
+%h2 Forgot your password?
+
+= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f|
+
+ = render "devise/shared/error_messages", resource: resource
+
+ .field
+ %p= f.label :email
+ %p= f.email_field :email, autofocus: true, autocomplete: "email"
+
+ .actions
+ = f.submit "Send me password reset instructions", class: 'btn btn-primary btn-large'
+
+= render "devise/shared/links"
diff --git a/app/views/devise/registrations/_change_password.html.haml b/app/views/devise/registrations/_change_password.html.haml
new file mode 100644
index 00000000..e54c8e7e
--- /dev/null
+++ b/app/views/devise/registrations/_change_password.html.haml
@@ -0,0 +1,20 @@
+.row
+ .col-md-6.col-sm-12
+ .vertical-20
+ - if existing
+ %small.nowrap NOTE: leave these fields blank if you don't want to change your password.
+ - else
+ %h3 Please enter your new password, twice for confirmation.
+
+.row
+ .col-md-6.col-sm-12
+ = f.label :password
+ = f.password_field :password, autocomplete: "new-password"
+ - if @minimum_password_length
+ %small.nowrap
+ NOTE:
+ = "#{@minimum_password_length} characters minimum"
+
+ .col-6
+ %p= f.label :password_confirmation
+ %p= f.password_field :password_confirmation, autocomplete: "new-password"
diff --git a/app/views/devise/registrations/_form.html.haml b/app/views/devise/registrations/_form.html.haml
new file mode 100644
index 00000000..5092aa38
--- /dev/null
+++ b/app/views/devise/registrations/_form.html.haml
@@ -0,0 +1,57 @@
+
+- http_method = existing ? :put : :post
+= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: http_method }) do |f|
+
+ = render "devise/shared/error_messages", resource: resource
+
+ .container-fluid
+ .row
+ .col-md-6.col-sm-12
+ = f.label :first, 'First Name'
+ = f.text_field :first, maxlength: User::MAX_NAME_LENGTH, required: true
+
+ .col-md-6.col-sm-12
+ = f.label :last, 'Last Name'
+ = f.text_field :last, maxlength: User::MAX_NAME_LENGTH, required: true
+
+ .row
+ .col-md-8.col-sm-11
+ = f.label :email, 'Your Email Address'
+ = f.email_field :email, placeholder: resource.email || resource.unconfirmed_email || 'dj-awesome@gmail.com', maxlength: User::MAX_EMAIL_LENGTH, required: true, autofocus: true, autocomplete: "email"
+ .col-md-4.col-sm-1
+
+ = render partial: 'change_password', locals: { existing: existing, f: f }
+ %br
+ %hr
+ - if existing
+ .row
+ .col-md-6.col-sm-12
+ %h5.nowrap
+ We need your current password to confirm your changes
+ = f.label :current_password
+ = f.password_field :current_password, autocomplete: "current-password"
+
+ .row
+ .col-12
+ .actions
+ = f.submit "Update", class: 'btn btn-primary btn-large w-1'
+
+ - unless existing
+
+ .row
+ .col-12
+ .actions
+ = f.submit "Register", class: 'btn btn-primary btn-large'
+
+ .row
+ .col-12
+ .actions
+ %h3
+ Close my accounts completely.
+ %div
+ Wanna go? #{button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, class: 'btn btn-secondary btn-large', method: :delete}
+
+ .row
+ .col-12
+ = link_to "Go Back ↩".html_safe, :back
+
diff --git a/app/views/devise/registrations/edit.html.haml b/app/views/devise/registrations/edit.html.haml
new file mode 100644
index 00000000..056417cd
--- /dev/null
+++ b/app/views/devise/registrations/edit.html.haml
@@ -0,0 +1,4 @@
+%h2
+ Edit Your Account
+
+= render partial: 'form', locals: { existing: resource&.persisted?, resource: resource, resource_name: resource_name}
diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml
new file mode 100644
index 00000000..02d31e9e
--- /dev/null
+++ b/app/views/devise/registrations/new.html.haml
@@ -0,0 +1,3 @@
+%h2 Register a New Account
+
+= render partial: 'form', locals: { existing: resource&.persisted?, resource: resource, resource_name: resource_name}
diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
new file mode 100644
index 00000000..3449e916
--- /dev/null
+++ b/app/views/devise/sessions/new.html.haml
@@ -0,0 +1,23 @@
+%h2 Log in
+
+= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
+
+ .field
+ %p= f.label :email
+ %p= f.email_field :email, autofocus: true, autocomplete: "email"
+
+ .field
+ %p= f.label :password
+ %p= f.password_field :password, autocomplete: "current-password"
+
+ - if devise_mapping.rememberable?
+ .field
+ %p= f.check_box :remember_me
+ %p= f.label :remember_me
+
+ .actions
+ = f.submit "Log in", class: 'btn btn-primary btn-large'
+
+%hr
+
+= render "devise/shared/links"
diff --git a/app/views/devise/shared/_error_messages.html.haml b/app/views/devise/shared/_error_messages.html.haml
new file mode 100644
index 00000000..c34097ea
--- /dev/null
+++ b/app/views/devise/shared/_error_messages.html.haml
@@ -0,0 +1,10 @@
+- if resource.errors.any?
+ .alert.alert-danger
+ #error_explanation{"data-turbo-cache" => "false"}
+ %h2
+ = I18n.t("errors.messages.not_saved", |
+ count: resource.errors.count, |
+ resource: resource.class.model_name.human.downcase) |
+ %ul
+ - resource.errors.full_messages.each do |message|
+ %li= message
diff --git a/app/views/devise/shared/_links.html.haml b/app/views/devise/shared/_links.html.haml
new file mode 100644
index 00000000..f33911a3
--- /dev/null
+++ b/app/views/devise/shared/_links.html.haml
@@ -0,0 +1,19 @@
+%ul.shared-user-links
+ - if controller_name != 'sessions'
+ %p= link_to "Sign in", new_session_path(resource_name)
+
+ - if devise_mapping.registerable? && controller_name != 'registrations'
+ %p= link_to "Register", new_registration_path(resource_name)
+
+ - if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations'
+ %p= link_to "Forgot your password?", new_password_path(resource_name)
+
+ - if devise_mapping.confirmable? && controller_name != 'confirmations'
+ %p= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name)
+
+ - if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks'
+ %p= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name)
+
+ - if devise_mapping.omniauthable?
+ - resource_class.omniauth_providers.each do |provider|
+ %p= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false }
diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml
new file mode 100644
index 00000000..84c54502
--- /dev/null
+++ b/app/views/devise/unlocks/new.html.haml
@@ -0,0 +1,9 @@
+%h2 Resend unlock instructions
+= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f|
+ = render "devise/shared/error_messages", resource: resource
+ .field
+ %p= f.label :email
+ %p= f.email_field :email, autofocus: true, autocomplete: "email"
+ .actions
+ = f.submit "Resend unlock instructions"
+= render "devise/shared/links"
diff --git a/app/views/eald_payment_mailer/eald_payment_received.html.haml b/app/views/eald_payment_mailer/eald_payment_received.html.haml
index e7891bc9..5bd4ca8f 100644
--- a/app/views/eald_payment_mailer/eald_payment_received.html.haml
+++ b/app/views/eald_payment_mailer/eald_payment_received.html.haml
@@ -23,5 +23,4 @@
%p
More information can be found at:
- = link_to 'Early Arrival / Late Departure Policy',
- 'https://fnf.page.link/eald-policy'
+ = link_to 'Early Arrival / Late Departure Policy', 'https://fnf.page.link/eald-policy'
diff --git a/app/views/events/_form.html.haml b/app/views/events/_form.html.haml
index f2fac50a..368d08cb 100644
--- a/app/views/events/_form.html.haml
+++ b/app/views/events/_form.html.haml
@@ -1,17 +1,16 @@
-= form_for @event, html: { multipart: true } do |f|
+= form_for @event, html: { multipart: true }, data: { controller: "flatpickr" } do |f|
= render 'shared/form_errors', resource: @event
%fieldset
%legend Event Details
= f.label :name, 'Event Name'
- = f.text_field :name, class: 'input-large', required: true,
- maxlength: Event::MAX_NAME_LENGTH
+ = f.text_field :name, class: 'input-large', required: true, maxlength: Event::MAX_NAME_LENGTH
= f.label :start_time, 'Start Time'
- = datetimepicker(:start_time, @event.start_time || 1.day.from_now.beginning_of_day)
+ = render partial: 'shared/datetime_field', locals: {form: f, name: "start_time", datetime: (@event.start_time || 1.day.from_now.beginning_of_day) }
= f.label :end_time, 'End Time'
- = datetimepicker(:end_time, @event.end_time || 1.day.from_now.beginning_of_day)
+ = render partial: 'shared/datetime_field', locals: {form: f, name: "end_time", datetime: (@event.end_time || 1.day.from_now.beginning_of_day)}
%fieldset
%legend Ticket Details
@@ -19,19 +18,19 @@
Ticket Sales Opening Time
%p.muted
Leave blank to begin selling tickets right away
- = datetimepicker(:ticket_sales_start_time, @event.ticket_sales_start_time)
+ = render partial: 'shared/datetime_field', locals: {form: f, name: "ticket_sales_start_time", datetime: (@event.ticket_sales_start_time) }
= f.label :ticket_sales_end_time do
Ticket Sales Closing Time
%p.muted
Leave blank to allow selling tickets up to the end of the event
- = datetimepicker(:ticket_sales_end_time, @event.ticket_sales_end_time)
+ = render partial: 'shared/datetime_field', locals: {form: f, name: "ticket_sales_end_time", datetime: (@event.ticket_sales_end_time) }
= f.label :ticket_requests_end_time do
Ticket Requests Closing Time
%p.muted
Leave blank to allow requesting tickets up to the end of the event
- = datetimepicker(:ticket_requests_end_time, @event.ticket_requests_end_time)
+ = render partial: 'shared/datetime_field', locals: {form: f, name: "ticket_requests_end_time", datetime: (@event.ticket_requests_end_time) }
= f.label :tickets_require_approval, class: 'checkbox' do
= f.check_box :tickets_require_approval
diff --git a/app/views/events/index.html.haml b/app/views/events/index.html.haml
index 34fc2e0b..26777d82 100644
--- a/app/views/events/index.html.haml
+++ b/app/views/events/index.html.haml
@@ -12,8 +12,8 @@
%td
= link_to event do
= event.name
- %td= event.start_time.localtime.to_s(:friendly)
- %td= event.end_time.localtime.to_s(:friendly)
+ %td= event.start_time.localtime.to_formatted_s(:friendly)
+ %td= event.end_time.localtime.to_formatted_s(:friendly)
= link_to new_event_path, class: 'btn btn-primary' do
New Event
diff --git a/app/views/events/show.html.haml b/app/views/events/show.html.haml
index 73813396..7d34e4aa 100644
--- a/app/views/events/show.html.haml
+++ b/app/views/events/show.html.haml
@@ -18,13 +18,13 @@
Edit
%i.icon-edit
-= image_tag image_path('fnf.png'), class: 'event-preview'
+= image_tag image_path('logos/fnf-transparent.png'), class: 'event-preview'
%dl.dl-horizontal
%dt Start Time
- %dd= @event.start_time.localtime.to_s(:friendly)
+ %dd= TimeHelper.for_display(@event.start_time.localtime)
%dt End Time
- %dd= @event.end_time.localtime.to_s(:friendly)
+ %dd= TimeHelper.for_display(@event.end_time.localtime)
%dt Adult Ticket Price
%dd
= number_to_currency(@event.adult_ticket_price)
@@ -57,9 +57,10 @@
%span.help-inline
Share this link so people can create ticket requests
%code
- = new_event_ticket_request_url(@event)
+ = new_event_ticket_request_path(@event)
-.well
+.clear-right
+.container-fluid
%h2 Event Admins
%p
The following individuals have administrative privileges for this event.
diff --git a/app/views/jobs/show.html.haml b/app/views/jobs/show.html.haml
index 9aa2cbaf..e46edddd 100644
--- a/app/views/jobs/show.html.haml
+++ b/app/views/jobs/show.html.haml
@@ -21,7 +21,7 @@
- @time_slots.each do |time_slot|
%tr
%td
- #{time_slot.start_time.localtime.to_s(:dhmm)} – #{time_slot.end_time.localtime.to_s(:hmm)}
+ #{time_slot.start_time.localtime.to_formatted_s(:dhmm)} – #{time_slot.end_time.localtime.to_formatted_s(:hmm)}
%td= time_slot.slots
%td
= link_to edit_event_job_time_slot_path(@event, @job, time_slot), class: 'btn' do
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 8bf2abb9..103d8e0c 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -1,10 +1,11 @@
!!!
-%html{ lang: 'en' }
+%html{ lang: 'en', 'data-bs-theme': 'dark' }
%head
- %meta{ charset: 'utf-8' }
- %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1.0' }
- = csrf_meta_tags
- = yield :meta
+ %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
+ %meta{:content => "width=device-width,initial-scale=1", :name => "viewport"}/
+ %meta{:content => "yes", :name => "apple-mobile-web-app-capable"}/
+ %meta{:content => "#{stripe_publishable_api_key}", :name => "stripe-key"}/
+ %link{:href => "/icons/nav-bar-logo.png", :rel => "shortcut icon", :type => "image/png"}/
%title
= PRODUCT_NAME
@@ -12,44 +13,37 @@
—
= yield(:title)
- /[if lt IE9]
- %script{ src: '//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.6.1/html5shiv.js',
- type: 'text/javascript' }
+ = csrf_meta_tags
+ = csp_meta_tag
+ = yield :heads
+
+ %link{:href => "/manifest.json", :rel => "manifest"}/
- = stylesheet_link_tag 'application', media: 'all'
+ // Stripe
+ %script{:src => "https://js.stripe.com/v3/"}
- = yield :head
+ = stylesheet_link_tag "application", "data-turbo-track": "reload"
+ = javascript_include_tag "application", "data-turbo-track": "reload", type: "module"
%body
- .navbar
- .navbar-inner
- .container
- %a.brand{ href: root_path }
- = PRODUCT_NAME
- %ul.nav.pull-right
- - if user_signed_in?
- %li
- = link_to edit_user_registration_path do
- %i.icon-user
- %span.user-name
- = current_user.first_name
-
- %li
- = link_to destroy_user_session_path, method: :delete do
- %span.sign-out-text Logout
- %i.icon-off
- - else
- %li
- = link_to new_user_session_path do
- Sign in
-
+ = render partial: "shared/navigation"
.container
- = bootstrap_flash
- = yield
+ .row
+ .col-3.col-md-3
+ .col-6.col-md-6
+ .turbo-frame#flash
+ = render 'shared/flash'
+ .col-3.col-md-3
+
+ .row
+ .col-1.col-md-1
+ .col-10.col-md-10
+ = yield
+ .col-1.col-md-1
#footer
.container
%p.muted.text-center#footer-text
- Crafted with ♡
+ %small
+ Crafted with ♡
- = javascript_include_tag 'application'
diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb
new file mode 100644
index 00000000..3aac9002
--- /dev/null
+++ b/app/views/layouts/mailer.html.erb
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+ <%= yield %>
+
+
diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb
new file mode 100644
index 00000000..37f0bddb
--- /dev/null
+++ b/app/views/layouts/mailer.text.erb
@@ -0,0 +1 @@
+<%= yield %>
diff --git a/app/views/pwa/manifest.json.erb b/app/views/pwa/manifest.json.erb
new file mode 100644
index 00000000..7b348353
--- /dev/null
+++ b/app/views/pwa/manifest.json.erb
@@ -0,0 +1,22 @@
+{
+ "name": "TicketBooth",
+ "icons": [
+ {
+ "src": "/icon.png",
+ "type": "image/png",
+ "sizes": "512x512"
+ },
+ {
+ "src": "/icon.png",
+ "type": "image/png",
+ "sizes": "512x512",
+ "purpose": "maskable"
+ }
+ ],
+ "start_url": "/",
+ "display": "standalone",
+ "scope": "/",
+ "description": "TicketBooth.",
+ "theme_color": "red",
+ "background_color": "red"
+}
diff --git a/app/views/pwa/service-worker.js b/app/views/pwa/service-worker.js
new file mode 100644
index 00000000..68d5c2ee
--- /dev/null
+++ b/app/views/pwa/service-worker.js
@@ -0,0 +1,26 @@
+// Add a service worker for processing Web Push notifications:
+//
+// self.addEventListener("push", async (event) => {
+// const { title, options } = await event.data.json()
+// event.waitUntil(self.registration.showNotification(title, options))
+// })
+//
+// self.addEventListener("notificationclick", function(event) {
+// event.notification.close()
+// event.waitUntil(
+// clients.matchAll({ type: "window" }).then((clientList) => {
+// for (let i = 0; i < clientList.length; i++) {
+// let client = clientList[i]
+// let clientPath = (new URL(client.url)).pathname
+//
+// if (clientPath == event.notification.data.path && "focus" in client) {
+// return client.focus()
+// }
+// }
+//
+// if (clients.openWindow) {
+// return clients.openWindow(event.notification.data.path)
+// }
+// })
+// )
+// })
diff --git a/app/views/shared/_datetime_field.html.haml b/app/views/shared/_datetime_field.html.haml
new file mode 100644
index 00000000..3653fd3a
--- /dev/null
+++ b/app/views/shared/_datetime_field.html.haml
@@ -0,0 +1,8 @@
+- formatted_time = TimeHelper.to_string_for_flatpickr(datetime&.to_datetime)
+.flatpickr
+ = form.text_field name, value: formatted_time, "data-input": nil, placeholder: "Select Date..", class: 'flatpickr-date-time'
+ %a.input-button{"data-toggle" => "", :title => "toggle"}
+ %i.icon-calendar
+ %a.input-button{"data-clear" => "", :title => "clear"}
+ %i.icon-close
+
diff --git a/app/views/shared/_flash.html.haml b/app/views/shared/_flash.html.haml
new file mode 100644
index 00000000..8f0b888b
--- /dev/null
+++ b/app/views/shared/_flash.html.haml
@@ -0,0 +1,11 @@
+- flash.each do |type, msg|
+ .alert{ class: alert_class(type), role: "alert"}
+ - main, rest = msg.split(/:/)
+ %h3
+ = main
+ - if rest != main && rest.present?
+ %hr
+ - rest.gsub(/.*:/, '').gsub(/["\[\]]/, '').split(', ').each do |message|
+ %ul
+ %li
+ = message
diff --git a/app/views/shared/_navigation.html.haml b/app/views/shared/_navigation.html.haml
new file mode 100644
index 00000000..a6c59b03
--- /dev/null
+++ b/app/views/shared/_navigation.html.haml
@@ -0,0 +1,31 @@
+%nav.navbar.navbar-expand-lg.border-bottom.border-body.navbar-dark
+ %a.navbar-brand{ href: root_path }
+ = image_tag "/icons/nav-bar-logo.png", size: "80x80"
+ %button.navbar-toggler{"aria-controls" => "navbarSupportedContent", "aria-expanded" => "false", "aria-label" => "Toggle navigation", "data-bs-target" => "#navbarSupportedContent", "data-bs-toggle" => "collapse", :type => "button"}
+ %span.navbar-toggler-icon
+
+ #navbarSupportedContent.collapse.navbar-collapse
+ %ul.navbar-nav.me-auto.mb-2.mb-lg-0
+ - if user_signed_in?
+ %li.nav-item
+ %a.nav-link{ href: events_path }
+ Events
+
+ %li.nav-item
+ %a.nav-link{ href: edit_user_registration_path }
+ Your Profile
+
+ %li.nav-item
+ = link_to destroy_user_session_path, data: { "turbo-method": :delete }, class: 'nav-link' do
+ Logout
+ - else
+ %li.nav-item
+ %a.nav-link{ href: new_user_session_path }
+ Login
+
+ - if User.has_devise_module?(:registerable) && controller_name != 'registrations'
+ %li.nav-item
+ %a.nav-link{ href: new_user_registration_path }
+ Register
+
+
diff --git a/app/views/shared/_pay.html.haml b/app/views/shared/_pay.html.haml
new file mode 100644
index 00000000..00eb14d3
--- /dev/null
+++ b/app/views/shared/_pay.html.haml
@@ -0,0 +1,7 @@
+%form#payment-form{"data-controller" => "checkout", "data-stripe-client-secret" => "#{@payment_intent_client_secret}", "data-stripe-publishable-key" => "#{stripe_publishable_api_key}"}
+ #payment-message.hidden
+ #payment-element
+ %button.submit.btn.btn-success.mt-3s
+ #spinner.spinner.hidden
+ %span.button-text
+ Pay now
diff --git a/app/views/shifts/index.html.haml b/app/views/shifts/index.html.haml
index 6284db05..f8328c3e 100644
--- a/app/views/shifts/index.html.haml
+++ b/app/views/shifts/index.html.haml
@@ -30,16 +30,16 @@
- schedule_matrix.each do |cells|
%tr
%td.time-column
- = cur_time.localtime.to_s(:dhmm)
+ = cur_time.localtime.to_formatted_s(:dhmm)
- cells.each do |cell|
%td{ rowspan: cell[:rowspan], class: ('nothing' unless cell[:time_slot]) }
- time_slot = cell[:time_slot]
- if time_slot
%p.text-center
%span.muted
- = time_slot.start_time.localtime.to_s(:dhmm)
+ = time_slot.start_time.localtime.to_formatted_s(:dhmm)
—
- = time_slot.end_time.localtime.to_s(:hmm)
+ = time_slot.end_time.localtime.to_formatted_s(:hmm)
%br
%small= time_slot.job.name
%br
diff --git a/app/views/ticket_request_mailer/request_approved.html.haml b/app/views/ticket_request_mailer/request_approved.html.haml
index ecf7ac20..9a7aa2b7 100644
--- a/app/views/ticket_request_mailer/request_approved.html.haml
+++ b/app/views/ticket_request_mailer/request_approved.html.haml
@@ -11,9 +11,7 @@
- else
You can now
= succeed '.' do
- = link_to new_payment_url(ticket_request_id: @ticket_request,
- user_id: @ticket_request.user_id,
- user_token: @auth_token) do
+ = link_to @payment_url do
purchase your
= 'ticket'.pluralize(@ticket_request.total_tickets)
@@ -21,14 +19,7 @@
%p
%b Planning to arrive early or leave late?
%br
- :ruby
- extra_params = {}
- extra_params[:early_arrival_passes] = @ticket_request.early_arrival_passes
- extra_params[:late_departure_passes] = @ticket_request.late_departure_passes
- extra_params[:email] = @ticket_request.user.email
- extra_params[:name] = @ticket_request.user.name
- = link_to 'Early Arrival / Late Departure Passes',
- new_event_eald_payment_url(@event, extra_params)
+ = link_to 'Early Arrival / Late Departure Passes', @eald_url
must be purchased separately!
%p
diff --git a/app/views/ticket_request_mailer/request_received.html.haml b/app/views/ticket_request_mailer/request_received.html.haml
index 31f7dd10..b90296af 100644
--- a/app/views/ticket_request_mailer/request_received.html.haml
+++ b/app/views/ticket_request_mailer/request_received.html.haml
@@ -4,7 +4,7 @@
%p
We have received your ticket request for #{@event.name}.
--# XXX: This is temporary until tickets stop selling
+-# TODO: This is temporary until tickets stop selling
-#- if @ticket_request.role == 'volunteer'
-# %p
-# At this point, all available tickets have been requested. However,
@@ -28,5 +28,4 @@
P.S. You can see the status of your request at any time by
visiting
= succeed '.' do
- = link_to 'this page',
- event_ticket_request_url(@event, @ticket_request)
+ = link_to 'this page', @ticket_request_url
diff --git a/app/views/ticket_requests/_form.html.haml b/app/views/ticket_requests/_form.html.haml
index 9fb6da88..c6394d01 100644
--- a/app/views/ticket_requests/_form.html.haml
+++ b/app/views/ticket_requests/_form.html.haml
@@ -1,7 +1,7 @@
- form_action_name = action_name == 'edit' ? :update : :create
- is_update = form_action_name == :update
-= form_for @ticket_request,
- url: { controller: :ticket_requests, action: form_action_name } do |f|
+
+= form_for @ticket_request, url: { controller: :ticket_requests, action: form_action_name } do |f|
= render 'shared/form_errors', resource: @ticket_request
= f.hidden_field :event_id, value: @event.id
@@ -18,7 +18,7 @@
= ff.label :email, class: 'required-label' do
Email (username)
- = ff.email_field :email, class: 'input-xlarge',
+ = ff.email_field :email,
maxlength: User::MAX_EMAIL_LENGTH, required: true,
placeholder: 'email@example.com',
data: { lookup_url: emails_path }
@@ -34,196 +34,206 @@
= link_to 'password reset email', new_user_password_path,
data: { reset_url: reset_passwords_path },
id: 'password-reset-link'
+
.alert.alert-block.hidden#email-reset-sent
%h4 Password reset email sent!
%p
Check your email!
= ff.label :name, 'Full Name', class: 'required-label'
- = ff.text_field :name, class: 'input-xlarge',
+ = ff.text_field :name,
maxlength: User::MAX_NAME_LENGTH, required: true,
placeholder: 'First and last name'
= ff.label :password, 'Create Password', class: 'required-label'
= ff.password_field :password,
required: true,
- maxlength: User::MAX_PASSWORD_LENGTH, class: 'input-xlarge'
+ maxlength: User::MAX_PASSWORD_LENGTH
- %fieldset
- %p
- = f.label :role_volunteer do
- How are you contributing to the campout this year?
- = f.label :role_volunteer, class: 'radio inline' do
- = f.radio_button :role, TicketRequest::ROLE_VOLUNTEER,
- data: { 'max-tickets' => TicketRequest::TICKET_LIMITS[TicketRequest::ROLE_VOLUNTEER] }
- = TicketRequest::ROLES[TicketRequest::ROLE_VOLUNTEER]
- = f.label :role_contributor, class: 'radio inline' do
- = f.radio_button :role, TicketRequest::ROLE_CONTRIBUTOR,
- data: { 'max-tickets' => TicketRequest::TICKET_LIMITS[TicketRequest::ROLE_CONTRIBUTOR] }
- = TicketRequest::ROLES[TicketRequest::ROLE_CONTRIBUTOR]
- = f.label :role_coordinator, class: 'radio inline' do
- = f.radio_button :role, TicketRequest::ROLE_COORDINATOR,
- data: { 'max-tickets' => TicketRequest::TICKET_LIMITS[TicketRequest::ROLE_COORDINATOR] }
- = TicketRequest::ROLES[TicketRequest::ROLE_COORDINATOR]
- = f.label :role_uber_coordinator, class: 'radio inline' do
- = f.radio_button :role, TicketRequest::ROLE_UBER_COORDINATOR,
- data: { 'max-tickets' => TicketRequest::TICKET_LIMITS[TicketRequest::ROLE_UBER_COORDINATOR] }
- = TicketRequest::ROLES[TicketRequest::ROLE_UBER_COORDINATOR]
- = f.label :role_other, class: 'radio inline' do
- = f.radio_button :role, TicketRequest::ROLE_OTHER,
- data: { 'max-tickets' => TicketRequest::TICKET_LIMITS[TicketRequest::ROLE_OTHER] }
- = TicketRequest::ROLES[TicketRequest::ROLE_OTHER]
- %p.muted.role-explanation{ class: TicketRequest::ROLE_VOLUNTEER }
- You are working one or more shifts at the event
- %p.muted.role-explanation.hidden{ class: TicketRequest::ROLE_CONTRIBUTOR }
- Working before and during or during and after; actively involved in
- planning the campout e.g. a coordinator assistant or apprentice. Bringing
- gear, driving trucks, art committee, etc.
- %p.muted.role-explanation.hidden{ class: TicketRequest::ROLE_COORDINATOR }
- Listed on the Coordinator Sheet as someone who is leading a major area of
- campout planning; working before, during, and after the event including
- things like resource coordination, significant gear prep, pre/post-site
- visits, etc.
- %p.muted.role-explanation.hidden{ class: TicketRequest::ROLE_OTHER }
- Art Grantee or DJ
- = f.text_area :role_explanation, class: 'input-xlarge hidden',
- placeholder: 'Briefly describe your role (required)',
- rows: 2, maxlength: 200, required: false
-
- = f.label :previous_contribution, 'What was your role last year?'
- = f.text_area :previous_contribution, class: 'input-xlarge',
- rows: 2, maxlength: 250
-
- - if signed_in? && @event.admin?(current_user) && form_action_name == :update
- .well
- = f.label :special_price, 'Special Total Price ($)'
- %p.text-error
- Editable by event admins only. Leave blank to use default price.
- = f.number_field :special_price, class: 'input-small', min: 0
-
- = f.label :adults, 'Number of tickets'
- = f.number_field :adults, class: 'input-mini', min: 1, required: true,
- max: (TicketRequest::TICKET_LIMITS[TicketRequest::ROLE_VOLUNTEER] unless form_action_name == :update),
- data: { default_price: @event.adult_ticket_price.to_i }
- %span.help-inline.inline-price
-
- - if @event.kid_ticket_price
- = f.label :kids do
- Number of children you are bringing with you (not transferable to adults; babes in arms are free)
- = help_mark help_text_for(:kids)
- = f.number_field :kids, class: 'input-mini', min: 0,
- max: @event.max_kid_tickets_per_request,
- data: { default_price: @event.kid_ticket_price.to_i,
- custom_prices: price_rules_to_json(@event) }
- %span.help-inline.inline-price
- %p.help-inline-block.muted
- Please include the ages of the children (including infants) you are
- bringing with you in the notes below.
-
- - if @event.cabin_price
- = f.label :cabins do
- How many cabins did you want?
- = help_mark help_text_for(:cabins)
- = f.number_field :cabins, class: 'input-mini', min: 0,
- max: @event.max_cabins_per_request,
- data: { default_price: @event.cabin_price.to_i },
- disabled: !@event.cabins_available?
- %span.help-inline.inline-price
- - unless @event.cabins_available?
- Sorry, we are sold out of cabins at this point in time.
-
- .control-group
-
- - if @event.allow_financial_assistance
- = f.label :needs_assistance, class: 'checkbox' do
- = f.check_box :needs_assistance
- I’m requesting financial assistance with purchasing my ticket(s)
- = help_mark help_text_for(:needs_assistance)
+ .input-group-large
+ %fieldset
+ %p
+ = f.label :role_volunteer do
+ How are you contributing to the campout this year?
+ = f.label :role_volunteer, class: 'radio inline' do
+ = f.radio_button :role, TicketRequest::ROLE_VOLUNTEER,
+ data: { 'max-tickets' => TicketRequest::TICKET_LIMITS[TicketRequest::ROLE_VOLUNTEER] }
+ = TicketRequest::ROLES[TicketRequest::ROLE_VOLUNTEER]
+ = f.label :role_contributor, class: 'radio inline' do
+ = f.radio_button :role, TicketRequest::ROLE_CONTRIBUTOR,
+ data: { 'max-tickets' => TicketRequest::TICKET_LIMITS[TicketRequest::ROLE_CONTRIBUTOR] }
+ = TicketRequest::ROLES[TicketRequest::ROLE_CONTRIBUTOR]
+ = f.label :role_coordinator, class: 'radio inline' do
+ = f.radio_button :role, TicketRequest::ROLE_COORDINATOR,
+ data: { 'max-tickets' => TicketRequest::TICKET_LIMITS[TicketRequest::ROLE_COORDINATOR] }
+ = TicketRequest::ROLES[TicketRequest::ROLE_COORDINATOR]
+ = f.label :role_uber_coordinator, class: 'radio inline' do
+ = f.radio_button :role, TicketRequest::ROLE_UBER_COORDINATOR,
+ data: { 'max-tickets' => TicketRequest::TICKET_LIMITS[TicketRequest::ROLE_UBER_COORDINATOR] }
+ = TicketRequest::ROLES[TicketRequest::ROLE_UBER_COORDINATOR]
+ = f.label :role_other, class: 'radio inline' do
+ = f.radio_button :role, TicketRequest::ROLE_OTHER,
+ data: { 'max-tickets' => TicketRequest::TICKET_LIMITS[TicketRequest::ROLE_OTHER] }
+ = TicketRequest::ROLES[TicketRequest::ROLE_OTHER]
+
+ %p.muted.role-explanation{ class: TicketRequest::ROLE_VOLUNTEER }
+ You are working one or more shifts at the event
+
+ %p.muted.role-explanation.hidden{ class: TicketRequest::ROLE_CONTRIBUTOR }
+ Working before and during or during and after; actively involved in
+ planning the campout e.g. a coordinator assistant or apprentice. Bringing
+ gear, driving trucks, art committee, etc.
+
+ %p.muted.role-explanation.hidden{ class: TicketRequest::ROLE_COORDINATOR }
+ Listed on the Coordinator Sheet as someone who is leading a major area of
+ camp out planning; working before, during, and after the event including
+ things like resource coordination, significant gear prep, pre/post-site
+ visits, etc.
+
+ %p.muted.role-explanation.hidden{ class: TicketRequest::ROLE_OTHER }
+ Art Grantee or DJ
+
+ = f.text_area :role_explanation, class: 'input-xlarge hidden',
+ placeholder: 'Briefly describe your role (required)',
+ rows: 2, maxlength: 200, required: false
+
+ = f.label :previous_contribution, 'What was your role last year?'
+ = f.text_area :previous_contribution,
+ rows: 2, maxlength: 250
+
+ - if signed_in? && @event.admin?(current_user) && form_action_name == :update
+ .well
+ = f.label :special_price, 'Special Total Price ($)'
+
+ %p.text-error
+ Editable by event admins only. Leave blank to use default price.
+ = f.number_field :special_price, class: 'input-small', min: 0
+
+ = f.label :adults, 'Number of tickets'
+
+ = f.number_field :adults, class: 'input-mini', min: 1, required: true,
+ max: (TicketRequest::TICKET_LIMITS[TicketRequest::ROLE_VOLUNTEER] unless form_action_name == :update),
+ data: { default_price: @event.adult_ticket_price.to_i }
- .control-group
+ %span.help-inline.inline-price
- - if @event.require_mailing_address
- %fieldset
- = f.label :address_line1, class: 'required-label' do
- Address
- = help_mark help_text_for(:address)
- = f.text_field :address_line1, required: true, class: 'input-xlarge',
- placeholder: 'Street address, P.O. box, etc.',
- maxlength: 200
+ - if @event.kid_ticket_price
+
+ = f.label :kids do
+ Number of children you are bringing with you (not transferable to adults; babes in arms are free)
+ = help_mark help_text_for(:kids)
+
+ = f.number_field :kids, class: 'input-mini', min: 0,
+ max: @event.max_kid_tickets_per_request,
+ data: { default_price: @event.kid_ticket_price.to_i,
+ custom_prices: price_rules_to_json(@event) }
+
+ %span.help-inline.inline-price
+ %p.help-inline-block.muted
+ Please include the ages of the children (including infants) you are
+ bringing with you in the notes below.
+
+ - if @event.cabin_price
+
+ = f.label :cabins do
+ How many cabins did you want?
+ = help_mark help_text_for(:cabins)
+
+ = f.number_field :cabins, class: 'input-mini', min: 0,
+ max: @event.max_cabins_per_request,
+ data: { default_price: @event.cabin_price.to_i },
+ disabled: !@event.cabins_available?
+
+ %span.help-inline.inline-price
+ - unless @event.cabins_available?
+ Sorry, we are sold out of cabins at this point in time.
+
+ .control-group
+
+ - if @event.allow_financial_assistance
+ = f.label :needs_assistance, class: 'checkbox' do
+ = f.check_box :needs_assistance
+ I’m requesting financial assistance with purchasing my ticket(s)
+ = help_mark help_text_for(:needs_assistance)
+
+ .control-group
+
+ - if @event.require_mailing_address
+ %fieldset
+ = f.label :address_line1, class: 'required-label' do
+ Address
+ = help_mark help_text_for(:address)
+ = f.text_field :address_line1, required: true, placeholder: 'Street address, P.O. box, etc.', maxlength: 200
+ = f.text_field :address_line2, placeholder: 'Apartment, suite, unit, building, floor, etc.', maxlength: 200
+ = f.label :city, 'City', class: 'required-label'
+ = f.text_field :city, required: true, maxlength: 50
+ = f.label :state, 'State/Province/Region', class: 'required-label'
+ = f.text_field :state, required: true, maxlength: 50
+ = f.label :zip_code, 'Zip/Postal Code', class: 'required-label'
+ = f.text_field :zip_code, required: true, maxlength: 32
+ = f.label :country_code, 'Country', class: 'required-label'
+ = f.country_select "country_code", iso_codes: true
+
+ = f.label :notes do
+ Who did you hear about this event from?
+ Any special requests or things you want to mention?
+
+ = f.text_area :notes,
+ rows: 10, columns: 20, maxlength: 500
+
+ - if signed_in? && @event.admin?(current_user) && form_action_name == :update
+ .well
+ = f.label :admin_notes do
+ Additional Notes
+ %p.text-error
+ Visible by event admins only.
+ = f.text_area :admin_notes, rows: 5, maxlength: 512
+
+ %hr
+
+ - unless is_update
+ %p
+ Every year, every attendee, no matter how long they've been coming, must
+ acquaint themselves with our values and Code of Conduct. Please acknowledge
+ you've done so this year.
+ = f.label :agrees_to_terms, class: 'checkbox' do
+ = f.check_box :agrees_to_terms
+ I’ve read
+ = link_to 'The FnF Way', 'http://cfaea.net/the-fnf-way/', target: '_blank'
+ and
+ = link_to 'Code of Conduct', 'https://fnf.page.link/coc', target: '_blank'
+ and agree to follow the principles outlined therein
%br
- = f.text_field :address_line2, class: 'input-xlarge',
- placeholder: 'Apartment, suite, unit, building, floor, etc.',
- maxlength: 200
- = f.label :city, 'City', class: 'required-label'
- = f.text_field :city, required: true, maxlength: 50, class: 'input-xlarge'
- = f.label :state, 'State/Province/Region', class: 'required-label'
- = f.text_field :state, required: true, class: 'input-xlarge',
- maxlength: 50
- = f.label :zip_code, 'Zip/Postal Code', class: 'required-label'
- = f.text_field :zip_code, required: true, maxlength: 32, class: 'input-small'
- = f.label :country_code, 'Country', class: 'required-label'
- = f.country_select :country_code, %w[US CA GB], iso_codes: true
-
- = f.label :notes do
- Who did you hear about this event from?
- Any special requests or things you want to mention?
- = f.text_area :notes, class: 'input-xxlarge',
- rows: 3, maxlength: 500
-
- - if signed_in? && @event.admin?(current_user) && form_action_name == :update
- .well
- = f.label :admin_notes do
- Additional Notes
- %p.text-error
- Visible by event admins only.
- = f.text_area :admin_notes, class: 'input-xxlarge', rows: 5, maxlength: 512
-
- %hr
-
- - unless is_update
+
+ - if is_update
+ .control-group
+ %h4 Guests
%p
- Every year, every attendee, no matter how long they've been coming, must
- acquaint themselves with our values and Code of Conduct. Please acknowledge
- you've done so this year.
- = f.label :agrees_to_terms, class: 'checkbox' do
- = f.check_box :agrees_to_terms
- I’ve read
- = link_to 'The FnF Way', 'http://cfaea.net/the-fnf-way/', target: '_blank'
- and
- = link_to 'Code of Conduct',
- 'https://fnf.page.link/coc',
- target: '_blank'
- and agree to follow the principles outlined therein
+ Include yourself as a guest. Use First Name and Last Name.
+ DJ names or monikers should be accompanied with their real name.
+ If we cannot identify the name, we will ask you to change it.
+ %b Examples of good names
+ %ol
+ %li Tim Bergling
+ %li Tim Bergling (Avicii)
+ %b Examples of bad names
+ %ol
+ %li Tim
+ %li Bergling
+ %li T. B.
%br
+ - list_finalized = (@event.start_time - Time.now) < 2.days
+ - if list_finalized
+ Guest list has been finalized. If you need to update your guests, please email
+ = mail_to 'tickets@fnf.org'
+ %br
+ - @ticket_request.guest_count.times do |guest_id|
+ = f.text_field :guests, readonly: list_finalized, multiple: true, value: Array(@ticket_request.guests)[guest_id]
+ %br
- - if is_update
- .control-group
- %h4 Guests
- %p
- Include yourself as a guest. Use First Name and Last Name.
- DJ names or monikers should be accompanied with their real name.
- If we cannot identify the name, we will ask you to change it.
- %b Examples of good names
- %ol
- %li Tim Bergling
- %li Tim Bergling (Avicii)
- %b Examples of bad names
- %ol
- %li Tim
- %li Bergling
- %li T. B.
- %br
- - list_finalized = (@event.start_time - Time.now) < 2.days
- - if list_finalized
- Guest list has been finalized. If you need to update your guests, please email
- = mail_to 'tickets@fnf.org'
- %br
- - @ticket_request.guest_count.times do |guest_id|
- = f.text_field :guests, readonly: list_finalized, multiple: true, value: Array(@ticket_request.guests)[guest_id]
- %br
+ .actions
+ - submit_btn_text = is_update ? 'Update Request' : 'Submit Request'
+ = f.submit submit_btn_text, class: 'btn btn-primary btn-large', disabled: !is_update,
+ id: 'submit-request',
+ data: { disable_with: 'Submitting...' }
- .actions
- - submit_btn_text = is_update ? 'Update Request' : 'Submit Request'
- = f.submit submit_btn_text, class: 'btn btn-primary btn-large', disabled: !is_update,
- id: 'submit-request',
- data: { disable_with: 'Submitting...' }
diff --git a/app/views/ticket_requests/index.html.haml b/app/views/ticket_requests/index.html.haml
index d0c52f70..ef117a98 100644
--- a/app/views/ticket_requests/index.html.haml
+++ b/app/views/ticket_requests/index.html.haml
@@ -131,7 +131,7 @@
%span{ class: ('label label-info' if ticket_request.special_price) }
= number_to_currency(ticket_request.price, precision: 0)
%td
- = ticket_request.created_at.localtime.to_s(:month_day)
+ = ticket_request.created_at.localtime.to_formatted_s(:month_day)
%td
%span{ class: text_class_for_status(ticket_request) }
= text_for_status ticket_request
diff --git a/app/views/ticket_requests/new.html.haml b/app/views/ticket_requests/new.html.haml
index 1264b6f4..54cb162c 100644
--- a/app/views/ticket_requests/new.html.haml
+++ b/app/views/ticket_requests/new.html.haml
@@ -1,14 +1,14 @@
- if @event.ticket_requests_open?
%h1 Request Tickets for #{@event.name}
- = image_tag('fnf.png', class: 'pull-right', width: 100)
+ = image_tag('logos/fnf-transparent.png', class: 'pull-right', width: 100)
%p.lead
%i.icon-calendar
- = @event.start_time.localtime.to_s(:month_day)
+ = @event.start_time.localtime.to_formatted_s(:month_day)
–
= succeed ',' do
- = @event.end_time.localtime.to_s(:month_day)
+ = @event.end_time.localtime.to_formatted_s(:month_day)
= @event.end_time.year
%p
diff --git a/app/views/ticket_requests/show.html.haml b/app/views/ticket_requests/show.html.haml
index cceb9b75..9a039b10 100644
--- a/app/views/ticket_requests/show.html.haml
+++ b/app/views/ticket_requests/show.html.haml
@@ -22,7 +22,7 @@
%p
%b Date Requested:
- = @ticket_request.created_at.localtime.to_s(:friendly)
+ = @ticket_request.created_at.localtime.to_formatted_s(:friendly)
%p
%b Status:
diff --git a/app/views/users/confirmations/new.html.haml b/app/views/users/confirmations/new.html.haml
deleted file mode 100644
index e0145c60..00000000
--- a/app/views/users/confirmations/new.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-= form_for resource, as: resource_name, url: confirmation_path(resource_name),
- html: { method: :post } do |f|
- = render 'shared/form_errors', resource: resource
-
- %h2 Didn't receive a confirmation email?
-
- %p.lead
- Fill out the form below with the
- email you used to register an account and we'll resend the confirmation email.
-
- = f.label :email
- = f.email_field :email, autofocus: true
-
- .actions
- = f.submit 'Resend email', class: 'btn btn-primary btn-large'
-
-= render 'users/shared/links'
diff --git a/app/views/users/mailer/confirmation_instructions.html.haml b/app/views/users/mailer/confirmation_instructions.html.haml
deleted file mode 100644
index 1d076074..00000000
--- a/app/views/users/mailer/confirmation_instructions.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-%p
- Hi #{@resource.first_name}!
-
-%p
- Thanks for registering an account. You can confirm your email through the following link:
-
-%p
- = link_to 'Confirm my account',
- confirmation_url(@resource, confirmation_token: @resource.confirmation_token)
diff --git a/app/views/users/mailer/reset_password_instructions.html.haml b/app/views/users/mailer/reset_password_instructions.html.haml
deleted file mode 100644
index 68411989..00000000
--- a/app/views/users/mailer/reset_password_instructions.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-%p
- Hi #{@resource.first_name},
-
-%p
- Someone (hopefully you) has requested a link to change your password.
- You can reset your password by following the link below:
-
-%p
- = link_to 'Change my password',
- edit_password_url(@resource, reset_password_token: @token)
-
-%p
- If you didn't request this, please ignore this email.
- Your password won't change until you access the link above and create a new one.
diff --git a/app/views/users/mailer/unlock_instructions.html.haml b/app/views/users/mailer/unlock_instructions.html.haml
deleted file mode 100644
index 6976c4c9..00000000
--- a/app/views/users/mailer/unlock_instructions.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%p Hello #{@resource.first_name}!
-
-%p
- Your account has been locked due to an excessive number of unsuccessful attempts to sign in.
-
-%p
- You can follows the link below to unlock your account:
-
-%p= link_to 'Unlock my account',
- unlock_url(@resource, unlock_token: @resource.unlock_token)
diff --git a/app/views/users/passwords/edit.html.haml b/app/views/users/passwords/edit.html.haml
deleted file mode 100644
index 47ae4852..00000000
--- a/app/views/users/passwords/edit.html.haml
+++ /dev/null
@@ -1,15 +0,0 @@
-= form_for resource, as: resource_name, url: password_path(resource_name),
- html: { method: :put } do |f|
- = render 'shared/form_errors', resource: resource
-
- %h2 Change your Password
-
- = f.hidden_field :reset_password_token
-
- = f.label :password, 'New password'
- = f.password_field :password, autofocus: true
-
- .actions
- = f.submit 'Change my password', class: 'btn btn-primary btn-large'
-
-= render 'users/shared/links'
diff --git a/app/views/users/passwords/new.html.haml b/app/views/users/passwords/new.html.haml
deleted file mode 100644
index d2cc947d..00000000
--- a/app/views/users/passwords/new.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-= form_for resource, as: resource_name, url: password_path(resource_name),
- html: { method: :post } do |f|
- = render 'shared/form_errors', resource: resource
-
- %h2 Forgot your password?
-
- %p.lead
- No problem. Enter your email and we'll send you password reset instructions.
-
- = f.label :email
- = f.email_field :email, autofocus: true, placeholder: 'email@example.com'
-
- .actions
- = f.submit 'Send me instructions', class: 'btn btn-primary btn-large'
-
-= render 'users/shared/links'
diff --git a/app/views/users/registrations/edit.html.haml b/app/views/users/registrations/edit.html.haml
deleted file mode 100644
index ff32edd7..00000000
--- a/app/views/users/registrations/edit.html.haml
+++ /dev/null
@@ -1,36 +0,0 @@
-= form_for resource, as: resource_name, url: registration_path(resource_name),
- html: { method: :put } do |f|
- = render 'shared/form_errors', resource: resource
-
- %h2 Account Settings
-
- = f.label :name
- = f.text_field :name, maxlength: User::MAX_NAME_LENGTH, required: true,
- class: 'input-xlarge'
-
- = f.label :email
- = f.email_field :email, placeholder: 'email@example.com',
- maxlength: User::MAX_EMAIL_LENGTH, required: true, autofocus: true,
- class: 'input-xlarge'
-
- - if devise_mapping.confirmable? && resource.pending_reconfirmation?
- Currently waiting confirmation for:
- %strong= resource.unconfirmed_email
-
- = f.label :password do
- Password
- %p.muted Leave blank to keep the same
- = f.password_field :password,
- required: true, autocomplete: 'off',
- maxlength: User::MAX_PASSWORD_LENGTH, class: 'input-xlarge'
-
- = f.label :current_password do
- Current password
- %p.muted Enter your current password to confirm your changes
- = f.password_field :current_password, maxlength: User::MAX_PASSWORD_LENGTH,
- required: true, class: 'input-xlarge'
-
- .actions
- = f.submit 'Update', class: 'btn btn-primary btn-large'
-
-= link_to 'Back', :back
diff --git a/app/views/users/registrations/new.html.haml b/app/views/users/registrations/new.html.haml
deleted file mode 100644
index e106059e..00000000
--- a/app/views/users/registrations/new.html.haml
+++ /dev/null
@@ -1,28 +0,0 @@
-= form_for resource, as: resource_name, url: registration_path(resource_name) do |f|
- = render 'shared/form_errors', resource: resource
-
- %h2 Register an account
-
- %p.lead
- Having an account allows you to request tickets and receive status
- updates on your requests.
-
- = f.label :name, 'Full Name'
- = f.text_field :name, autofocus: true, placeholder: 'First and last name',
- maxlength: User::MAX_NAME_LENGTH, required: true,
- class: 'input-xlarge'
-
- = f.label :email
- = f.email_field :email, placeholder: 'email@example.com',
- maxlength: User::MAX_EMAIL_LENGTH, required: true,
- class: 'input-xlarge'
-
- = f.label :password
- = f.password_field :password, placeholder: 'Must be at least 8 characters',
- required: true,
- maxlength: User::MAX_PASSWORD_LENGTH, class: 'input-xlarge'
-
- .actions
- = f.submit 'Register', class: 'btn btn-primary btn-large'
-
-= render 'users/shared/links'
diff --git a/app/views/users/sessions/new.html.haml b/app/views/users/sessions/new.html.haml
deleted file mode 100644
index 03e46a07..00000000
--- a/app/views/users/sessions/new.html.haml
+++ /dev/null
@@ -1,18 +0,0 @@
-= form_for resource, as: resource_name, url: session_path(resource_name) do |f|
- %h2 Sign In
-
- = f.label :email
- = f.email_field :email, autofocus: true, placeholder: 'email@example.com'
-
- = f.label :password
- = f.password_field :password
-
- - if devise_mapping.rememberable?
- = f.label :remember_me, class: 'checkbox' do
- = f.check_box :remember_me
- Remember me
-
- .actions
- = f.submit 'Sign in', class: 'btn btn-primary btn-large'
-
-= render 'users/shared/links'
diff --git a/app/views/users/shared/_links.html.haml b/app/views/users/shared/_links.html.haml
deleted file mode 100644
index 671b0352..00000000
--- a/app/views/users/shared/_links.html.haml
+++ /dev/null
@@ -1,15 +0,0 @@
-%ul.shared-user-links
- - if controller_name != 'sessions'
- %li= link_to 'Sign in', new_session_path(resource_name)
-
- - if devise_mapping.registerable? && controller_name != 'registrations'
- %li= link_to 'Register', new_registration_path(resource_name)
-
- - if devise_mapping.recoverable? && controller_name != 'passwords'
- %li= link_to 'Forgot your password?', new_password_path(resource_name)
-
- - if devise_mapping.confirmable? && controller_name != 'confirmations'
- %li= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name)
-
- - if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks'
- %li= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name)
diff --git a/app/views/users/unlocks/new.html.haml b/app/views/users/unlocks/new.html.haml
deleted file mode 100644
index eb0a45eb..00000000
--- a/app/views/users/unlocks/new.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-= form_for resource, as: resource_name, url: unlock_path(resource_name),
- html: { method: :post } do |f|
- = render 'shared/form_errors', resource: resource
-
- %h2 Were you told that your account was locked?
-
- %p.lead
- Fill out the form below and we'll resend you instructions for unlocking your account.
-
- = f.label :email
- = f.email_field :email, autofocus: true, placeholder: 'email@example.com'
-
- .actions
- = f.submit 'Resend email', class: 'btn btn-primary btn-large'
-
-= render 'users/shared/links'
diff --git a/bin/boot-up b/bin/boot-up
index f4df534f..a543f836 100755
--- a/bin/boot-up
+++ b/bin/boot-up
@@ -16,226 +16,49 @@ export BASHMATIC_HOME="${BASHMATIC_HOME:-"${HOME}/.bashmatic"}"
source "${BASHMATIC_HOME}/init.sh" >/dev/null 2>&1
output.constrain-screen-width 100
-export default_bundler_version="1.17.3"
-export python_version="2.7.18"
-export python_major_version="$(echo "${python_version}" | cut -d '.' -f 1)"
+h1 "Installing Brew Packages..."
+run.set-all continue-on-error
+run "brew bundle --no-upgrade --no-lock"
+run.set-all abort-on-error
-export ruby_version="$(cat .ruby-version | tr -d '\n')"
+run "brew services start postgresql@16"
+run "brew services start memcached"
-function ensure-postgresql() {
- [[ -n "$( ps -ef | grep "[p]ostg" )" ]] && pg_isready >/dev/null && {
- success "PostgreSQL is already running."
- return 0
- }
+h1 "Installing Dependencies"
+rbenv install -s "$(cat .ruby-version)"
- (command -v brew >/dev/null) && {
- run.set-all continue-on-error
+eval "$(rbenv init -)"
- h1 "Installing and starting PostgreSQL using Homebrew..."
+run "curl https://get.volta.sh | bash"
+run "volta install node@lts"
+run "volta install yarn@4.1.1"
+run "volta pin node"
+run "volta pin yarn"
- run "brew install postgresql@14"
- run "brew link postgresql@14"
- run "brew services start postgresql@14"
-
- sleep 3
-
- pg_isready && success "Database is UP."
- pg_isready || {
- error "Unable to start PostgreSQL. Please install it manually" \
- "from: https://www.postgresql.org/download/"
- open "https://www.postgresql.org/download/"
- exit 1
- }
-
- run "createuser -s postgres -U ${USER}"
- run "createuser -s root -U ${USER}"
- }
-}
-
-function ensure-stripe-mock() {
- run.set-all continue-on-error
- run "brew install stripe/stripe-mock/stripe-mock"
- run "brew upgrade stripe-mock"
- run "brew services start stripe-mock"
-}
-
-function ensure-python() {
- local local_python_version
- local -a version_parts
- run.set-all continue-on-error
- command -v python >/dev/null && {
- version_parts=( $(python -c 'import sys; print(sys.version_info[0])' | tr '.' ' ') )
- }
-
- local_python_version="${version_parts[0]}"
-
- if [[ ${local_python_version} -eq ${python_major_version} ]]; then
- success "Python ${python_version} is already installed."
- return 0
- else
- h1 "Installing Python ${python_version} using pyenv..."
- command -v pyenv >/dev/null || run "brew install pyenv"
- eval "$(pyenv init -)"
- run "pyenv install -s ${python_version}"
- run "pyenv global ${python_version}"
- fi
-}
-
-function ensure-ruby-via-ruby-install() {
- run "brew install ruby-install --formulae"
- export CFLAGS="-Wno-error=implicit-function-declaration"
- run "mkdir -p ${HOME}/.rbenv/versions"
- run "ruby-install --install-dir ${HOME}/.rbenv/versions ${ruby_version} -- --enable-shared"
-}
-
-
-function ensure-ruby-via-rbenv() {
- export CFLAGS="-Wno-error=implicit-function-declaration"
- run "rbenv install -s ${ruby_version}"
-}
-
-function ensure-ruby() {
- h2 "Ensuring Ruby & Gems are Installed..."
- if command -v rbenv >/dev/null; then
- run "brew upgrade rbenv ruby-build"
- else
- run "brew install rbenv ruby-build"
- fi
-
- eval "$(rbenv init -)"
-
- local bundler_version=$(gem.gemfile.bundler-version)
- test -z "${bundler_version}" && bundler_version="${default_bundler_version}"
-
- if ${use_ruby_install}; then
- ensure-ruby-via-ruby-install
- else
- ensure-ruby-via-rbenv
- fi
-}
-
-
-function ensure-bundle() {
- h2 "Installing Dependent Gems..."
- run "gem install bundler --version ${bundler_version} -N"
-
- run "bundle config set --local path 'vendor/bundle'"
- run "bundle config pg --with-pg-config=$(command -v pg_config)"
- run "bundle config libv8 --with-system-v8"
- run "bundle config therubyracer --with-v8-dir=$(brew --prefix)/opt/v8@3.15"
- run "bundle check || bundle install -j 12 || bundler update --bundler && bundle install"
-
- h2 "Creating Databases & Running Tests"
- run "bundle exec rake db:create"
- run "bundle exec rake db:migrate db:seed"
- run "bundle exec rake db:test:prepare"
- run.set-next show-output-on
- run "bundle exec rspec"
-}
-
-function puma-pids() {
- # shellcheck disable=SC2009
- ps -ef | grep "[p]uma" | cut -d ' ' -f 4
-}
-
-function puma-running() {
- netstat -an | grep LISTEN | grep -q 3000
-}
-
-# shellcheck disable=SC2207
-function kill-puma() {
- local counter=0
- while puma-running; do
- counter=$((counter + 1))
- if [[ ${counter} -gt 10 ]]; then
- .err "Too many attempts to kill puma, please whack it manually."
- exit 1
- fi
- local -a pids=($(puma-pids))
- if [[ ${#pids[@]} -gt 0 ]]; then
- set +e
- kill -TERM "${pids[@]}"
- sleep 5
- pids=($(puma-pids))
- [[ ${#pids[@]} -gt 0 ]] && {
- kill -KILL "${pids[@]}"
- sleep 2
- }
- fi
- done
-}
-
-function .err() {
- printf -- "\n${bakred} 🖕 ${clr}${txtred}${clr} ${txtred}${clr}${bakred} %s ${clr}${txtred}${clr}\n" "$*"
-}
-
-function .inf() {
- printf -- "${bakgrn} ️✔︎ ${clr}${txtgrn}${clr} ${txtylw}%s....${clr}\n" "$*"
+command -V direnv >/dev/null && {
+ h1 "Setting up direnv..."
+ eval "$(direnv hook bash)" # this script evaluates within BASH
+ run "direnv allow ."
}
-function setup() {
- export MAKE_ENV=".make.env"
- cat <>"${MAKE_ENV}"
-export RUBYOPT="-W0"
-export MALLOC_ARENA_MAX=2
-EOF
-
- eval "$(cat "${MAKE_ENV}")"
-
- local v8pkg="$(brew list | grep v8)"
- [[ -z "${v8pkg}" || ${v8pkg} == "v8" ]] && brew uninstall v8 >/dev/null 2>&1
-
- run "brew list | grep -q v8@3.15 || brew install v8@3.15 2>/dev/null"
-
- ensure-postgresql || exit 1
- ensure-ruby || exit 4
- ensure-python || exit 3
- ensure-bundle || exit 5
- ensure-stripe-mock || exit 6
- return 0
-}
+run "yarn install"
+run "yarn run build"
-function main() {
- setup
+run "rbenv install -s $(cat .ruby-version)"
+run "rbenv local $(cat .ruby-version)"
- if [[ -z "${RAILS_ENV}" ]]; then
- .err "RAILS_ENV must be set prior to calling this script."
- echo
- .inf "To boot the application via the 'make' target, run one of the: "
- echo
- .inf ' make development boot'
- .inf ' make staging boot'
- .inf ' make production boot'
- echo
- exit 1
- else
- .inf "Starting with RAILS_ENV=${RAILS_ENV}"
- fi
-
- puma-running && {
- .err "It appears that port 3000 is taken by something..."
- exit 1
- }
- .inf "Starting Puma on port 3000"
- (sleep 8 && open "http://127.0.0.1:3000") &
- run.set-next show-output-on
- run "bundle exec puma -C config/puma.rb"
- rm -f "${MAKE_ENV}"
-}
-
-export use_ruby_install=false
-
-[[ "$1" == "-h" || "$1" == "--help" ]] && {
- echo "Usage: $0 [--source] [--ruby-install]"
- echo
- echo " --source - load the source code of this script but don't run main()"
- echo " --ruby-install - Use ruby-install to build ruby instead of rbenv"
- echo
- exit 0
-}
+run "mkdir -p .bundle"
+run "cp development/config/config.Darwin.arm64 .bundle/config"
+run "bundle install -j 12"
-[[ "$*" =~ "--ruby-install" ]] && use_ruby_install=true
+h1 "Running Migrations..."
+run "bin/rails db:migrate"
+run "bin/rails db:seed"
+run "bundle exec rake db:test:prepare"
-[[ "$1" == "--source" ]] || main "$@"
+h1 "Starting Rails Server..."
+run.set-next show-output-on
+run "sleep 5 && open http://0.0.0.0:3000/" 2>/dev/null 1>&2 &
+run "make dev"
diff --git a/bin/brakeman b/bin/brakeman
new file mode 100755
index 00000000..ace1c9ba
--- /dev/null
+++ b/bin/brakeman
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+require "rubygems"
+require "bundler/setup"
+
+ARGV.unshift("--ensure-latest")
+
+load Gem.bin_path("brakeman", "brakeman")
diff --git a/bin/bundle b/bin/bundle
new file mode 100755
index 00000000..50da5fdf
--- /dev/null
+++ b/bin/bundle
@@ -0,0 +1,109 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+#
+# This file was generated by Bundler.
+#
+# The application 'bundle' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require "rubygems"
+
+m = Module.new do
+ module_function
+
+ def invoked_as_script?
+ File.expand_path($0) == File.expand_path(__FILE__)
+ end
+
+ def env_var_version
+ ENV["BUNDLER_VERSION"]
+ end
+
+ def cli_arg_version
+ return unless invoked_as_script? # don't want to hijack other binstubs
+ return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
+ bundler_version = nil
+ update_index = nil
+ ARGV.each_with_index do |a, i|
+ if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN)
+ bundler_version = a
+ end
+ next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
+ bundler_version = $1
+ update_index = i
+ end
+ bundler_version
+ end
+
+ def gemfile
+ gemfile = ENV["BUNDLE_GEMFILE"]
+ return gemfile if gemfile && !gemfile.empty?
+
+ File.expand_path("../Gemfile", __dir__)
+ end
+
+ def lockfile
+ lockfile =
+ case File.basename(gemfile)
+ when "gems.rb" then gemfile.sub(/\.rb$/, ".locked")
+ else "#{gemfile}.lock"
+ end
+ File.expand_path(lockfile)
+ end
+
+ def lockfile_version
+ return unless File.file?(lockfile)
+ lockfile_contents = File.read(lockfile)
+ return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
+ Regexp.last_match(1)
+ end
+
+ def bundler_requirement
+ @bundler_requirement ||=
+ env_var_version ||
+ cli_arg_version ||
+ bundler_requirement_for(lockfile_version)
+ end
+
+ def bundler_requirement_for(version)
+ return "#{Gem::Requirement.default}.a" unless version
+
+ bundler_gem_version = Gem::Version.new(version)
+
+ bundler_gem_version.approximate_recommendation
+ end
+
+ def load_bundler!
+ ENV["BUNDLE_GEMFILE"] ||= gemfile
+
+ activate_bundler
+ end
+
+ def activate_bundler
+ gem_error = activation_error_handling do
+ gem "bundler", bundler_requirement
+ end
+ return if gem_error.nil?
+ require_error = activation_error_handling do
+ require "bundler/version"
+ end
+ return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
+ warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
+ exit 42
+ end
+
+ def activation_error_handling
+ yield
+ nil
+ rescue StandardError, LoadError => e
+ e
+ end
+end
+
+m.load_bundler!
+
+if m.invoked_as_script?
+ load Gem.bin_path("bundler", "bundle")
+end
diff --git a/bin/db b/bin/db
deleted file mode 100755
index 156de910..00000000
--- a/bin/db
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/usr/bin/env bash
-# vim: ft=bash
-#
-# @description This script connects to production database by setting up port-forwarding.
-# For this to work you must have kubectl installed, and gcloud authenticated.
-# If you are using `direnv` you should create a file .env.local and put
-# export PGPASSWORD="..." (real password of the production db connection).
-# Then you will be able to seamlessly connect via `bin/db`.
-
-set +e
-
-# shellcheck disable=2046
-[[ -z ${BASHMATIC_HOME} ]] && export BASHMATIC_HOME="$(dirname $(cd $(dirname "${BASH_SOURCE[0]:-${(%):-%x}}") || exit 1; pwd -P))"
-[[ -d "${BASHMATIC_HOME}" ]] || bash -c "$(curl -fsSL https://bashmatic.re1.re); bashmatic-install -v" 1>/dev/null 2>/dev/null
-source "${BASHMATIC_HOME}/init.sh"
-
-is.blank "${PGPASSWORD}" && warning "You will need to paste the PostgreSQL user's password, or set PGPASSWORD env var."
-
-POD="pod/tickets-postgresql"
-PGPORT="${PGPORT:-5433}"
-
-kubectl get all | grep "${POD}" | grep -q Running || {
- error "No running PostgreSQL was detected under the pod name ${bldylw}${POD}"
- exit 1
-}
-
-netstat -an | grep LISTEN | grep -q "${PGPORT}" || {
- info "INFO: You must setup PostgreSQL port forwarding via:"
- info " ${bldylw}kubectl port-forward service/tickets-postgresql ${PGPORT}:5432"
- info "in another terminal."
- exit 2
-}
-
-set -e
-
-info "Connecting you to the Production Database..."
-run.set-next show-output-on
-run "psql -p 5433 -h localhost -U ticketsuser tickets" && success 'We hope you enjoyed your psql session ;-) Come back soon.'
diff --git a/bin/dev b/bin/dev
new file mode 100755
index 00000000..eda330c7
--- /dev/null
+++ b/bin/dev
@@ -0,0 +1,11 @@
+#!/usr/bin/env sh
+
+if gem list --no-installed --exact --silent foreman; then
+ echo "Installing foreman..."
+ gem install foreman
+fi
+
+# Default to port 3000 if not specified
+export PORT="${PORT:-3000}"
+
+exec foreman start -f Procfile.dev "$@"
diff --git a/entrypoint.sh b/bin/docker-entrypoint
similarity index 57%
rename from entrypoint.sh
rename to bin/docker-entrypoint
index 2cbb1418..511f44a9 100755
--- a/entrypoint.sh
+++ b/bin/docker-entrypoint
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
set -e
@@ -6,7 +6,7 @@ if [[ -z $RAILS_ENV ]]; then
echo "Please set RAILS_ENV before invoking this script."
exit 1
fi
-
+#
# Discourse Settings to optimize Garbage Collection
export RUBY_GC_HEAP_INIT_SLOTS=997339
export RUBY_GC_HEAP_FREE_SLOTS=626600
@@ -20,20 +20,27 @@ export RUBY_GC_OLDMALLOC_LIMIT=39339204
export RUBY_GC_OLDMALLOC_LIMIT_MAX=47207045
export RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR=1.2
+# Enable jemalloc for reduced memory usage and latency.
+if [[ -z "${LD_PRELOAD+x}" ]] && [[ -f /usr/lib/*/libjemalloc.so.2 ]]; then
+ export LD_PRELOAD="$(echo /usr/lib/*/libjemalloc.so.2)"
+fi
+
+# If running the rails server then create or migrate existing database
+if [[ "${1}" == "./bin/rails" ]] && [[ "${2}" == "server" ]]; then
+ ./bin/rails db:prepare
+elif [[ "${1}" == "migrate" ]]; then
+ ./bin/rails db:migrate
+fi
+
# https://www.mikeperham.com/2018/04/25/taming-rails-memory-bloat/
export MALLOC_ARENA_MAX=2
-if [[ ${RAILS_ENV} == "production" ]]; then
+if [[ -n ${TICKET_DB_HOST} ]]; then
export DATABASE_URL="postgres://${TICKET_DB_USER}:${TICKET_DB_PASSWORD}@${TICKET_DB_HOST}:${TICKET_DB_PORT}/${TICKET_DB_NAME}?"
+else
+ export DATABASE_URL="postgres://postgres:@localhost:5432/ticketing_app_production"
fi
-# This may be a problem if we run more than one pod
-# possible solution — adds an arbitrary delay before running it
-# delay="$(( RANDOM / 500 ))" ; sleep ${delay} && bundle exec rake db:migrate
-bundle exec rake db:migrate
-
-if [[ "$DB_SEED" = "true" ]]; then
- echo "seeding the DB"
- bundle exec rake db:seed
-fi
-bundle exec rails s -p 3000 -b '0.0.0.0'
+RAILS_ENV=${RAILS_ENV} bundle exec rails s -p 3000 -b '0.0.0.0'
+exec "${@}"
+
diff --git a/bin/music-submission-links b/bin/music-submission-links
index cdc6c896..94daee51 100755
--- a/bin/music-submission-links
+++ b/bin/music-submission-links
@@ -14,32 +14,14 @@
#
# frozen_string_literal: true
-require_relative '../lib/fnf/html_generator'
-require_relative '../lib/fnf/csv_reader'
+require_relative '../lib/fnf/submission_links'
-path = ARGV.first
-if path.nil?
- puts "USAGE: #{$PROGRAM_NAME} csv-file"
- puts 'Where the CSV file has three columns: DJ Name, Real Name, URL'
- exit 1
-end
+simple_css = if ARGV.include?('--simple-css')
+ true
+ else
+ false
+ end
-generator = FnF::HTMLGenerator.new
-reader = FnF::CSVReader.new(path)
+csv_file = ARGV.find { |arg| arg.match?(/\.csv$/) }
-generator.ol(:br) do
- reader.read do |dj_name, real_name, url|
- li do
- strong { a(href: url, target: '_blank', ref: 'canonical') { dj_name } }
- print ' '
- if dj_name != real_name
- span class: 'djName' do
- "(#{real_name.split(/ /).map(&:capitalize).join(' ')})"
- end
- end
- end
- generator.puts
- end
-end
-
-generator.render
+FnF::SubmissionLinks.new(csv_file, simple_css: simple_css).run
diff --git a/bin/puma b/bin/puma
deleted file mode 100755
index 1201e20f..00000000
--- a/bin/puma
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env ruby
-# vim: ft=ruby
-#
-# frozen_string_literal: true
-
-require 'rubygems'
-# Set up gems listed in the Gemfile.
-rails_root = File.expand_path('../', __dir__)
-ENV['BUNDLE_GEMFILE'] ||= "#{rails_root}/Gemfile"
-
-require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
-require_relative '../config/boot'
-
-require 'puma/cli'
-
-args = %W[-C #{rails_root}/config/puma.rb] + ARGV
-
-warn "> puma #{args.join(' ')}"
-
-cli = Puma::CLI.new(args)
-cli.run
diff --git a/bin/rails b/bin/rails
index e1068b28..efc03774 100755
--- a/bin/rails
+++ b/bin/rails
@@ -1,8 +1,4 @@
#!/usr/bin/env ruby
-# frozen_string_literal: true
-
-# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
-
-APP_PATH = File.expand_path('../config/application', __dir__)
-require File.expand_path('../config/boot', __dir__)
-require 'rails/commands'
+APP_PATH = File.expand_path("../config/application", __dir__)
+require_relative "../config/boot"
+require "rails/commands"
diff --git a/bin/rake b/bin/rake
index 5f615c2a..4fbf10b9 100755
--- a/bin/rake
+++ b/bin/rake
@@ -1,27 +1,4 @@
#!/usr/bin/env ruby
-# frozen_string_literal: true
-
-#
-# This file was generated by Bundler.
-#
-# The application 'rake' is installed as part of a gem, and
-# this file is here to facilitate running it.
-#
-
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
-
-bundle_binstub = File.expand_path('bundle', __dir__)
-
-if File.file?(bundle_binstub)
- if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
- load(bundle_binstub)
- else
- abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
-Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
- end
-end
-
-require 'rubygems'
-require 'bundler/setup'
-
-load Gem.bin_path('rake', 'rake')
+require_relative "../config/boot"
+require "rake"
+Rake.application.run
diff --git a/bin/rubocop b/bin/rubocop
new file mode 100755
index 00000000..40330c0f
--- /dev/null
+++ b/bin/rubocop
@@ -0,0 +1,8 @@
+#!/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/bin/setup b/bin/setup
new file mode 100755
index 00000000..a1fffbbf
--- /dev/null
+++ b/bin/setup
@@ -0,0 +1,40 @@
+#!/usr/bin/env ruby
+require "fileutils"
+
+APP_ROOT = File.expand_path("..", __dir__)
+APP_NAME = "ticket_booth"
+
+def system!(*args)
+ system(*args, exception: true)
+end
+
+FileUtils.chdir APP_ROOT do
+ # This script is a way to set up or update your development environment automatically.
+ # This script is idempotent, so that you can run it at any time and get an expectable outcome.
+ # Add necessary setup steps to this file.
+
+ puts "== Installing dependencies =="
+ system! "gem install bundler --conservative"
+ system("bundle check") || system!("bundle install")
+
+ # Install JavaScript dependencies
+ system("yarn install --check-files")
+
+ # puts "\n== Copying sample files =="
+ # unless File.exist?("config/database.yml")
+ # FileUtils.cp "config/database.yml.sample", "config/database.yml"
+ # end
+
+ puts "\n== Preparing database =="
+ system! "bin/rails db:prepare"
+
+ puts "\n== Removing old logs and tempfiles =="
+ system! "bin/rails log:clear tmp:clear"
+
+ puts "\n== Restarting application server =="
+ system! "bin/rails restart"
+
+ # puts "\n== Configuring puma-dev =="
+ # system "ln -nfs #{APP_ROOT} ~/.puma-dev/#{APP_NAME}"
+ # system "curl -Is https://#{APP_NAME}.test/up | head -n 1"
+end
diff --git a/bin/shchk b/bin/shchk
index cb833ee0..d78f3781 100755
--- a/bin/shchk
+++ b/bin/shchk
@@ -1,17 +1,21 @@
#!/usr/bin/env bash
# vim: ft=bash
-[[ -d ${HOME}/.bashmatic ]] || bash -c "$(curl -fsSL https://bashmatic.re1.re); bashmatic-install -q" ;
-source "${HOME}/.bashmatic/init.sh" 2>/dev/null
-
-command -v shellcheck >/dev/null || brew install shellcheck
+command -v shellcheck >/dev/null || {
+ if [[ $(uname -s) == "Darwin" ]]; then
+ brew install shellcheck
+ else
+ apt-get install shellcheck
+ fi
+}
shellcheck -a --color -x bin/boot-up bin/start
code=$?
if [[ ${code} -eq 0 ]]; then
- success "ShellCheck PASSED"
+ echo "ShellCheck PASSED ✅"
else
- abort "ShellChecl FAILED"
+ echo "ShellChecl FAILED ❌"
+ exit 1
fi
diff --git a/bin/site-admin b/bin/site-admin
index 993b7841..0ea44017 100755
--- a/bin/site-admin
+++ b/bin/site-admin
@@ -8,7 +8,7 @@ require 'colored2'
user_email = ARGV[1]
action = ARGV[0]
-if ARGV.size.zero? || user_email !~ URI::MailTo::EMAIL_REGEXP || !%w[add remove].include?(action)
+if ARGV.empty? || user_email !~ URI::MailTo::EMAIL_REGEXP || !%w[add remove].include?(action)
puts 'USAGE:'.bold.green
puts ' bin/site-admin '.bold.blue + 'add '.bold.magenta + ' '.bold.yellow
puts ' bin/site-admin '.bold.blue + 'remove'.bold.magenta + ' '.bold.yellow
@@ -25,7 +25,7 @@ class God
def initialize(user_email:)
@email = user_email
- @user = ::User.where(email: email).first
+ @user = ::User.where(email:).first
if user.present?
puts "\nNice to meet you, #{user.name.bold.blue} ;-)".yellow
else
@@ -65,4 +65,4 @@ class God
end
end
-God.new(user_email: user_email).divine_intervention(action.to_sym)
+God.new(user_email:).divine_intervention(action.to_sym)
diff --git a/config.ru b/config.ru
index 49b3fb10..6dc83218 100644
--- a/config.ru
+++ b/config.ru
@@ -2,5 +2,7 @@
# This file is used by Rack-based servers to start the application.
-require ::File.expand_path('config/environment', __dir__)
-run TicketBooth::Application
+require_relative 'config/environment'
+
+run Rails.application
+Rails.application.load_server
diff --git a/config/application.rb b/config/application.rb
index d328ea95..d1e798ae 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -1,41 +1,54 @@
# frozen_string_literal: true
-require File.expand_path('boot', __dir__)
-
-require 'rails/all'
-
-if defined?(Bundler)
- # If you precompile assets before deploying to production, use this line
- Bundler.require(:default, Rails.env)
+require_relative 'boot'
+
+require 'time'
+class Time
+ # Ruby 1.9 has Date#xmlschema which converts to a string without the time component.
+ # Moved the following line out of class Date.
+ unless method_defined?(:xmlschema)
+ def xmlschema; end
+ end
end
-# @description Main Application Class for the Ticket Booth
-module TicketBooth
- class Application < ::Rails::Application
- # Settings in config/environments/* take precedence over those specified here.
- # Application configuration should go into files in config/initializers
- # -- all .rb files in that directory are automatically loaded.
-
- # Custom directories with classes and modules you want to be autoloadable.
- # config.autoload_paths += %W(#{config.root}/extras)
-
- # Only load the plugins named here, in the order given (default is alphabetical).
- # :all can be used as a placeholder for all plugins not explicitly named.
- # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
-
- # Activate observers that should always be running.
- # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
+require 'rails'
+# Pick the frameworks you want:
+require 'active_model/railtie'
+require 'active_job/railtie'
+require 'active_record/railtie'
+require 'active_storage/engine'
+require 'action_controller/railtie'
+require 'action_mailer/railtie'
+# require "action_mailbox/engine"
+require 'action_text/engine'
+require 'action_view/railtie'
+require 'action_cable/engine'
+# require "rails/test_unit/railtie"
+
+# Require the gems listed in Gemfile, including any gems
+# you've limited to :test, :development, or :production.
+Bundler.require(*Rails.groups)
- # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
- # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
- # config.time_zone = 'Central Time (US & Canada)'
-
- # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
- # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
- # config.i18n.default_locale = :de
-
- # Configure the default encoding used in templates for Ruby 1.9.
- config.encoding = 'utf-8'
+module TicketBooth
+ class Application < Rails::Application
+ # Initialize configuration defaults for originally generated Rails version.
+ config.load_defaults 7.1
+
+ # Please, add to the `ignore` list any other `lib` subdirectories that do
+ # not contain `.rb` files, or that should not be reloaded or eager loaded.
+ # Common ones are `templates`, `generators`, or `middleware`, for example.
+ config.autoload_lib(ignore: %w[assets tasks])
+
+ # Configuration for the application, engines, and railties goes here.
+ #
+ # These settings can be overridden in specific environments using the files
+ # in config/environments, which are processed later.
+ #
+ config.time_zone = 'Pacific Time (US & Canada)'
+ config.eager_load_paths << Rails.root.join('app/classes')
+
+ # Don't generate system test files.
+ config.generators.system_tests = nil
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password]
@@ -47,16 +60,5 @@ class Application < ::Rails::Application
# This is necessary if your schema can't be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types
config.active_record.schema_format = :sql
-
- config.active_record.raise_in_transactional_callbacks = true
-
- # Enable the asset pipeline
- config.assets.enabled = true
-
- # Version of your assets, change this if you want to expire all your assets
- config.assets.version = '1.0'
-
- # Force application to not access DB or load models when precompiling assets
- config.assets.initialize_on_precompile = false
end
end
diff --git a/config/boot.rb b/config/boot.rb
index b37c7b06..c04863fa 100644
--- a/config/boot.rb
+++ b/config/boot.rb
@@ -1,8 +1,6 @@
# frozen_string_literal: true
-require 'rubygems'
-
-# 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 'bundler/setup' # Set up gems listed in the Gemfile.
+require 'bootsnap/setup' # Speed up boot time by caching expensive operations.
diff --git a/config/cable.yml b/config/cable.yml
new file mode 100644
index 00000000..eaa2b172
--- /dev/null
+++ b/config/cable.yml
@@ -0,0 +1,11 @@
+development:
+ adapter: redis
+ url: redis://localhost:6379/1
+
+test:
+ adapter: test
+
+production:
+ adapter: redis
+ url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
+ channel_prefix: ticket_booth_production
diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc
new file mode 100644
index 00000000..3b6649dd
--- /dev/null
+++ b/config/credentials.yml.enc
@@ -0,0 +1 @@
+VCDth9rl7xBy5IepxdQ4sH5s9sCi39B8/Gwzw+zqFaq+iuFENS/9JyMcHqA12VJ/vL0/jc57iQ7AtappjIRs1YvNc2XCvPC6JIUpMWKh4jdKjfpU9kWR7V/a53DDNfSQiXgmyZbBL+8opPYPAGgJnnQGSzmWP9yZQBY95JD04qMfgFI+YZk7tv7jRsOc6QNq7BniHl7LqsgqubieqNzzwfZq2CHyyaArEZ/pj7mWgoC9MpqhYSVrnnyh1OZs8VZjdOoeybXV6eSgBSnJQ8SO5VRPXFTNw4C/rTbhsQS0XRIxSFA7vFdcpUrXbE32Cu8tNWg08sFwhOhiX6DEjUdhVJX7zDRcxDeXEkTKVDXIxAZST+FFSf0acst1xxtVP2npH1RomEjOYxevliAX6XptllS385TECuUWQBobJQ3uxhaberM6217Uzld3rMER73fhfvwRfxiYuZSRnp0nd3bOQr1pPhQHJu0EzX6dVo2cVw9POZyzUM1C0l2S8ePGGmiHZJMab1TIvXwY1XdwQ9jImhe61EboHkmIvoVaB8XSWSLVb1RJhad684WLwJy5icxZdkU+eM4qHktQF8KdpqafTIqtdQtO16B1PX+iDDgCVa5ZqUYdWpOt9B6R2G3jEvs6mvKgUm5ozT8pYnRhLQvh4ZbjEkKQD7ZkG6fg4LdRDT7J7+SzcxCUBEyqdZ/dDkbjuZRmwsIebDBRlepy0XVV0PDnjlv/E7za+q1d5ETZ6ujPr9qDKdIYoaRCRLmiBDkQrn3SO3HrL7f8bcW0DjxkeGP7jQEdidNVCuPPRStaiPkceBd+B2sStoBjpqqpC5sHMW431ft3J16yLCz0rKYKoSjoxgS7ghBcsXRKog+zxpaOoWmsMK2gyViuy/9lhj+lLxlGCWqjcEuNRKMvv88j1mNbpJrqqSHygjNCNFQmD70j19mIicdoKTqXVIPJMrm83N9+9kyaQw8QfiBc+iiYEH0Wzw08KlzXCzswK37aeQ==--0okqjz1XA5ID2l+o--EncVmFHba3Wz1T1m3UHqoA==
\ No newline at end of file
diff --git a/config/database.yml b/config/database.yml
index b223df57..fe82298d 100644
--- a/config/database.yml
+++ b/config/database.yml
@@ -6,7 +6,7 @@ development:
pool: 5
username: postgres
password: postgres
- database: tickets_development
+ database: ticketing_app_development
test:
adapter: postgresql
@@ -16,7 +16,7 @@ test:
pool: 5
username: postgres
password: postgres
- database: tickets_test
+ database: ticketing_app_test
production:
adapter: postgresql
@@ -26,5 +26,5 @@ production:
pool: 5
username: postgres
password: postgres
- database: tickets_production
+ database: ticketing_app_production
pool: 15
diff --git a/config/deploy.rb b/config/deploy.rb
deleted file mode 100644
index bb5e048c..00000000
--- a/config/deploy.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-# frozen_string_literal: true
-
-set :application, 'tickets'
-set :user, application
-set :domain, 'tickets.com'
-set :use_sudo, false
-set :ssh_options, { forward_agent: true, compression: 'none' }
-set :keep_releases, 20 # Keep the last N releases
-
-# Source code repository
-set :repository, '.'
-set :branch, 'master'
-set :migrate_target, :current
-set :scm, :git
-set :git_enable_submodules, 1
-
-set :deploy_via, :copy
-set :deploy_to, "/home/#{user}/deploy"
-set :copy_dir, '/tmp/capistrano'
-set :copy_remote_dir, "#{deploy_to}/capistrano"
-
-set :default_environment, {
- 'RAILS_ENV' => 'production',
- 'PATH' => '$HOME/.rbenv/shims:$HOME/.rbenv/bin:$PATH'
-}
-
-set :uploads_directory, "/home/#{user}/uploads"
-
-server domain, :app, :web, :db, primary: true
-
-# Technically could break site for a short time, but that's OK for our scale
-before 'deploy:restart', 'deploy:migrate'
-
-# Asset precompilation is the first step that needs to load Rails (and hence
-# YAML config files), so create symlinks before we run the precompilation step.
-before 'deploy:assets:precompile', 'deploy:create_symlinks'
-
-namespace :deploy do
- desc 'Symlinks production config files and directories'
- task :create_symlinks, roles: :app do
- run "ln -nfs #{deploy_to}/shared/config/database.yml #{release_path}/config/database.yml"
- run "ln -nfs #{deploy_to}/shared/config/smtp.yml #{release_path}/config/smtp.yml"
- run "ln -nfs #{deploy_to}/shared/config/secret.yml #{release_path}/config/secret.yml"
- run "ln -nfs #{deploy_to}/shared/config/secrets.yml #{release_path}/config/secrets.yml"
- run "ln -nfs #{deploy_to}/shared/config/sentry.yml #{release_path}/config/sentry.yml"
- run "ln -nfs #{deploy_to}/shared/config/stripe.yml #{release_path}/config/stripe.yml"
- run "ln -nfs #{uploads_directory} #{release_path}/public/uploads"
- end
-
- desc 'Zero-downtime restart of Unicorn to load new code'
- task :restart, roles: :app do
- # XXX: There are problems with the zero-downtime deploys. In the interest
- # of having something working, just kill and start the server for now.
- # run "kill -s USR2 `cat #{shared_path}/pids/unicorn.#{application}.pid`"
- run "kill -s QUIT `cat #{shared_path}/pids/unicorn.#{application}.pid`"
- run "cd #{current_path} && bundle exec unicorn_rails -c config/unicorn.rb -D"
- end
-
- task :start, roles: :app do
- run "cd #{current_path} && bundle exec unicorn_rails -c config/unicorn.rb -D"
- end
-
- task :stop, roles: :app do
- run "kill -s QUIT `cat #{shared_path}/pids/unicorn.#{application}.pid`"
- end
-end
-
-before 'deploy' do
- `mkdir -p /tmp/capistrano`
-end
-
-# Ensure we have the latest gems before our asset pipeline tries to load them
-before 'deploy:assets:precompile', 'bundle:install'
-namespace :bundle do
- desc 'Install all gem dependencies'
- task :install, roles: :app do
- run "cd #{release_path} && bundle install --without development test"
- end
-end
diff --git a/config/environment.rb b/config/environment.rb
index 62ac0bc6..d5abe558 100644
--- a/config/environment.rb
+++ b/config/environment.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-# Load the rails application
-require File.expand_path('application', __dir__)
+# Load the Rails application.
+require_relative 'application'
-# Initialize the rails application
-TicketBooth::Application.initialize!
+# Initialize the Rails application.
+Rails.application.initialize!
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 5a5b6f6c..fc8c823b 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -1,31 +1,85 @@
# frozen_string_literal: true
-TicketBooth::Application.configure do
- # Settings specified here will take precedence over those in config/application.rb
+require 'active_support/core_ext/integer/time'
- # In the development environment your application's code is reloaded on
- # every request. This slows down response time but is perfect for development
+Rails.application.configure do
+ # Settings specified here will take precedence over those in config/application.rb.
+
+ # In the development environment your application's code is reloaded any time
+ # it changes. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
- config.cache_classes = false
- # Enable threaded mode
- # config.threadsafe!
+ config.enable_reloading = true
+ # Do not eager load code on boot.
config.eager_load = false
- # Show full error reports and disable caching
- config.consider_all_requests_local = true
- config.action_controller.perform_caching = false
+ # Show full error reports.
+ config.consider_all_requests_local = true
+
+ # Enable server timing
+ config.server_timing = true
+
+ # Enable/disable caching. By default caching is disabled.
+ # Run rails dev:cache to toggle caching.
+ if Rails.root.join('tmp/caching-dev.txt').exist?
+ config.action_controller.perform_caching = true
+ config.action_controller.enable_fragment_cache_logging = true
+
+ config.cache_store = :memory_store
+ config.public_file_server.headers = { 'Cache-Control' => "public, max-age=#{2.days.to_i}" }
+ else
+ config.action_controller.perform_caching = false
+
+ config.cache_store = :null_store
+ end
- # Print deprecation notices to the Rails logger
+ config.public_file_server.enabled = true
+
+ # Store uploaded files on the local file system (see config/storage.yml for options).
+ config.active_storage.service = :local
+
+ config.action_mailer.perform_caching = false
+
+ # Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
- # Raise exception on mass assignment protection for Active Record models
- config.active_record.mass_assignment_sanitizer = :strict
+ # Raise exceptions for disallowed deprecations.
+ config.active_support.disallowed_deprecation = :raise
+
+ # Tell Active Support which deprecation messages to disallow.
+ config.active_support.disallowed_deprecation_warnings = []
- # Expands the lines which load the assets
- config.assets.debug = true
+ # Raise an error on page load if there are pending migrations.
+ config.active_record.migration_error = :page_load
+
+ # Highlight code that triggered database queries in logs.
+ config.active_record.verbose_query_logs = true
+
+ # Highlight code that enqueued background job in logs.
+ config.active_job.verbose_enqueue_logs = true
+
+ # Suppress logger output for asset requests.
+ config.assets.quiet = true
+
+ # Raises error for missing translations.
+ # config.i18n.raise_on_missing_translations = true
+
+ # Annotate rendered view with file names.
+ config.action_view.annotate_rendered_view_with_filenames = true
+
+ # Uncomment if you wish to allow Action Cable access from any origin.
+ # config.action_cable.disable_request_forgery_protection = true
+
+ # Raise error when a before_action's only/except options reference missing actions
+ config.action_controller.raise_on_missing_callback_actions = true
+
+ # Apply autocorrection by RuboCop to files generated by `bin/rails generate`.
+ config.generators.apply_rubocop_autocorrect_after_generate!
config.action_mailer.default_url_options = { host: 'localhost:3000' }
- config.action_mailer.raise_delivery_errors = true
+ config.action_mailer.raise_delivery_errors = false
config.action_mailer.delivery_method = :test
+
+ config.hosts = %w[localhost 127.0.0.1 127.0.0.1:3000 127.0.0.1:5000]
+ config.hosts << 'tickets-local.fnf.org:5000'
end
diff --git a/config/environments/production.rb b/config/environments/production.rb
index e8b3d8e5..aaf214b6 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -1,87 +1,119 @@
# frozen_string_literal: true
-TicketBooth::Application.configure do
- # Settings specified here will take precedence over those in config/application.rb
+require 'active_support/core_ext/integer/time'
- # Code is not reloaded between requests
- config.cache_classes = true
+Rails.application.configure do
+ # Settings specified here will take precedence over those in config/application.rb.
+ # Code is not reloaded between requests.
+ config.enable_reloading = false
+
+ # Eager load code on boot. This eager loads most of Rails and
+ # your application in memory, allowing both threaded web servers
+ # and those relying on copy on write to perform better.
+ # Rake tasks automatically ignore this option for performance.
config.eager_load = true
- # Full error reports are disabled and caching is turned on
- config.consider_all_requests_local = false
+ # Full error reports are disabled and caching is turned on.
+ config.consider_all_requests_local = false
config.action_controller.perform_caching = true
- # Disable Rails's static asset server (Apache or nginx will already do this)
- ### MM: Modified for Kubernetes
- config.serve_static_files = true
+ # Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment
+ # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files).
+ # config.require_master_key = true
+
+ # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead.
+ # config.public_file_server.enabled = false
- # Compress CSS and JavaScripts
+ # Compress CSS using a preprocessor.
config.assets.css_compressor = :sass
- config.assets.js_compressor = :uglifier
- # Don't fallback to assets pipeline if a precompiled asset is missed
+ # Do not fall back to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
- # Generate digests for assets URLs
- config.assets.digest = true
+ # Enable serving of images, stylesheets, and JavaScripts from an asset server.
+ # config.asset_host = "http://assets.example.com"
+
+ # Specifies the header that your server uses for sending files.
+ # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
+ # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX
- # Defaults to nil and saved in location specified by config.assets.prefix
- # config.assets.manifest = YOUR_PATH
+ # Store uploaded files on the local file system (see config/storage.yml for options).
+ config.active_storage.service = :local
- # Specifies the header that your server uses for sending files
- # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
- # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
+ # Mount Action Cable outside main process or domain.
+ # config.action_cable.mount_path = nil
+ # config.action_cable.url = "wss://example.com/cable"
+ # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ]
+
+ # Assume all access to the app is happening through a SSL-terminating reverse proxy.
+ # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies.
+ config.assume_ssl = true
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
- ### MM: Modified for Kubernetes
- ## FIXME: Get secure cookies working.
- config.force_ssl = false
+ config.force_ssl = true
+
+ # Log to STDOUT by default
+ config.logger = ActiveSupport::Logger.new($stdout)
+ .tap { |logger| logger.formatter = Logger::Formatter.new }
+ .then { |logger| ActiveSupport::TaggedLogging.new(logger) }
# See everything in the log (default is :info)
config.log_level = :warn
# Prepend all log lines with the following tags
- config.log_tags = [->(_req) { DateTime.now }, :uuid]
-
- # For Kubernetes: Log to STDOUT
- config.logger = Logger.new($stdout)
+ config.log_tags = [->(_req) { DateTime.now }, :session_id, :request_id]
- # Use a different logger for distributed setups
- # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
+ # "info" includes generic and useful information about system operation, but avoids logging too much
+ # information to avoid inadvertent exposure of personally identifiable information (PII). If you
+ # want to log everything, set the level to "debug".
+ config.log_level = ENV.fetch('RAILS_LOG_LEVEL', 'info')
- # Use a different cache store in production
- # config.cache_store = :mem_cache_store
+ # Use a different cache store in production.
+ config.cache_store = :mem_cache_store
- # Enable serving of images, stylesheets, and JavaScripts from an asset server
- # config.action_controller.asset_host = "http://assets.example.com"
+ # Use a real queuing backend for Active Job (and separate queues per environment).
+ # config.active_job.queue_adapter = :resque
+ # config.active_job.queue_name_prefix = "ticket_booth_production"
- # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
- # config.assets.precompile += %w( search.js )
+ config.action_mailer.perform_caching = false
- # Enable threaded mode
- # config.threadsafe!
+ # Ignore bad email addresses and do not raise email delivery errors.
+ # Set this to true and configure the email server for immediate delivery to raise delivery errors.
+ # config.action_mailer.raise_delivery_errors = false
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
- # the I18n.default_locale when a translation can not be found)
+ # the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true
+ # Don't log any deprecations.
+ config.active_support.report_deprecations = false
+
+ # Do not dump schema after migrations.
+ config.active_record.dump_schema_after_migration = false
+
+ # Enable DNS rebinding protection and other `Host` header attacks.
+ # config.hosts = [
+ # "example.com", # Allow requests from example.com
+ # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com`
+ # ]
+ # Skip DNS rebinding protection for the default health check endpoint.
+ # config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
+
# Send deprecation notices to registered listeners
config.active_support.deprecation = :notify
- config.action_mailer.default_url_options = { host: ENV['TICKETS_HOST'] }
+ config.action_mailer.default_url_options = { host: ENV.fetch('TICKETS_HOST', nil) }
config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
- address: ENV['SMTP_ADDRESS'],
- port: ENV['SMTP_PORT'],
- domain: ENV['SMTP_DOMAIN'],
- user_name: ENV['SMTP_USERNAME'],
- password: ENV['SMTP_PASSWORD'],
+ address: ENV.fetch('SMTP_ADDRESS', nil),
+ port: ENV.fetch('SMTP_PORT', nil),
+ domain: ENV.fetch('SMTP_DOMAIN', nil),
+ user_name: ENV.fetch('SMTP_USERNAME', nil),
+ password: ENV.fetch('SMTP_PASSWORD', nil),
authentication: :plain,
enable_starttls_auto: true
}
-
- config.active_record.dump_schema_after_migration = false
end
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 2d4ff432..2f74fca0 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -1,17 +1,17 @@
# frozen_string_literal: true
-TicketBooth::Application.configure do
- # Settings specified here will take precedence over those in config/application.rb
+require 'active_support/core_ext/integer/time'
- # The test environment is used exclusively to run your application's
- # test suite. You never need to work with it otherwise. Remember that
- # your test database is "scratch space" for the test suite and is wiped
- # and recreated between test runs. Don't rely on the data there!
- config.cache_classes = false
- # Enable threaded mode
- # config.threadsafe!
+# The test environment is used exclusively to run your application's
+# test suite. You never need to work with it otherwise. Remember that
+# your test database is "scratch space" for the test suite and is wiped
+# and recreated between test runs. Don't rely on the data there!
- config.eager_load = true
+Rails.application.configure do
+ # Settings specified here will take precedence over those in config/application.rb.
+
+ # While tests run files are not watched, reloading is not necessary.
+ config.enable_reloading = false
# Prepend all log lines with the following tags
config.log_tags = [->(_req) { DateTime.now }, :uuid]
@@ -20,25 +20,36 @@
config.logger = Logger.new(File.expand_path('log/test.log', Rails.root))
config.logger.level = Logger::DEBUG
- # Configure static asset server for tests with Cache-Control for performance
- config.serve_static_files = true
- config.static_cache_control = 'public, max-age=3600'
+ # Eager loading loads your entire application. When running a single test locally,
+ # this is usually not necessary, and can slow down your test suite. However, it's
+ # recommended that you enable it in continuous integration systems to ensure eager
+ # loading is working properly before deploying your code.
+ config.eager_load = ENV['CI'].present?
- config.active_support.deprecation = :stderr
+ # Configure public file server for tests with Cache-Control for performance.
+ config.public_file_server.headers = { 'Cache-Control' => "public, max-age=#{1.hour.to_i}" }
- # Show full error reports and disable caching
- config.consider_all_requests_local = true
+ # Show full error reports and disable caching.
+ config.consider_all_requests_local = true
config.action_controller.perform_caching = false
+ config.cache_store = :null_store
- # Raise exceptions instead of rendering exception templates
- config.action_dispatch.show_exceptions = false
-
- # Disable request forgery protection in test environment
- config.action_controller.allow_forgery_protection = false
+ config.active_support.deprecation = :stderr
# Raise exception on mass assignment protection for Active Record models
config.active_record.mass_assignment_sanitizer = :strict
+ # Render exception templates for rescuable exceptions and raise for other exceptions.
+ config.action_dispatch.show_exceptions = :rescuable
+
+ # Disable request forgery protection in test environment.
+ config.action_controller.allow_forgery_protection = false
+
+ # Store uploaded files on the local file system in a temporary directory.
+ config.active_storage.service = :test
+
+ config.action_mailer.perform_caching = false
+
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
@@ -46,4 +57,29 @@
config.action_mailer.perform_deliveries = false
config.action_mailer.raise_delivery_errors = false
config.action_mailer.default_url_options = { host: 'fnf.events' }
+
+ # Unlike controllers, the mailer instance doesn't have any context about the
+ # incoming request so you'll need to provide the :host parameter yourself.
+ config.action_mailer.default_url_options = { host: 'fnf.events' }
+
+ # raise delivery errors in tests
+ config.action_mailer.raise_delivery_errors = true
+
+ # Print deprecation notices to the stderr.
+ config.active_support.deprecation = :stderr
+
+ # Raise exceptions for disallowed deprecations.
+ config.active_support.disallowed_deprecation = :raise
+
+ # Tell Active Support which deprecation messages to disallow.
+ config.active_support.disallowed_deprecation_warnings = []
+
+ # Raises error for missing translations.
+ # config.i18n.raise_on_missing_translations = true
+
+ # Annotate rendered view with file names.
+ # config.action_view.annotate_rendered_view_with_filenames = true
+
+ # Raise error when a before_action's only/except options reference missing actions
+ config.action_controller.raise_on_missing_callback_actions = true
end
diff --git a/config/initializers/active_support.rb b/config/initializers/active_support.rb
new file mode 100644
index 00000000..50facd84
--- /dev/null
+++ b/config/initializers/active_support.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'active_support'
+require 'active_support/core_ext'
+
+ActiveSupport::Inflector.inflections do |inflect|
+ inflect.acronym 'FnF'
+ inflect.acronym 'CSV'
+end
+
+class Time
+ unless method_defined?(:xmlschema)
+ def xmlschema; end
+ end
+end
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb
new file mode 100644
index 00000000..399154f1
--- /dev/null
+++ b/config/initializers/assets.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+# Be sure to restart your server when you modify this file.
+
+# Version of your assets, change this if you want to expire all your assets.
+Rails.application.config.assets.version = '1.0'
+
+# Add additional assets to the asset load path.
+# Rails.application.config.assets.paths << Emoji.images_path
+Rails.application.config.assets.paths << Rails.root.join('node_modules/bootstrap')
+Rails.application.config.assets.paths << Rails.root.join('node_modules/bootstrap-icons/font')
+Rails.application.config.assets.paths << Rails.root.join('node_modules/flatpickr')
+Rails.application.config.assets.paths << Rails.root.join('node_modules/flatpickr/themes')
+
+# Precompile additional assets.
+# application.js, application.css, and all non-JS/CSS in the app/assets
+# folder are already added.
+# Rails.application.config.assets.precompile += %w( admin.js admin.css )
diff --git a/config/initializers/auto_annotate_models.rb b/config/initializers/auto_annotate_models.rb
new file mode 100644
index 00000000..6c13e27b
--- /dev/null
+++ b/config/initializers/auto_annotate_models.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+Annotate.set_defaults(
+ 'active_admin' => 'false',
+ 'additional_file_patterns' => [],
+ 'routes' => 'false',
+ 'models' => 'true',
+ 'position_in_routes' => 'before',
+ 'position_in_class' => 'before',
+ 'position_in_test' => 'before',
+ 'position_in_fixture' => 'before',
+ 'position_in_factory' => 'before',
+ 'position_in_serializer' => 'before',
+ 'show_foreign_keys' => 'true',
+ 'show_complete_foreign_keys' => 'false',
+ 'show_indexes' => 'true',
+ 'simple_indexes' => 'false',
+ 'model_dir' => 'app/models',
+ 'root_dir' => '',
+ 'include_version' => 'false',
+ 'require' => '',
+ 'exclude_tests' => 'false',
+ 'exclude_fixtures' => 'false',
+ 'exclude_factories' => 'false',
+ 'exclude_serializers' => 'false',
+ 'exclude_scaffolds' => 'true',
+ 'exclude_controllers' => 'true',
+ 'exclude_helpers' => 'true',
+ 'exclude_sti_subclasses' => 'false',
+ 'ignore_model_sub_dir' => 'false',
+ 'ignore_columns' => nil,
+ 'ignore_routes' => nil,
+ 'ignore_unknown_models' => 'false',
+ 'hide_limit_column_types' => 'integer,bigint,boolean',
+ 'hide_default_column_types' => 'json,jsonb,hstore',
+ 'skip_on_db_migrate' => 'false',
+ 'format_bare' => 'true',
+ 'format_rdoc' => 'false',
+ 'format_yard' => 'false',
+ 'format_markdown' => 'false',
+ 'sort' => 'false',
+ 'force' => 'false',
+ 'frozen' => 'false',
+ 'classified_sort' => 'true',
+ 'trace' => 'false',
+ 'wrapper_open' => nil,
+ 'wrapper_close' => nil,
+ 'with_comment' => 'true'
+)
diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb
index d0f0d3b5..4b63f289 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/constants.rb b/config/initializers/constants.rb
index f345b73f..79120495 100644
--- a/config/initializers/constants.rb
+++ b/config/initializers/constants.rb
@@ -1,3 +1,3 @@
# frozen_string_literal: true
-PRODUCT_NAME = 'Ticket Booth'
+PRODUCT_NAME = 'FnF Ticket Booth'
diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb
new file mode 100644
index 00000000..35ab3fd6
--- /dev/null
+++ b/config/initializers/content_security_policy.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+# Be sure to restart your server when you modify this file.
+
+# Define an application-wide content security policy.
+# See the Securing Rails Applications Guide for more information:
+# https://guides.rubyonrails.org/security.html#content-security-policy-header
+
+# Rails.application.configure do
+# config.content_security_policy do |policy|
+# policy.default_src :self, :https
+# policy.font_src :self, :https, :data
+# policy.img_src :self, :https, :data
+# policy.object_src :none
+# policy.script_src :self, :https
+# policy.style_src :self, :https
+# # Specify URI for violation reports
+# # policy.report_uri "/csp-violation-report-endpoint"
+# end
+#
+# # Generate session nonces for permitted importmap, inline scripts, and inline styles.
+# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
+# config.content_security_policy_nonce_directives = %w(script-src style-src)
+#
+# # Report violations without enforcing the policy.
+# # config.content_security_policy_report_only = true
+# end
diff --git a/config/initializers/country_select.rb b/config/initializers/country_select.rb
new file mode 100644
index 00000000..ff3efae8
--- /dev/null
+++ b/config/initializers/country_select.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+require 'country_select'
+
+CountrySelect::DEFAULTS[:only] = %w[US GB]
+# CountrySelect::DEFAULTS[:priority_countries] = %w[US GB]
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 176d856f..c8db3126 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -1,15 +1,36 @@
# frozen_string_literal: true
+# Assuming you have not yet modified this file, each configuration option below
+# is set to its default value. Note that some are commented out while others
+# are not: uncommented lines are intended to protect your configuration from
+# breaking changes in upgrades (i.e., in the event that future versions of
+# Devise change the default values for those options).
+#
# Use this hook to configure devise mailer, warden hooks and so forth.
# Many of these configuration options can be set straight in your model.
Devise.setup do |config|
+ # The secret key used by Devise. Devise uses this key to generate
+ # random tokens. Changing this key will render invalid all existing
+ # confirmation, reset password and unlock tokens in the database.
+ # Devise will use the `secret_key_base` as its `secret_key`
+ # by default. You can change it below and use your own secret key.
+ # config.secret_key = 'e65998c860480884efbb578c3fa25df5dabc2bfb323bc8609248df8d9f857f1b80914fe7e58dd1478e4e97021eaa962ba894bf0901537b53f0a39d0842580347'
+
+ # ==> Controller configuration
+ # Configure the parent class to the devise controllers.
+ # config.parent_controller = 'DeviseController'
+
# ==> Mailer Configuration
# Configure the e-mail address which will be shown in Devise::Mailer,
- # note that it will be overwritten if you use your own mailer class with default "from" parameter.
+ # note that it will be overwritten if you use your own mailer class
+ # with default "from" parameter.
config.mailer_sender = 'FnF Tickets '
# Configure the class responsible to send e-mails.
- # config.mailer = "Devise::Mailer"
+ # config.mailer = 'Devise::Mailer'
+
+ # Configure the parent class responsible to send e-mails.
+ # config.parent_mailer = 'ActionMailer::Base'
# ==> ORM configuration
# Load and configure the ORM. Supports :active_record (default) and
@@ -25,7 +46,7 @@
# session. If you need permissions, you should implement that in a before filter.
# You can also supply a hash where the value is a boolean determining whether
# or not authentication should be aborted when the value is not present.
- # config.authentication_keys = [ :email ]
+ # config.authentication_keys = [:email]
# Configure parameters from the request object used for authentication. Each entry
# given should be a request method and it will automatically be passed to the
@@ -50,17 +71,21 @@
# enable it only for database (email + password) authentication.
# config.params_authenticatable = true
- # Tell if authentication through HTTP Basic Auth is enabled. False by default.
+ # Tell if authentication through HTTP Auth is enabled. False by default.
# It can be set to an array that will enable http authentication only for the
- # given strategies, for example, `config.http_authenticatable = [:token]` will
- # enable it only for token authentication.
+ # given strategies, for example, `config.http_authenticatable = [:database]` will
+ # enable it only for database authentication.
+ # For API-only applications to support authentication "out-of-the-box", you will likely want to
+ # enable this with :database unless you are using a custom strategy.
+ # The supported strategies are:
+ # :database = Support basic authentication with authentication key + password
# config.http_authenticatable = false
- # If http headers should be returned for AJAX requests. True by default.
+ # If 401 status code should be returned for AJAX requests. True by default.
# config.http_authenticatable_on_xhr = true
- # The realm used in Http Basic Authentication. "Application" by default.
- # config.http_authentication_realm = "Application"
+ # The realm used in Http Basic Authentication. 'Application' by default.
+ # config.http_authentication_realm = 'Application'
# It will change confirmation, password recovery and other workflows
# to behave the same regardless if the e-mail provided was right or wrong.
@@ -68,31 +93,57 @@
# config.paranoid = true
# By default Devise will store the user in session. You can skip storage for
- # :http_auth and :token_auth by adding those symbols to the array below.
+ # particular strategies by setting this option.
# Notice that if you are skipping storage for all authentication paths, you
# may want to disable generating routes to Devise's sessions controller by
- # passing :skip => :sessions to `devise_for` in your config/routes.rb
+ # passing skip: :sessions to `devise_for` in your config/routes.rb
config.skip_session_storage = [:http_auth]
+ # By default, Devise cleans up the CSRF token on authentication to
+ # avoid CSRF token fixation attacks. This means that, when using AJAX
+ # requests for sign in and sign up, you need to get a new CSRF token
+ # from the server. You can disable this option at your own risk.
+ # config.clean_up_csrf_token_on_authentication = true
+
+ # When false, Devise will not attempt to reload routes on eager load.
+ # This can reduce the time taken to boot the app but if your application
+ # requires the Devise mappings to be loaded during boot time the application
+ # won't boot properly.
+ # config.reload_routes = true
+
# ==> Configuration for :database_authenticatable
- # For bcrypt, this is the cost for hashing the password and defaults to 10. If
- # using other encryptors, it sets how many times you want the password re-encrypted.
+ # For bcrypt, this is the cost for hashing the password and defaults to 12. If
+ # using other algorithms, it sets how many times you want the password to be hashed.
+ # The number of stretches used for generating the hashed password are stored
+ # with the hashed password. This allows you to change the stretches without
+ # invalidating existing passwords.
#
# Limiting the stretches to just one in testing will increase the performance of
# your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use
- # a value less than 10 in other environments.
- config.stretches = Rails.env.test? ? 1 : 10
+ # a value less than 10 in other environments. Note that, for bcrypt (the default
+ # algorithm), the cost increases exponentially with the number of stretches (e.g.
+ # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation).
+ config.stretches = Rails.env.test? ? 1 : 12
+
+ # Set up a pepper to generate the hashed password.
+ # config.pepper = 'efa82e8d45e1e8f30bb0280d59c3952046739dae62e54a85e6aba2cc9e4ebd8665cd3b0642ee544958e08768d911302e545a4db72578ac6ea2ef4585eb93e7b4'
+
+ # Send a notification to the original email when the user's email is changed.
+ # config.send_email_changed_notification = false
- # Setup a pepper to generate the encrypted password.
- # config.pepper = "8637e2b1a4751e2d8368f014163b7d7702f4600088d505cb7f4e27b44028ef3e28d2e06059502a2c8f662b8b3138e62c17e64142932aa329e4b4cc331d57b4ec"
+ # Send a notification email when the user's password is changed.
+ # config.send_password_change_notification = false
# ==> Configuration for :confirmable
# A period that the user is allowed to access the website even without
- # confirming his account. For instance, if set to 2.days, the user will be
- # able to access the website for two days without confirming his account,
- # access will be blocked just in the third day. Default is 0.days, meaning
- # the user cannot access the website without confirming his account.
- # config.allow_unconfirmed_access_for = 24.hours
+ # confirming their account. For instance, if set to 2.days, the user will be
+ # able to access the website for two days without confirming their account,
+ # access will be blocked just in the third day.
+ # You can also set it to nil, which will allow the user to access the website
+ # without confirming their account.
+ # Default is 0.days, meaning the user cannot access the website without
+ # confirming their account.
+ # config.allow_unconfirmed_access_for = 2.days
# A period that the user is allowed to confirm their account before their
# token becomes invalid. For example, if set to 3.days, the user can confirm
@@ -104,41 +155,41 @@
# If true, requires any email changes to be confirmed (exactly the same way as
# initial account confirmation) to be applied. Requires additional unconfirmed_email
- # db field (see migrations). Until confirmed new email is stored in
- # unconfirmed email column, and copied to email column on successful confirmation.
- # config.reconfirmable = true
+ # db field (see migrations). Until confirmed, new email is stored in
+ # unconfirmed_email column, and copied to email column on successful confirmation.
+ config.reconfirmable = true
# Defines which key will be used when confirming an account
- # config.confirmation_keys = [ :email ]
+ # config.confirmation_keys = [:email]
# ==> Configuration for :rememberable
# The time the user will be remembered without asking for credentials again.
# config.remember_for = 2.weeks
+ # Invalidates all the remember me tokens when the user signs out.
+ config.expire_all_remember_me_on_sign_out = true
+
# If true, extends the user's remember period when remembered via cookie.
# config.extend_remember_period = false
# Options to be passed to the created cookie. For instance, you can set
- # :secure => true in order to force SSL only cookies.
+ # secure: true in order to force SSL only cookies.
# config.rememberable_options = {}
# ==> Configuration for :validatable
- # Range for password length. Default is 8..128.
- config.password_length = 5..128
+ # Range for password length.
+ config.password_length = 6..128
# Email regex used to validate email formats. It simply asserts that
- # an one (and only one) @ exists in the given string. This is mainly
+ # one (and only one) @ exists in the given string. This is mainly
# to give user feedback and not to assert the e-mail validity.
- # config.email_regexp = /\A[^@]+@[^@]+\z/
+ config.email_regexp = /\A[^@\s]+@[^@\s]+\z/
# ==> Configuration for :timeoutable
# The time you want to timeout the user session without activity. After this
# time the user will be asked for credentials again. Default is 30 minutes.
# config.timeout_in = 30.minutes
- # If true, expires auth token on session timeout.
- # config.expire_auth_token_on_timeout = false
-
# ==> Configuration for :lockable
# Defines which strategy will be used to lock an account.
# :failed_attempts = Locks an account after a number of failed attempts to sign in.
@@ -146,7 +197,7 @@
config.lock_strategy = :failed_attempts
# Defines which key will be used when locking and unlocking an account
- # config.unlock_keys = [ :email ]
+ # config.unlock_keys = [:email]
# Defines which strategy will be used to unlock an account.
# :email = Sends an unlock link to the user email
@@ -162,33 +213,38 @@
# Time interval to unlock the account if :time is enabled as unlock_strategy.
config.unlock_in = 1.hour
+ # Warn on the last attempt before the account is locked.
+ # config.last_attempt_warning = true
+
# ==> Configuration for :recoverable
#
# Defines which key will be used when recovering the password for an account
- # config.reset_password_keys = [ :email ]
+ # config.reset_password_keys = [:email]
# Time interval you can reset your password with a reset password key.
# Don't put a too small interval or your users won't have the time to
# change their passwords.
config.reset_password_within = 6.hours
+ # When set to false, does not sign a user in automatically after their password is
+ # reset. Defaults to true, so a user is signed in automatically after a reset.
+ # config.sign_in_after_reset_password = true
+
# ==> Configuration for :encryptable
- # Allow you to use another encryption algorithm besides bcrypt (default). You can use
- # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1,
- # :authlogic_sha512 (then you should set stretches above to 20 for default behavior)
- # and :restful_authentication_sha1 (then you should set stretches to 10, and copy
- # REST_AUTH_SITE_KEY to pepper)
+ # Allow you to use another hashing or encryption algorithm besides bcrypt (default).
+ # You can use :sha1, :sha512 or algorithms from others authentication tools as
+ # :clearance_sha1, :authlogic_sha512 (then you should set stretches above to 20
+ # for default behavior) and :restful_authentication_sha1 (then you should set
+ # stretches to 10, and copy REST_AUTH_SITE_KEY to pepper).
+ #
+ # Require the `devise-encryptable` gem when using anything other than bcrypt
# config.encryptor = :sha512
- # ==> Configuration for :token_authenticatable
- # Defines name of the authentication token params key
- # config.token_authentication_key = :auth_token
-
# ==> Scopes configuration
# Turn scoped views on. Before rendering "sessions/new", it will first check for
# "users/sessions/new". It's turned off by default because it's slower if you
# are using only default views.
- config.scoped_views = true
+ # config.scoped_views = false
# Configure the default scope given to Warden. By default it's the first
# devise role declared in your routes (usually :user).
@@ -200,14 +256,14 @@
# ==> Navigation configuration
# Lists the formats that should be treated as navigational. Formats like
- # :html, should redirect to the sign in page when the user does not have
+ # :html should redirect to the sign in page when the user does not have
# access, but formats like :xml or :json, should return 401.
#
# If you have any extra navigational formats, like :iphone or :mobile, you
# should add them to the navigational formats lists.
#
# The "*/*" below is required to match Internet Explorer requests.
- # config.navigational_formats = ["*/*", :html]
+ # config.navigational_formats = ['*/*', :html, :turbo_stream]
# The default HTTP method used to sign out a resource. Default is :delete.
config.sign_out_via = :delete
@@ -215,7 +271,7 @@
# ==> OmniAuth
# Add a new OmniAuth provider. Check the wiki for more information on setting
# up on your models and hooks.
- # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo'
+ # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'
# ==> Warden configuration
# If you want to use other strategies, that are not supported by Devise, or
@@ -223,7 +279,7 @@
#
# config.warden do |manager|
# manager.intercept_401 = false
- # manager.default_strategies(:scope => :user).unshift :some_external_strategy
+ # manager.default_strategies(scope: :user).unshift :some_external_strategy
# end
# ==> Mountable engine configurations
@@ -231,12 +287,27 @@
# is mountable, there are some extra configurations to be taken into account.
# The following options are available, assuming the engine is mounted as:
#
- # mount MyEngine, at: "/my_engine"
+ # mount MyEngine, at: '/my_engine'
#
# The router that invoked `devise_for`, in the example above, would be:
# config.router_name = :my_engine
#
- # When using omniauth, Devise cannot automatically set Omniauth path,
+ # When using OmniAuth, Devise cannot automatically set OmniAuth path,
# so you need to do it manually. For the users scope, it would be:
- # config.omniauth_path_prefix = "/my_engine/users/auth"
+ # config.omniauth_path_prefix = '/my_engine/users/auth'
+
+ # ==> Hotwire/Turbo configuration
+ # When using Devise with Hotwire/Turbo, the http status for error responses
+ # and some redirects must match the following. The default in Devise for existing
+ # apps is `200 OK` and `302 Found` respectively, but new apps are generated with
+ # these new defaults that match Hotwire/Turbo behavior.
+ # Note: These might become the new default in future versions of Devise.
+ config.responder.error_status = :unprocessable_entity
+ config.responder.redirect_status = :see_other
+
+ # ==> Configuration for :registerable
+
+ # When set to false, does not sign a user in automatically after their password is
+ # changed. Defaults to true, so a user is signed in automatically after changing a password.
+ # config.sign_in_after_change_password = true
end
diff --git a/config/initializers/enable_yjit.rb b/config/initializers/enable_yjit.rb
new file mode 100644
index 00000000..8d19d5a5
--- /dev/null
+++ b/config/initializers/enable_yjit.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+# Automatically enable YJIT as of Ruby 3.3, as it brings very
+# sizeable performance improvements.
+
+# If you are deploying to a memory constrained environment
+# you may want to delete this file, but otherwise it's free
+# performance.
+if defined? RubyVM::YJIT.enable
+ Rails.application.config.after_initialize do
+ RubyVM::YJIT.enable
+ end
+end
diff --git a/config/initializers/event_initializer.rb b/config/initializers/event_initializer.rb
deleted file mode 100644
index c299671c..00000000
--- a/config/initializers/event_initializer.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'ventable'
-require_relative '../../lib/fnf/events'
-
-module FnF
- module Events
- TicketRequestEvent.configure do
- notifies ::TicketRequestMailer
- end
-
- TicketRequestApprovedEvent.configure do
- notifies ::TicketRequestMailer
- end
- end
-end
diff --git a/config/initializers/extensions.rb b/config/initializers/extensions.rb
index 0915f36d..1f9cb945 100644
--- a/config/initializers/extensions.rb
+++ b/config/initializers/extensions.rb
@@ -1,3 +1,3 @@
# frozen_string_literal: true
-Dir.glob("#{Rails.root}/lib/extensions/*").sort.each { |f| require f }
+Dir.glob(Rails.root.join('lib/extensions/*').to_s).each { |f| require f }
diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb
new file mode 100644
index 00000000..5a36c532
--- /dev/null
+++ b/config/initializers/filter_parameter_logging.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+# Be sure to restart your server when you modify this file.
+
+# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file.
+# Use this to limit dissemination of sensitive information.
+# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
+Rails.application.config.filter_parameters += %i[
+ passw email secret token _key crypt salt certificate otp ssn
+]
diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb
index 6de9b251..9e049dcc 100644
--- a/config/initializers/inflections.rb
+++ b/config/initializers/inflections.rb
@@ -1,16 +1,18 @@
# frozen_string_literal: true
+
# Be sure to restart your server when you modify this file.
-# Add new inflection rules using the following format
-# (all these examples are active by default):
-# ActiveSupport::Inflector.inflections do |inflect|
-# inflect.plural /^(ox)$/i, '\1en'
-# inflect.singular /^(ox)en/i, '\1'
-# inflect.irregular 'person', 'people'
+# Add new inflection rules using the following format. Inflections
+# are locale specific, and you may define rules for as many different
+# locales as you wish. All of these examples are active by default:
+# ActiveSupport::Inflector.inflections(:en) do |inflect|
+# inflect.plural /^(ox)$/i, "\\1en"
+# inflect.singular /^(ox)en/i, "\\1"
+# inflect.irregular "person", "people"
# inflect.uncountable %w( fish sheep )
# end
-#
+
# These inflection rules are supported but not enabled by default:
-# ActiveSupport::Inflector.inflections do |inflect|
-# inflect.acronym 'RESTful'
+# ActiveSupport::Inflector.inflections(:en) do |inflect|
+# inflect.acronym "RESTful"
# end
diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb
index f75864f9..df5ec138 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/permissions_policy.rb b/config/initializers/permissions_policy.rb
new file mode 100644
index 00000000..e8d0b2ae
--- /dev/null
+++ b/config/initializers/permissions_policy.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+# Be sure to restart your server when you modify this file.
+
+# Define an application-wide HTTP permissions policy. For further
+# information see: https://developers.google.com/web/updates/2018/06/feature-policy
+
+# Rails.application.config.permissions_policy do |policy|
+# policy.camera :none
+# policy.gyroscope :none
+# policy.microphone :none
+# policy.usb :none
+# policy.fullscreen :self
+# policy.payment :self, "https://secure.example.com"
+# end
diff --git a/config/initializers/rollbar.rb b/config/initializers/rollbar.rb
deleted file mode 100644
index 2d76b012..00000000
--- a/config/initializers/rollbar.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# frozen_string_literal: true
-
-Rollbar.configure do |config|
- # Without configuration, Rollbar is enabled in all environments.
- # To disable in specific environments, set config.enabled=false.
-
- config.access_token = '114e7c2e6a5c4383acaf0f841db5c9fc'
-
- # Here we'll disable in 'test':
- config.enabled = false if Rails.env.test?
-
- # By default, Rollbar will try to call the `current_user` controller method
- # to fetch the logged-in user object, and then call that object's `id`
- # method to fetch this property. To customize:
- # config.person_method = "my_current_user"
- # config.person_id_method = "my_id"
-
- # Additionally, you may specify the following:
- # config.person_username_method = "username"
- # config.person_email_method = "email"
-
- # If you want to attach custom data to all exception and message reports,
- # provide a lambda like the following. It should return a hash.
- # config.custom_data_method = lambda { {:some_key => "some_value" } }
-
- # Add exception class names to the exception_level_filters hash to
- # change the level that exception is reported at. Note that if an exception
- # has already been reported and logged the level will need to be changed
- # via the rollbar interface.
- # Valid levels: 'critical', 'error', 'warning', 'info', 'debug', 'ignore'
- # 'ignore' will cause the exception to not be reported at all.
- # config.exception_level_filters.merge!('MyCriticalException' => 'critical')
- #
- # You can also specify a callable, which will be called with the exception instance.
- # config.exception_level_filters.merge!('MyCriticalException' => lambda { |e| 'critical' })
-
- # Enable asynchronous reporting (uses girl_friday or Threading if girl_friday
- # is not installed)
- # config.use_async = true
- # Supply your own async handler:
- # config.async_handler = Proc.new { |payload|
- # Thread.new { Rollbar.process_from_async_handler(payload) }
- # }
-
- # Enable asynchronous reporting (using sucker_punch)
- # config.use_sucker_punch
-
- # Enable delayed reporting (using Sidekiq)
- # config.use_sidekiq
- # You can supply custom Sidekiq options:
- # config.use_sidekiq 'queue' => 'default'
-
- # If your application runs behind a proxy server, you can set proxy parameters here.
- # If https_proxy is set in your environment, that will be used. Settings here have precedence.
- # The :host key is mandatory and must include the URL scheme (e.g. 'http://'), all other fields
- # are optional.
- #
- # config.proxy = {
- # host: 'http://some.proxy.server',
- # port: 80,
- # user: 'username_if_auth_required',
- # password: 'password_if_auth_required'
- # }
-
- # If you run your staging application instance in production environment then
- # you'll want to override the environment reported by `Rails.env` with an
- # environment variable like this: `ROLLBAR_ENV=staging`. This is a recommended
- # setup for Heroku. See:
- # https://devcenter.heroku.com/articles/deploying-to-a-custom-rails-environment
- config.environment = ENV['ROLLBAR_ENV'].presence || Rails.env
-end
diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb
index 1fd19fbd..39ce00f2 100644
--- a/config/initializers/secret_token.rb
+++ b/config/initializers/secret_token.rb
@@ -1,4 +1,4 @@
# frozen_string_literal: true
-secret_config = YAML.load_file("#{Rails.root}/config/secret.yml")
+secret_config = YAML.load_file(Rails.root.join('config/secret.yml').to_s)
TicketBooth::Application.config.secret_key_base = secret_config['secret_key_base']
diff --git a/config/initializers/stripe.rb b/config/initializers/stripe.rb
index 3d3130c5..1a91c244 100644
--- a/config/initializers/stripe.rb
+++ b/config/initializers/stripe.rb
@@ -1,4 +1,10 @@
# frozen_string_literal: true
-Stripe.api_key = ENV['STRIPE_SECRET_KEY']
-STRIPE_PUBLIC_KEY = ENV['STRIPE_PUBLIC_KEY']
+require 'stripe'
+
+TicketBooth::Application.config.x.stripe.secret_key = ENV.fetch('STRIPE_SECRET_KEY',
+ Rails.application.credentials[Rails.env.to_sym].stripe.secret_api_key)
+TicketBooth::Application.config.x.stripe.public_key = ENV.fetch('STRIPE_PUBLIC_KEY',
+ Rails.application.credentials[Rails.env.to_sym].stripe.publishable_api_key)
+
+Stripe.api_key = TicketBooth::Application.config.x.stripe.secret_key
diff --git a/config/initializers/ventable.rb b/config/initializers/ventable.rb
new file mode 100644
index 00000000..cc55e970
--- /dev/null
+++ b/config/initializers/ventable.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require_relative '../../app/classes/fnf/events'
+require_relative '../../app/classes/fnf/event_reporter'
+require_relative 'active_support'
+
+FnF::Events.initialize_events!
diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml
index 6d84c766..260e1c4b 100644
--- a/config/locales/devise.en.yml
+++ b/config/locales/devise.en.yml
@@ -1,60 +1,65 @@
-# Additional translations at https://github.com/plataformatec/devise/wiki/I18n
+# Additional translations at https://github.com/heartcombo/devise/wiki/I18n
en:
+ devise:
+ confirmations:
+ confirmed: "Your email address has been successfully confirmed."
+ send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes."
+ send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
+ failure:
+ already_authenticated: "You are already signed in."
+ inactive: "Your account is not activated yet."
+ invalid: "Invalid %{authentication_keys} or password."
+ locked: "Your account is locked."
+ last_attempt: "You have one more attempt before your account is locked."
+ not_found_in_database: "Invalid %{authentication_keys} or password."
+ timeout: "Your session expired. Please sign in again to continue."
+ unauthenticated: "You need to sign in or sign up before continuing."
+ unconfirmed: "You have to confirm your email address before continuing."
+ mailer:
+ confirmation_instructions:
+ subject: "Confirmation instructions"
+ reset_password_instructions:
+ subject: "Reset password instructions"
+ unlock_instructions:
+ subject: "Unlock instructions"
+ email_changed:
+ subject: "Email Changed"
+ password_change:
+ subject: "Password Changed"
+ omniauth_callbacks:
+ failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
+ success: "Successfully authenticated from %{kind} account."
+ passwords:
+ no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
+ send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
+ send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
+ updated: "Your password has been changed successfully. You are now signed in."
+ updated_not_active: "Your password has been changed successfully."
+ registrations:
+ destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon."
+ signed_up: "Welcome! You have signed up successfully."
+ signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
+ signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
+ signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
+ update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirmation link to confirm your new email address."
+ updated: "Your account has been updated successfully."
+ updated_but_not_signed_in: "Your account has been updated successfully, but since your password was changed, you need to sign in again."
+ sessions:
+ signed_in: "Signed in successfully."
+ signed_out: "Signed out successfully."
+ already_signed_out: "Signed out successfully."
+ unlocks:
+ send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes."
+ send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes."
+ unlocked: "Your account has been unlocked successfully. Please sign in to continue."
errors:
messages:
+ already_confirmed: "was already confirmed, please try signing in"
+ confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
expired: "has expired, please request a new one"
not_found: "not found"
- already_confirmed: "was already confirmed, please try signing in"
not_locked: "was not locked"
not_saved:
one: "1 error prohibited this %{resource} from being saved:"
other: "%{count} errors prohibited this %{resource} from being saved:"
- confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
-
- devise:
- failure:
- already_authenticated: 'You are already signed in.'
- unauthenticated: 'You need to sign in or sign up before continuing.'
- unconfirmed: 'You have to confirm your account before continuing.'
- locked: 'Your account is locked.'
- not_found_in_database: 'Invalid email or password.'
- invalid: 'Invalid email or password.'
- invalid_token: 'Invalid authentication token.'
- timeout: 'Your session expired, please sign in again to continue.'
- inactive: 'Your account was not activated yet.'
- sessions:
- signed_in: ''
- signed_out: ''
- passwords:
- send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.'
- updated: 'Your password was changed successfully. You are now signed in.'
- updated_not_active: 'Your password was changed successfully.'
- send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
- no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
- confirmations:
- send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.'
- send_paranoid_instructions: 'If your email address exists in our database, you will receive an email with instructions about how to confirm your account in a few minutes.'
- confirmed: 'Your account was successfully confirmed. You are now signed in.'
- registrations:
- signed_up: 'Welcome! You have signed up successfully.'
- signed_up_but_unconfirmed: 'A message with a confirmation link has been sent to your email address. Please open the link to activate your account.'
- signed_up_but_inactive: 'You have signed up successfully. However, we could not sign you in because your account is not yet activated.'
- signed_up_but_locked: 'You have signed up successfully. However, we could not sign you in because your account is locked.'
- updated: 'You updated your account successfully.'
- update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and click on the confirm link to finalize confirming your new email address."
- destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.'
- unlocks:
- send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.'
- unlocked: 'Your account has been unlocked successfully. Please sign in to continue.'
- send_paranoid_instructions: 'If your account exists, you will receive an email with instructions about how to unlock it in a few minutes.'
- omniauth_callbacks:
- success: 'Successfully authenticated from %{kind} account.'
- failure: 'Could not authenticate you from %{kind} because "%{reason}".'
- mailer:
- confirmation_instructions:
- subject: 'Confirmation instructions'
- reset_password_instructions:
- subject: 'Reset password instructions'
- unlock_instructions:
- subject: 'Unlock Instructions'
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 179c14ca..6c349ae5 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1,5 +1,31 @@
-# Sample localization file for English. Add more files in this directory for other locales.
-# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
+# Files in the config/locales directory are used for internationalization and
+# are automatically loaded by Rails. If you want to use locales other than
+# English, add the necessary files in this directory.
+#
+# To use the locales, use `I18n.t`:
+#
+# I18n.t "hello"
+#
+# In views, this is aliased to just `t`:
+#
+# <%= t("hello") %>
+#
+# To use a different locale, set it with `I18n.locale`:
+#
+# I18n.locale = :es
+#
+# This would use the information in config/locales/es.yml.
+#
+# To learn more about the API, please read the Rails Internationalization guide
+# at https://guides.rubyonrails.org/i18n.html.
+#
+# Be aware that YAML interprets the following case-insensitive strings as
+# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings
+# must be quoted to be interpreted as strings. For example:
+#
+# en:
+# "yes": yup
+# enabled: "ON"
en:
hello: "Hello world"
diff --git a/config/newrelic.yml b/config/newrelic.yml
deleted file mode 100644
index 4cdd5c14..00000000
--- a/config/newrelic.yml
+++ /dev/null
@@ -1,66 +0,0 @@
-#
-# This file configures the New Relic Agent. New Relic monitors Ruby, Java,
-# .NET, PHP, Python, Node, and Go applications with deep visibility and low
-# overhead. For more information, visit www.newrelic.com.
-#
-# Generated April 24, 2022
-#
-# This configuration file is custom generated for NewRelic Administration
-#
-# For full documentation of agent configuration options, please refer to
-# https://docs.newrelic.com/docs/agents/ruby-agent/installation-configuration/ruby-agent-configuration
-
-common: &default_settings
- # Required license key associated with your New Relic account.
- license_key: 3b8d66564f4a9a62e69fea0940386b32ad8cNRAL
-
- # Your application name. Renaming here affects where data displays in New
- # Relic. For more details, see https://docs.newrelic.com/docs/apm/new-relic-apm/maintenance/renaming-applications
- app_name: TicketBooth
-
- distributed_tracing:
- enabled: true
-
- # To disable the agent regardless of other settings, uncomment the following:
- # agent_enabled: false
-
- # Logging level for log/newrelic_agent.log
- log_level: info
-
- application_logging:
- # If `true`, all logging-related features for the agent can be enabled or disabled
- # independently. If `false`, all logging-related features are disabled.
- enabled: true
- forwarding:
- # If `true`, the agent captures log records emitted by this application.
- enabled: true
- # Defines the maximum number of log records to buffer in memory at a time.
- max_samples_stored: 10000
- metrics:
- # If `true`, the agent captures metrics related to logging for this application.
- enabled: true
- local_decorating:
- # If `true`, the agent decorates logs with metadata to link to entities, hosts, traces, and spans.
- # This requires a log forwarder to send your log files to New Relic.
- # This should not be used when forwarding is enabled.
- enabled: false
-
-# Environment-specific settings are in this section.
-# RAILS_ENV or RACK_ENV (as appropriate) is used to determine the environment.
-# If your application has other named environments, configure them here.
-development:
- <<: *default_settings
- app_name: TicketBooth (Development)
-
-test:
- <<: *default_settings
- # It doesn't make sense to report to New Relic from automated test runs.
- monitor_mode: false
-
-staging:
- <<: *default_settings
- app_name: TicketBooth (Staging)
-
-production:
- <<: *default_settings
-
diff --git a/config/nginx-site.conf b/config/nginx-site.conf
deleted file mode 100644
index 35a2eee1..00000000
--- a/config/nginx-site.conf
+++ /dev/null
@@ -1,30 +0,0 @@
-upstream app {
- server rails:3000;
-}
-
-# Redirect all HTTP traffic to HTTPS
-server {
- listen 80;
- listen [::]:80;
- server_name _;
- return 301 https://$host$request_uri;
-}
-
-server {
- listen 443 ssl http2 default_server;
- listen [::]:443 ssl http2 default_server;
- server_name _;
-
- # Load Let's Encrypt SSL configuration
- include /config/nginx/ssl.conf;
-
- client_max_body_size 1M;
-
- # Forward all requests to app
- location / {
- proxy_pass http://app;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header Host $http_host;
- proxy_redirect off;
- }
-}
diff --git a/config/puma.rb b/config/puma.rb
index 373bf0a0..56295b35 100644
--- a/config/puma.rb
+++ b/config/puma.rb
@@ -32,14 +32,14 @@
log_requests
log_formatter do |str|
- "#{format '%5d', $PROCESS_ID} | #{Time.now.strftime DATETIME_FORMAT} : |puma| #{str}"
+ "#{format '%5d', $PROCESS_ID} | #{Time.zone.now.strftime DATETIME_FORMAT} : |puma| #{str}"
end
require 'newrelic_rpm'
lowlevel_error_handler do |exception|
begin
- ::NewRelic::Agent.notice_error(exception)
+ NewRelic::Agent.notice_error(exception)
rescue StandardError
nil
end
diff --git a/config/routes.rb b/config/routes.rb
index 8e973c49..4118b1b3 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,20 +1,25 @@
# frozen_string_literal: true
-TicketBooth::Application.routes.draw do
- root to: 'home#index'
- get 'oops', controller: :home, action: :oops
- devise_for :users
+Rails.application.routes.draw do
+ root 'home#index'
+ # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
- event_id = 9
+ get 'oops', controller: :home, action: :oops
- get 'fnf-tickets', controller: :ticket_requests, action: :new, event_id: event_id
- get 'fnf_tickets', controller: :ticket_requests, action: :new, event_id: event_id
- get 'fnftickets', controller: :ticket_requests, action: :new, event_id: event_id
- get 'fnf', controller: :ticket_requests, action: :new, event_id: event_id
- get 'FNF', controller: :ticket_requests, action: :new, event_id: event_id
- get 'FnF', controller: :ticket_requests, action: :new, event_id: event_id
+ devise_for :users
- get 'eald', controller: :eald_payments, action: :new, event_id: event_id
+ # WTF was this for? --@kig
+ #
+ # event_id = 9
+ #
+ # get('fnf-tickets', controller: :ticket_requests, action: :new, event_id:)
+ # get('fnf_tickets', controller: :ticket_requests, action: :new, event_id:)
+ # get('fnftickets', controller: :ticket_requests, action: :new, event_id:)
+ # get('fnf', controller: :ticket_requests, action: :new, event_id:)
+ # get('FNF', controller: :ticket_requests, action: :new, event_id:)
+ # get('FnF', controller: :ticket_requests, action: :new, event_id:)
+ #
+ # get('eald', controller: :eald_payments, action: :new, event_id:)
resources :payments, only: %i[new create show] do
collection do
@@ -67,6 +72,5 @@
get :reset
end
end
-
resources :site_admins, only: %i[index new create destroy]
end
diff --git a/config/secrets.yml b/config/secrets.yml
deleted file mode 100644
index dc2a9162..00000000
--- a/config/secrets.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-development:
- ticket_request_username: fnf
- ticket_request_password: fnf
-
-test:
- ticket_request_username: fnf
- ticket_request_password: fnf
diff --git a/config/storage.yml b/config/storage.yml
new file mode 100644
index 00000000..4942ab66
--- /dev/null
+++ b/config/storage.yml
@@ -0,0 +1,34 @@
+test:
+ service: Disk
+ root: <%= Rails.root.join("tmp/storage") %>
+
+local:
+ service: Disk
+ root: <%= Rails.root.join("storage") %>
+
+# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
+# amazon:
+# service: S3
+# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
+# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
+# region: us-east-1
+# bucket: your_own_bucket-<%= Rails.env %>
+
+# Remember not to checkin your GCS keyfile to a repository
+# google:
+# service: GCS
+# project: your_project
+# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
+# bucket: your_own_bucket-<%= Rails.env %>
+
+# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
+# microsoft:
+# service: AzureStorage
+# storage_account_name: your_account_name
+# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
+# container: your_container_name-<%= Rails.env %>
+
+# mirror:
+# service: Mirror
+# primary: local
+# mirrors: [ amazon, google, microsoft ]
diff --git a/config/unicorn.rb b/config/unicorn.rb
deleted file mode 100644
index ee5c4e27..00000000
--- a/config/unicorn.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-# Set environment to production unless something else is specified
-app_name = 'tickets'
-env = ENV['RAILS_ENV'] || 'production'
-deploy_path = "#{ENV['HOME']}/deploy"
-shared_path = "#{deploy_path}/shared"
-
-# listen on both a Unix domain socket and a TCP port,
-# we use a shorter backlog for quicker failover when busy
-listen "/tmp/#{app_name}.socket", backlog: 64
-listen 3000
-pid "#{shared_path}/pids/unicorn.#{app_name}.pid"
-
-# Server has 4 cores
-worker_processes 2
-
-# Preload app for more speed
-preload_app true
-
-# Nuke workers after 30 seconds instead of 60 seconds (the default)
-timeout 30
-
-# Help ensure your application will always spawn in the symlinked
-# "current" directory that Capistrano sets up.
-working_directory "#{deploy_path}/current"
-
-stderr_path "#{shared_path}/log/unicorn.#{app_name}.stderr.log"
-stdout_path "#{shared_path}/log/unicorn.#{app_name}.stdout.log"
-
-before_fork do |server, _worker|
- # the following is highly recomended for Rails + "preload_app true"
- # as there's no need for the master process to hold a connection
- ActiveRecord::Base.connection.disconnect! if defined?(ActiveRecord::Base)
-
- # Before forking, kill the master process that belongs to the .oldbin PID.
- # This enables 0 downtime deploys.
- old_pid = "#{shared_path}/pids/unicorn.#{app_name}.pid.oldbin"
- if File.exist?(old_pid) && server.pid != old_pid
- begin
- Process.kill('QUIT', File.read(old_pid).to_i)
- rescue Errno::ENOENT, Errno::ESRCH
- # Process was already killed or no previous process exists
- end
- end
-end
-
-after_fork do |_server, _worker|
- # Required for Rails + "preload_app true",
- ActiveRecord::Base.establish_connection if defined?(ActiveRecord::Base)
-
- # If preload_app is true, then you may also want to check and
- # restart any other shared sockets/descriptors such as Memcached,
- # and Redis.
-end
diff --git a/db/migrate/20130210233501_devise_create_users.rb b/db/migrate/20130210233501_devise_create_users.rb
index 273b5d5b..0b55975d 100644
--- a/db/migrate/20130210233501_devise_create_users.rb
+++ b/db/migrate/20130210233501_devise_create_users.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class DeviseCreateUsers < ActiveRecord::Migration
+class DeviseCreateUsers < ActiveRecord::Migration[6.0]
def change
create_table(:users) do |t|
## Database authenticatable
diff --git a/db/migrate/20130223202959_create_events.rb b/db/migrate/20130223202959_create_events.rb
index 2a290325..6736a29d 100644
--- a/db/migrate/20130223202959_create_events.rb
+++ b/db/migrate/20130223202959_create_events.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class CreateEvents < ActiveRecord::Migration
+class CreateEvents < ActiveRecord::Migration[6.0]
def change
create_table :events do |t|
t.string :name
diff --git a/db/migrate/20130223204913_create_ticket_requests.rb b/db/migrate/20130223204913_create_ticket_requests.rb
index 13859902..540bf52a 100644
--- a/db/migrate/20130223204913_create_ticket_requests.rb
+++ b/db/migrate/20130223204913_create_ticket_requests.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class CreateTicketRequests < ActiveRecord::Migration
+class CreateTicketRequests < ActiveRecord::Migration[6.0]
def change
create_table :ticket_requests do |t|
t.string :name, limit: 70, null: false
diff --git a/db/migrate/20130224204644_create_payments.rb b/db/migrate/20130224204644_create_payments.rb
index bfa3fe2f..3edbb1bb 100644
--- a/db/migrate/20130224204644_create_payments.rb
+++ b/db/migrate/20130224204644_create_payments.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class CreatePayments < ActiveRecord::Migration
+class CreatePayments < ActiveRecord::Migration[6.0]
def change
create_table :payments do |t|
t.integer :ticket_request_id, null: false
diff --git a/db/migrate/20130225033247_add_name_to_user.rb b/db/migrate/20130225033247_add_name_to_user.rb
index 1b07f2f3..bc4885df 100644
--- a/db/migrate/20130225033247_add_name_to_user.rb
+++ b/db/migrate/20130225033247_add_name_to_user.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddNameToUser < ActiveRecord::Migration
+class AddNameToUser < ActiveRecord::Migration[6.0]
def change
change_table(:users) do |t|
t.column :name, :string, limit: 70, null: false
diff --git a/db/migrate/20130226010856_create_site_admins.rb b/db/migrate/20130226010856_create_site_admins.rb
index 38465318..032a68d3 100644
--- a/db/migrate/20130226010856_create_site_admins.rb
+++ b/db/migrate/20130226010856_create_site_admins.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class CreateSiteAdmins < ActiveRecord::Migration
+class CreateSiteAdmins < ActiveRecord::Migration[6.0]
def change
create_table :site_admins do |t|
t.integer :user_id, null: false
diff --git a/db/migrate/20130226221916_add_user_to_ticket_request.rb b/db/migrate/20130226221916_add_user_to_ticket_request.rb
index 59d491fd..18895876 100644
--- a/db/migrate/20130226221916_add_user_to_ticket_request.rb
+++ b/db/migrate/20130226221916_add_user_to_ticket_request.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddUserToTicketRequest < ActiveRecord::Migration
+class AddUserToTicketRequest < ActiveRecord::Migration[6.0]
def change
change_table(:ticket_requests) do |t|
t.column :user_id, :integer, null: false
diff --git a/db/migrate/20130228052958_add_special_price_to_ticket_requests.rb b/db/migrate/20130228052958_add_special_price_to_ticket_requests.rb
index 5334ca19..097d23ae 100644
--- a/db/migrate/20130228052958_add_special_price_to_ticket_requests.rb
+++ b/db/migrate/20130228052958_add_special_price_to_ticket_requests.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddSpecialPriceToTicketRequests < ActiveRecord::Migration
+class AddSpecialPriceToTicketRequests < ActiveRecord::Migration[6.0]
def change
change_table(:ticket_requests) do |t|
t.column :special_price, :decimal, precision: 8, scale: 2, null: true
diff --git a/db/migrate/20130304020307_create_jobs.rb b/db/migrate/20130304020307_create_jobs.rb
index d9eafc1f..b4cd0820 100644
--- a/db/migrate/20130304020307_create_jobs.rb
+++ b/db/migrate/20130304020307_create_jobs.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class CreateJobs < ActiveRecord::Migration
+class CreateJobs < ActiveRecord::Migration[6.0]
def change
create_table :jobs do |t|
t.belongs_to :event, null: false
diff --git a/db/migrate/20130304021739_create_time_slots.rb b/db/migrate/20130304021739_create_time_slots.rb
index 6410625e..d3717c6e 100644
--- a/db/migrate/20130304021739_create_time_slots.rb
+++ b/db/migrate/20130304021739_create_time_slots.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class CreateTimeSlots < ActiveRecord::Migration
+class CreateTimeSlots < ActiveRecord::Migration[6.0]
def change
create_table :time_slots do |t|
t.belongs_to :job, null: false
diff --git a/db/migrate/20130304022508_create_shifts.rb b/db/migrate/20130304022508_create_shifts.rb
index d6b06521..cf900f86 100644
--- a/db/migrate/20130304022508_create_shifts.rb
+++ b/db/migrate/20130304022508_create_shifts.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class CreateShifts < ActiveRecord::Migration
+class CreateShifts < ActiveRecord::Migration[6.0]
def change
create_table :shifts do |t|
t.belongs_to :time_slot, null: false
diff --git a/db/migrate/20130311213508_add_event_id_to_ticket_request.rb b/db/migrate/20130311213508_add_event_id_to_ticket_request.rb
index 04869390..309687f2 100644
--- a/db/migrate/20130311213508_add_event_id_to_ticket_request.rb
+++ b/db/migrate/20130311213508_add_event_id_to_ticket_request.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddEventIdToTicketRequest < ActiveRecord::Migration
+class AddEventIdToTicketRequest < ActiveRecord::Migration[6.0]
def change
add_column :ticket_requests, :event_id, :integer, null: false
end
diff --git a/db/migrate/20130325024448_add_ticket_cost_info_to_event.rb b/db/migrate/20130325024448_add_ticket_cost_info_to_event.rb
index 261e740d..63e4cb39 100644
--- a/db/migrate/20130325024448_add_ticket_cost_info_to_event.rb
+++ b/db/migrate/20130325024448_add_ticket_cost_info_to_event.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddTicketCostInfoToEvent < ActiveRecord::Migration
+class AddTicketCostInfoToEvent < ActiveRecord::Migration[6.0]
def change
change_table :events do |t|
t.column :adult_ticket_price, :decimal, precision: 8, scale: 2
diff --git a/db/migrate/20130325051758_rename_assistance_to_needs_assistance.rb b/db/migrate/20130325051758_rename_assistance_to_needs_assistance.rb
index c216bb6e..94435afa 100644
--- a/db/migrate/20130325051758_rename_assistance_to_needs_assistance.rb
+++ b/db/migrate/20130325051758_rename_assistance_to_needs_assistance.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class RenameAssistanceToNeedsAssistance < ActiveRecord::Migration
+class RenameAssistanceToNeedsAssistance < ActiveRecord::Migration[6.0]
def change
change_table :ticket_requests do |t|
t.rename :assistance, :needs_assistance
diff --git a/db/migrate/20130425052112_add_volunteer_shifts_to_ticket_requests.rb b/db/migrate/20130425052112_add_volunteer_shifts_to_ticket_requests.rb
index b0114f2a..46805f8a 100644
--- a/db/migrate/20130425052112_add_volunteer_shifts_to_ticket_requests.rb
+++ b/db/migrate/20130425052112_add_volunteer_shifts_to_ticket_requests.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddVolunteerShiftsToTicketRequests < ActiveRecord::Migration
+class AddVolunteerShiftsToTicketRequests < ActiveRecord::Migration[6.0]
def change
change_table :ticket_requests do |t|
t.integer :volunteer_shifts
diff --git a/db/migrate/20130427054403_create_event_admins.rb b/db/migrate/20130427054403_create_event_admins.rb
index af2c7aa2..17c3be05 100644
--- a/db/migrate/20130427054403_create_event_admins.rb
+++ b/db/migrate/20130427054403_create_event_admins.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class CreateEventAdmins < ActiveRecord::Migration
+class CreateEventAdmins < ActiveRecord::Migration[6.0]
def change
create_table :event_admins do |t|
t.references :event
@@ -10,6 +10,6 @@ def change
end
add_index :event_admins, %i[event_id user_id], unique: true
- add_index :event_admins, :user_id
+ add_index :event_admins, :user_id, name: 'index_event_admins_on_user_id_only'
end
end
diff --git a/db/migrate/20130507051822_add_max_cabin_requests_to_events.rb b/db/migrate/20130507051822_add_max_cabin_requests_to_events.rb
index b83f8ad4..e3260367 100644
--- a/db/migrate/20130507051822_add_max_cabin_requests_to_events.rb
+++ b/db/migrate/20130507051822_add_max_cabin_requests_to_events.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddMaxCabinRequestsToEvents < ActiveRecord::Migration
+class AddMaxCabinRequestsToEvents < ActiveRecord::Migration[6.0]
def change
change_table :events do |t|
t.integer :max_cabin_requests
diff --git a/db/migrate/20130509021514_create_price_rules.rb b/db/migrate/20130509021514_create_price_rules.rb
index f20f25fd..f3332c23 100644
--- a/db/migrate/20130509021514_create_price_rules.rb
+++ b/db/migrate/20130509021514_create_price_rules.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class CreatePriceRules < ActiveRecord::Migration
+class CreatePriceRules < ActiveRecord::Migration[6.0]
def change
create_table :price_rules do |t|
t.string :type
@@ -10,7 +10,5 @@ def change
t.timestamps
end
-
- add_index :price_rules, :event_id
end
end
diff --git a/db/migrate/20130514035306_add_performer_to_ticket_request.rb b/db/migrate/20130514035306_add_performer_to_ticket_request.rb
index ce17a130..e416b5b6 100644
--- a/db/migrate/20130514035306_add_performer_to_ticket_request.rb
+++ b/db/migrate/20130514035306_add_performer_to_ticket_request.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddPerformerToTicketRequest < ActiveRecord::Migration
+class AddPerformerToTicketRequest < ActiveRecord::Migration[6.0]
def change
change_table :ticket_requests do |t|
t.boolean :performer, null: false, default: false
diff --git a/db/migrate/20130616002401_add_photo_to_events.rb b/db/migrate/20130616002401_add_photo_to_events.rb
index 34704ed2..62d0a7c1 100644
--- a/db/migrate/20130616002401_add_photo_to_events.rb
+++ b/db/migrate/20130616002401_add_photo_to_events.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddPhotoToEvents < ActiveRecord::Migration
+class AddPhotoToEvents < ActiveRecord::Migration[6.0]
def change
add_column :events, :photo, :string
end
diff --git a/db/migrate/20130628042018_add_tickets_require_approval_to_events.rb b/db/migrate/20130628042018_add_tickets_require_approval_to_events.rb
index 7f723de9..7b3c4825 100644
--- a/db/migrate/20130628042018_add_tickets_require_approval_to_events.rb
+++ b/db/migrate/20130628042018_add_tickets_require_approval_to_events.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddTicketsRequireApprovalToEvents < ActiveRecord::Migration
+class AddTicketsRequireApprovalToEvents < ActiveRecord::Migration[6.0]
def change
add_column :events, :tickets_require_approval, :boolean, default: true, null: false
end
diff --git a/db/migrate/20130628050717_remove_default_status_from_ticket_requests.rb b/db/migrate/20130628050717_remove_default_status_from_ticket_requests.rb
index f71428c9..d65d189a 100644
--- a/db/migrate/20130628050717_remove_default_status_from_ticket_requests.rb
+++ b/db/migrate/20130628050717_remove_default_status_from_ticket_requests.rb
@@ -1,15 +1,15 @@
# frozen_string_literal: true
-class RemoveDefaultStatusFromTicketRequests < ActiveRecord::Migration
+class RemoveDefaultStatusFromTicketRequests < ActiveRecord::Migration[6.0]
def up
- execute <<-SQL
+ execute <<-SQL.squish
ALTER TABLE ticket_requests
ALTER COLUMN status DROP DEFAULT
SQL
end
def down
- execute <<-SQL
+ execute <<-SQL.squish
ALTER TABLE ticket_requests
ALTER COLUMN status SET DEFAULT 'P'
SQL
diff --git a/db/migrate/20130701042655_add_require_mailing_address_to_events.rb b/db/migrate/20130701042655_add_require_mailing_address_to_events.rb
index bda744bb..b4f8f393 100644
--- a/db/migrate/20130701042655_add_require_mailing_address_to_events.rb
+++ b/db/migrate/20130701042655_add_require_mailing_address_to_events.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddRequireMailingAddressToEvents < ActiveRecord::Migration
+class AddRequireMailingAddressToEvents < ActiveRecord::Migration[6.0]
def change
add_column :events, :require_mailing_address, :boolean, default: false, null: false
end
diff --git a/db/migrate/20130701045629_add_allow_financial_assistance_to_events.rb b/db/migrate/20130701045629_add_allow_financial_assistance_to_events.rb
index dc400749..72f7be2d 100644
--- a/db/migrate/20130701045629_add_allow_financial_assistance_to_events.rb
+++ b/db/migrate/20130701045629_add_allow_financial_assistance_to_events.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddAllowFinancialAssistanceToEvents < ActiveRecord::Migration
+class AddAllowFinancialAssistanceToEvents < ActiveRecord::Migration[6.0]
def change
add_column :events, :allow_financial_assistance, :boolean, default: false, null: false
end
diff --git a/db/migrate/20130701054452_add_ask_how_many_shifts_to_events.rb b/db/migrate/20130701054452_add_ask_how_many_shifts_to_events.rb
index 0d26d0c5..982d755e 100644
--- a/db/migrate/20130701054452_add_ask_how_many_shifts_to_events.rb
+++ b/db/migrate/20130701054452_add_ask_how_many_shifts_to_events.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddAskHowManyShiftsToEvents < ActiveRecord::Migration
+class AddAskHowManyShiftsToEvents < ActiveRecord::Migration[6.0]
def change
add_column :events, :ask_how_many_shifts, :boolean, default: false, null: false
end
diff --git a/db/migrate/20130702041357_remove_not_null_constraint_from_ticket_request_address.rb b/db/migrate/20130702041357_remove_not_null_constraint_from_ticket_request_address.rb
index fcebd776..57dcf1d7 100644
--- a/db/migrate/20130702041357_remove_not_null_constraint_from_ticket_request_address.rb
+++ b/db/migrate/20130702041357_remove_not_null_constraint_from_ticket_request_address.rb
@@ -1,15 +1,15 @@
# frozen_string_literal: true
-class RemoveNotNullConstraintFromTicketRequestAddress < ActiveRecord::Migration
+class RemoveNotNullConstraintFromTicketRequestAddress < ActiveRecord::Migration[6.0]
def up
- execute <<-SQL
+ execute <<-SQL.squish
ALTER TABLE ticket_requests
ALTER COLUMN address DROP NOT NULL
SQL
end
def down
- execute <<-SQL
+ execute <<-SQL.squish
ALTER TABLE ticket_requests
ALTER COLUMN address SET NOT NULL
SQL
diff --git a/db/migrate/20130707204929_add_donation_to_ticket_requests.rb b/db/migrate/20130707204929_add_donation_to_ticket_requests.rb
index 67741d48..819775fd 100644
--- a/db/migrate/20130707204929_add_donation_to_ticket_requests.rb
+++ b/db/migrate/20130707204929_add_donation_to_ticket_requests.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddDonationToTicketRequests < ActiveRecord::Migration
+class AddDonationToTicketRequests < ActiveRecord::Migration[6.0]
def change
add_column :ticket_requests, :donation, :decimal, precision: 8, scale: 2, default: 0
end
diff --git a/db/migrate/20130707222903_add_allow_donations_to_events.rb b/db/migrate/20130707222903_add_allow_donations_to_events.rb
index 79550033..03588868 100644
--- a/db/migrate/20130707222903_add_allow_donations_to_events.rb
+++ b/db/migrate/20130707222903_add_allow_donations_to_events.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddAllowDonationsToEvents < ActiveRecord::Migration
+class AddAllowDonationsToEvents < ActiveRecord::Migration[6.0]
def change
add_column :events, :allow_donations, :boolean, default: false, null: false
end
diff --git a/db/migrate/20130803212458_add_start_and_end_ticket_sale_times_to_events.rb b/db/migrate/20130803212458_add_start_and_end_ticket_sale_times_to_events.rb
index 7afab46f..02c8f7c2 100644
--- a/db/migrate/20130803212458_add_start_and_end_ticket_sale_times_to_events.rb
+++ b/db/migrate/20130803212458_add_start_and_end_ticket_sale_times_to_events.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddStartAndEndTicketSaleTimesToEvents < ActiveRecord::Migration
+class AddStartAndEndTicketSaleTimesToEvents < ActiveRecord::Migration[6.0]
def change
add_column :events, :ticket_sales_start_time, :datetime
add_column :events, :ticket_sales_end_time, :datetime
diff --git a/db/migrate/20140428041744_add_explanation_to_payments.rb b/db/migrate/20140428041744_add_explanation_to_payments.rb
index 7bbdb152..87c23a83 100644
--- a/db/migrate/20140428041744_add_explanation_to_payments.rb
+++ b/db/migrate/20140428041744_add_explanation_to_payments.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddExplanationToPayments < ActiveRecord::Migration
+class AddExplanationToPayments < ActiveRecord::Migration[6.0]
def change
change_table :payments do |t|
t.string :explanation, null: true
diff --git a/db/migrate/20140428045329_add_role_to_ticket_requests.rb b/db/migrate/20140428045329_add_role_to_ticket_requests.rb
index e63be5a0..7b89c17f 100644
--- a/db/migrate/20140428045329_add_role_to_ticket_requests.rb
+++ b/db/migrate/20140428045329_add_role_to_ticket_requests.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddRoleToTicketRequests < ActiveRecord::Migration
+class AddRoleToTicketRequests < ActiveRecord::Migration[6.0]
def change
change_table :ticket_requests do |t|
t.string :role, null: false, default: 'volunteer'
diff --git a/db/migrate/20140515053804_remove_performer_from_ticket_requests.rb b/db/migrate/20140515053804_remove_performer_from_ticket_requests.rb
index 65ccf34e..48cf6b1a 100644
--- a/db/migrate/20140515053804_remove_performer_from_ticket_requests.rb
+++ b/db/migrate/20140515053804_remove_performer_from_ticket_requests.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class RemovePerformerFromTicketRequests < ActiveRecord::Migration
+class RemovePerformerFromTicketRequests < ActiveRecord::Migration[6.0]
def change
remove_column :ticket_requests, :performer
end
diff --git a/db/migrate/20140515054433_add_vehicle_camping_requested_to_ticket_requests.rb b/db/migrate/20140515054433_add_vehicle_camping_requested_to_ticket_requests.rb
index 11ddca3c..45210026 100644
--- a/db/migrate/20140515054433_add_vehicle_camping_requested_to_ticket_requests.rb
+++ b/db/migrate/20140515054433_add_vehicle_camping_requested_to_ticket_requests.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddVehicleCampingRequestedToTicketRequests < ActiveRecord::Migration
+class AddVehicleCampingRequestedToTicketRequests < ActiveRecord::Migration[6.0]
def change
add_column :ticket_requests, :vehicle_camping_requested, :boolean
end
diff --git a/db/migrate/20140605034909_add_previous_contribution_to_ticket_requests.rb b/db/migrate/20140605034909_add_previous_contribution_to_ticket_requests.rb
index 603dd66f..2811e519 100644
--- a/db/migrate/20140605034909_add_previous_contribution_to_ticket_requests.rb
+++ b/db/migrate/20140605034909_add_previous_contribution_to_ticket_requests.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddPreviousContributionToTicketRequests < ActiveRecord::Migration
+class AddPreviousContributionToTicketRequests < ActiveRecord::Migration[6.0]
def change
change_table :ticket_requests do |t|
t.string :previous_contribution, limit: 250, null: true
diff --git a/db/migrate/20140605045004_extract_address_into_multiple_fields.rb b/db/migrate/20140605045004_extract_address_into_multiple_fields.rb
index 944c47e2..9ca359b8 100644
--- a/db/migrate/20140605045004_extract_address_into_multiple_fields.rb
+++ b/db/migrate/20140605045004_extract_address_into_multiple_fields.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ExtractAddressIntoMultipleFields < ActiveRecord::Migration
+class ExtractAddressIntoMultipleFields < ActiveRecord::Migration[6.0]
def change
remove_column :ticket_requests, :address
diff --git a/db/migrate/20140605052627_remove_volunteer_shifts_from_ticket_requests.rb b/db/migrate/20140605052627_remove_volunteer_shifts_from_ticket_requests.rb
index b26201d2..5bbadd7a 100644
--- a/db/migrate/20140605052627_remove_volunteer_shifts_from_ticket_requests.rb
+++ b/db/migrate/20140605052627_remove_volunteer_shifts_from_ticket_requests.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class RemoveVolunteerShiftsFromTicketRequests < ActiveRecord::Migration
+class RemoveVolunteerShiftsFromTicketRequests < ActiveRecord::Migration[6.0]
def change
remove_column :ticket_requests, :volunteer_shifts
end
diff --git a/db/migrate/20140605053705_remove_ask_how_many_shifts_from_events.rb b/db/migrate/20140605053705_remove_ask_how_many_shifts_from_events.rb
index 1d29ee0a..75ffbe6e 100644
--- a/db/migrate/20140605053705_remove_ask_how_many_shifts_from_events.rb
+++ b/db/migrate/20140605053705_remove_ask_how_many_shifts_from_events.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class RemoveAskHowManyShiftsFromEvents < ActiveRecord::Migration
+class RemoveAskHowManyShiftsFromEvents < ActiveRecord::Migration[6.0]
def change
remove_column :events, :ask_how_many_shifts
end
diff --git a/db/migrate/20140605060026_add_camping_type_to_ticket_requests.rb b/db/migrate/20140605060026_add_camping_type_to_ticket_requests.rb
index accac8de..2bae8863 100644
--- a/db/migrate/20140605060026_add_camping_type_to_ticket_requests.rb
+++ b/db/migrate/20140605060026_add_camping_type_to_ticket_requests.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddCampingTypeToTicketRequests < ActiveRecord::Migration
+class AddCampingTypeToTicketRequests < ActiveRecord::Migration[6.0]
def change
remove_column :ticket_requests, :vehicle_camping_requested
diff --git a/db/migrate/20140611044708_add_admin_notes_to_ticket_requests.rb b/db/migrate/20140611044708_add_admin_notes_to_ticket_requests.rb
index a2c81a87..82c8cc70 100644
--- a/db/migrate/20140611044708_add_admin_notes_to_ticket_requests.rb
+++ b/db/migrate/20140611044708_add_admin_notes_to_ticket_requests.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddAdminNotesToTicketRequests < ActiveRecord::Migration
+class AddAdminNotesToTicketRequests < ActiveRecord::Migration[6.0]
def change
change_table :ticket_requests do |t|
t.string :admin_notes, limit: 512, null: true
diff --git a/db/migrate/20140611051614_add_authentication_token_to_users.rb b/db/migrate/20140611051614_add_authentication_token_to_users.rb
index 4096c41c..b61d0483 100644
--- a/db/migrate/20140611051614_add_authentication_token_to_users.rb
+++ b/db/migrate/20140611051614_add_authentication_token_to_users.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddAuthenticationTokenToUsers < ActiveRecord::Migration
+class AddAuthenticationTokenToUsers < ActiveRecord::Migration[6.0]
def change
change_table :users do |t|
t.string :authentication_token, limit: 64, null: true
diff --git a/db/migrate/20140616024138_add_camping_type_explanation_to_ticket_requests.rb b/db/migrate/20140616024138_add_camping_type_explanation_to_ticket_requests.rb
index ec30516a..141498d1 100644
--- a/db/migrate/20140616024138_add_camping_type_explanation_to_ticket_requests.rb
+++ b/db/migrate/20140616024138_add_camping_type_explanation_to_ticket_requests.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddCampingTypeExplanationToTicketRequests < ActiveRecord::Migration
+class AddCampingTypeExplanationToTicketRequests < ActiveRecord::Migration[6.0]
def change
change_table :ticket_requests do |t|
t.string :camping_type_explanation, limit: 200, null: true
diff --git a/db/migrate/20140616030905_change_camping_type_on_ticket_requests.rb b/db/migrate/20140616030905_change_camping_type_on_ticket_requests.rb
index 326699a5..1d37ed1a 100644
--- a/db/migrate/20140616030905_change_camping_type_on_ticket_requests.rb
+++ b/db/migrate/20140616030905_change_camping_type_on_ticket_requests.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ChangeCampingTypeOnTicketRequests < ActiveRecord::Migration
+class ChangeCampingTypeOnTicketRequests < ActiveRecord::Migration[6.0]
def change
remove_column :ticket_requests, :camping_type
remove_column :ticket_requests, :camping_type_explanation
diff --git a/db/migrate/20140706232217_add_ticket_request_end_time_to_events.rb b/db/migrate/20140706232217_add_ticket_request_end_time_to_events.rb
index 2acdac96..7f3faebe 100644
--- a/db/migrate/20140706232217_add_ticket_request_end_time_to_events.rb
+++ b/db/migrate/20140706232217_add_ticket_request_end_time_to_events.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddTicketRequestEndTimeToEvents < ActiveRecord::Migration
+class AddTicketRequestEndTimeToEvents < ActiveRecord::Migration[6.0]
def change
change_table :events do |t|
t.datetime :ticket_requests_end_time, null: true
diff --git a/db/migrate/20150609064608_add_agrees_terms_to_ticket_requests.rb b/db/migrate/20150609064608_add_agrees_terms_to_ticket_requests.rb
index 500d4bf4..fa7a5ac4 100644
--- a/db/migrate/20150609064608_add_agrees_terms_to_ticket_requests.rb
+++ b/db/migrate/20150609064608_add_agrees_terms_to_ticket_requests.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddAgreesTermsToTicketRequests < ActiveRecord::Migration
+class AddAgreesTermsToTicketRequests < ActiveRecord::Migration[6.0]
def change
add_column :ticket_requests, :agrees_to_terms, :boolean
end
diff --git a/db/migrate/20160611234315_add_eald_columns.rb b/db/migrate/20160611234315_add_eald_columns.rb
index 70b89394..af4262a8 100644
--- a/db/migrate/20160611234315_add_eald_columns.rb
+++ b/db/migrate/20160611234315_add_eald_columns.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddEaldColumns < ActiveRecord::Migration
+class AddEaldColumns < ActiveRecord::Migration[6.0]
def change
change_table :ticket_requests do |t|
t.integer :early_arrival_passes, null: false, default: 0
diff --git a/db/migrate/20180527021019_add_guests_to_ticket_request.rb b/db/migrate/20180527021019_add_guests_to_ticket_request.rb
index 5b9c73f6..534a4cc1 100644
--- a/db/migrate/20180527021019_add_guests_to_ticket_request.rb
+++ b/db/migrate/20180527021019_add_guests_to_ticket_request.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddGuestsToTicketRequest < ActiveRecord::Migration
+class AddGuestsToTicketRequest < ActiveRecord::Migration[6.0]
def change
change_table :ticket_requests do |t|
t.text :guests, null: true
diff --git a/db/migrate/20240311182346_remove_unused_indexes_from_event_admin.rb b/db/migrate/20240311182346_remove_unused_indexes_from_event_admin.rb
new file mode 100644
index 00000000..490598ab
--- /dev/null
+++ b/db/migrate/20240311182346_remove_unused_indexes_from_event_admin.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+class RemoveUnusedIndexesFromEventAdmin < ActiveRecord::Migration[6.0]
+ def up
+ if index_exists?(:event_admins, :user_id,
+ name: 'index_event_admins_on_user_id_only')
+ remove_index :event_admins, column: [:user_id], name: 'index_event_admins_on_user_id_only'
+ end
+
+ if index_exists?(:event_admins, :event_id, name: 'index_event_admins_on_event_id')
+ remove_index :event_admins, column: [:event_id], name: 'index_event_admins_on_event_id'
+ end
+
+ execute 'analyze event_admins'
+ end
+
+ def down
+ unless index_exists?(:event_admins, :user_id, name: 'index_event_admins_on_user_id_only')
+ add_index(:event_admins, [:user_id], name: 'index_event_admins_on_user_id_only')
+ end
+
+ unless index_exists?(:event_admins, :event_id, name: 'index_event_admins_on_event_id')
+ add_index(:event_admins, [:event_id], name: 'index_event_admins_on_event_id')
+ end
+
+ execute 'analyze event_admins'
+ end
+end
diff --git a/db/migrate/20240418004856_split_users_name_into_first_and_last.rb b/db/migrate/20240418004856_split_users_name_into_first_and_last.rb
new file mode 100644
index 00000000..f494db87
--- /dev/null
+++ b/db/migrate/20240418004856_split_users_name_into_first_and_last.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class SplitUsersNameIntoFirstAndLast < ActiveRecord::Migration[7.1]
+ # rubocop: disable Rails/BulkChangeTable
+ def up
+ add_column :users, :first, :text
+ add_column :users, :last, :text
+
+ User.find_each do |user|
+ next if user.name.blank?
+
+ name_components = user.name.gsub(/^(Dr\.?|Esq\.?)\s?/, '').split(/\s+/)
+ user.first = name_components.shift
+ user.last = name_components.join(' ')
+ execute "update users set first = '#{user.first}', last = '#{user.last}' where id = #{user.id}"
+ end
+ end
+
+ def down
+ remove_column(:users, :first)
+ remove_column(:users, :last)
+ end
+ # rubocop: enable Rails/BulkChangeTable
+end
diff --git a/db/schema.rb b/db/schema.rb
index b8081546..54d23912 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1,177 +1,177 @@
-# frozen_string_literal: true
-
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
-# Note that this schema.rb definition is the authoritative source for your
-# database schema. If you need to create the application database on another
-# system, you should be using db:schema:load, not running all the migrations
-# from scratch. The latter is a flawed and unsustainable approach (the more migrations
-# you'll amass, the slower it'll run and the greater likelihood for issues).
+# This file is the source Rails uses to define your schema when running `bin/rails
+# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
+# be faster and is potentially less error prone than running all of your
+# migrations from scratch. Old migrations may fail to apply correctly if those
+# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20_180_527_021_019) do
+ActiveRecord::Schema[7.1].define(version: 2024_03_11_182346) do
# These are extensions that must be enabled in order to support this database
- enable_extension 'plpgsql'
-
- create_table 'eald_payments', force: :cascade do |t|
- t.integer 'event_id'
- t.string 'stripe_charge_id', null: false
- t.integer 'amount_charged_cents', null: false
- t.string 'name', limit: 255, null: false
- t.string 'email', limit: 255, null: false
- t.integer 'early_arrival_passes', default: 0, null: false
- t.integer 'late_departure_passes', default: 0, null: false
- t.datetime 'created_at'
- t.datetime 'updated_at'
+ enable_extension "plpgsql"
+
+ create_table "eald_payments", force: :cascade do |t|
+ t.bigint "event_id"
+ t.string "stripe_charge_id", null: false
+ t.integer "amount_charged_cents", null: false
+ t.string "name", limit: 255, null: false
+ t.string "email", limit: 255, null: false
+ t.integer "early_arrival_passes", default: 0, null: false
+ t.integer "late_departure_passes", default: 0, null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["event_id"], name: "index_eald_payments_on_event_id"
end
- create_table 'event_admins', force: :cascade do |t|
- t.integer 'event_id'
- t.integer 'user_id'
- t.datetime 'created_at'
- t.datetime 'updated_at'
+ create_table "event_admins", force: :cascade do |t|
+ t.bigint "event_id"
+ t.bigint "user_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["event_id", "user_id"], name: "index_event_admins_on_event_id_and_user_id", unique: true
+ t.index ["user_id"], name: "index_event_admins_on_user_id"
end
- add_index 'event_admins', %w[event_id user_id], name: 'index_event_admins_on_event_id_and_user_id', unique: true,
- using: :btree
- add_index 'event_admins', ['user_id'], name: 'index_event_admins_on_user_id', using: :btree
-
- create_table 'events', force: :cascade do |t|
- t.string 'name'
- t.datetime 'start_time'
- t.datetime 'end_time'
- t.datetime 'created_at'
- t.datetime 'updated_at'
- t.decimal 'adult_ticket_price', precision: 8, scale: 2
- t.decimal 'kid_ticket_price', precision: 8, scale: 2
- t.decimal 'cabin_price', precision: 8, scale: 2
- t.integer 'max_adult_tickets_per_request'
- t.integer 'max_kid_tickets_per_request'
- t.integer 'max_cabins_per_request'
- t.integer 'max_cabin_requests'
- t.string 'photo'
- t.boolean 'tickets_require_approval', default: true, null: false
- t.boolean 'require_mailing_address', default: false, null: false
- t.boolean 'allow_financial_assistance', default: false, null: false
- t.boolean 'allow_donations', default: false, null: false
- t.datetime 'ticket_sales_start_time'
- t.datetime 'ticket_sales_end_time'
- t.datetime 'ticket_requests_end_time'
- t.decimal 'early_arrival_price', precision: 8, scale: 2, default: 0.0
- t.decimal 'late_departure_price', precision: 8, scale: 2, default: 0.0
+ create_table "events", force: :cascade do |t|
+ t.string "name"
+ t.datetime "start_time", precision: nil
+ t.datetime "end_time", precision: nil
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.decimal "adult_ticket_price", precision: 8, scale: 2
+ t.decimal "kid_ticket_price", precision: 8, scale: 2
+ t.decimal "cabin_price", precision: 8, scale: 2
+ t.integer "max_adult_tickets_per_request"
+ t.integer "max_kid_tickets_per_request"
+ t.integer "max_cabins_per_request"
+ t.integer "max_cabin_requests"
+ t.string "photo"
+ t.boolean "tickets_require_approval", default: true, null: false
+ t.boolean "require_mailing_address", default: false, null: false
+ t.boolean "allow_financial_assistance", default: false, null: false
+ t.boolean "allow_donations", default: false, null: false
+ t.datetime "ticket_sales_start_time", precision: nil
+ t.datetime "ticket_sales_end_time", precision: nil
+ t.datetime "ticket_requests_end_time", precision: nil
+ t.decimal "early_arrival_price", precision: 8, scale: 2, default: "0.0"
+ t.decimal "late_departure_price", precision: 8, scale: 2, default: "0.0"
end
- create_table 'jobs', force: :cascade do |t|
- t.integer 'event_id', null: false
- t.string 'name', limit: 100, null: false
- t.string 'description', limit: 512, null: false
- t.datetime 'created_at'
- t.datetime 'updated_at'
+ create_table "jobs", force: :cascade do |t|
+ t.bigint "event_id", null: false
+ t.string "name", limit: 100, null: false
+ t.string "description", limit: 512, null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["event_id"], name: "index_jobs_on_event_id"
end
- create_table 'payments', force: :cascade do |t|
- t.integer 'ticket_request_id', null: false
- t.string 'stripe_charge_id', limit: 255
- t.string 'status', limit: 1, default: 'P', null: false
- t.datetime 'created_at'
- t.datetime 'updated_at'
- t.string 'explanation'
+ create_table "payments", force: :cascade do |t|
+ t.integer "ticket_request_id", null: false
+ t.string "stripe_charge_id", limit: 255
+ t.string "status", limit: 1, default: "P", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.string "explanation"
end
- create_table 'price_rules', force: :cascade do |t|
- t.string 'type'
- t.integer 'event_id'
- t.decimal 'price', precision: 8, scale: 2
- t.integer 'trigger_value'
- t.datetime 'created_at'
- t.datetime 'updated_at'
+ create_table "price_rules", force: :cascade do |t|
+ t.string "type"
+ t.bigint "event_id"
+ t.decimal "price", precision: 8, scale: 2
+ t.integer "trigger_value"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["event_id"], name: "index_price_rules_on_event_id"
end
- add_index 'price_rules', ['event_id'], name: 'index_price_rules_on_event_id', using: :btree
-
- create_table 'shifts', force: :cascade do |t|
- t.integer 'time_slot_id', null: false
- t.integer 'user_id', null: false
- t.string 'name', limit: 70
- t.datetime 'created_at'
- t.datetime 'updated_at'
+ create_table "shifts", force: :cascade do |t|
+ t.bigint "time_slot_id", null: false
+ t.bigint "user_id", null: false
+ t.string "name", limit: 70
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["time_slot_id"], name: "index_shifts_on_time_slot_id"
+ t.index ["user_id"], name: "index_shifts_on_user_id"
end
- create_table 'site_admins', force: :cascade do |t|
- t.integer 'user_id', null: false
- t.datetime 'created_at'
- t.datetime 'updated_at'
+ create_table "site_admins", force: :cascade do |t|
+ t.integer "user_id", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
end
- create_table 'ticket_requests', force: :cascade do |t|
- t.integer 'adults', default: 1, null: false
- t.integer 'kids', default: 0, null: false
- t.integer 'cabins', default: 0, null: false
- t.boolean 'needs_assistance', default: false, null: false
- t.string 'notes', limit: 500
- t.string 'status', limit: 1, null: false
- t.datetime 'created_at'
- t.datetime 'updated_at'
- t.integer 'user_id', null: false
- t.decimal 'special_price', precision: 8, scale: 2
- t.integer 'event_id', null: false
- t.decimal 'donation', precision: 8, scale: 2, default: 0.0
- t.string 'role', default: 'volunteer', null: false
- t.string 'role_explanation', limit: 200
- t.string 'previous_contribution', limit: 250
- t.string 'address_line1', limit: 200
- t.string 'address_line2', limit: 200
- t.string 'city', limit: 50
- t.string 'state', limit: 50
- t.string 'zip_code', limit: 32
- t.string 'country_code', limit: 4
- t.string 'admin_notes', limit: 512
- t.boolean 'car_camping'
- t.string 'car_camping_explanation', limit: 200
- t.boolean 'agrees_to_terms'
- t.integer 'early_arrival_passes', default: 0, null: false
- t.integer 'late_departure_passes', default: 0, null: false
- t.text 'guests'
+ create_table "ticket_requests", force: :cascade do |t|
+ t.integer "adults", default: 1, null: false
+ t.integer "kids", default: 0, null: false
+ t.integer "cabins", default: 0, null: false
+ t.boolean "needs_assistance", default: false, null: false
+ t.string "notes", limit: 500
+ t.string "status", limit: 1, null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "user_id", null: false
+ t.decimal "special_price", precision: 8, scale: 2
+ t.integer "event_id", null: false
+ t.decimal "donation", precision: 8, scale: 2, default: "0.0"
+ t.string "role", default: "volunteer", null: false
+ t.string "role_explanation", limit: 200
+ t.string "previous_contribution", limit: 250
+ t.string "address_line1", limit: 200
+ t.string "address_line2", limit: 200
+ t.string "city", limit: 50
+ t.string "state", limit: 50
+ t.string "zip_code", limit: 32
+ t.string "country_code", limit: 4
+ t.string "admin_notes", limit: 512
+ t.boolean "car_camping"
+ t.string "car_camping_explanation", limit: 200
+ t.boolean "agrees_to_terms"
+ t.integer "early_arrival_passes", default: 0, null: false
+ t.integer "late_departure_passes", default: 0, null: false
+ t.text "guests"
end
- create_table 'time_slots', force: :cascade do |t|
- t.integer 'job_id', null: false
- t.datetime 'start_time', null: false
- t.datetime 'end_time', null: false
- t.integer 'slots', null: false
- t.datetime 'created_at'
- t.datetime 'updated_at'
+ create_table "time_slots", force: :cascade do |t|
+ t.bigint "job_id", null: false
+ t.datetime "start_time", precision: nil, null: false
+ t.datetime "end_time", precision: nil, null: false
+ t.integer "slots", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["job_id"], name: "index_time_slots_on_job_id"
end
- create_table 'users', force: :cascade do |t|
- t.string 'email', null: false
- t.string 'encrypted_password', null: false
- t.string 'reset_password_token'
- t.datetime 'reset_password_sent_at'
- t.datetime 'remember_created_at'
- t.integer 'sign_in_count', default: 0
- t.datetime 'current_sign_in_at'
- t.datetime 'last_sign_in_at'
- t.string 'current_sign_in_ip'
- t.string 'last_sign_in_ip'
- t.string 'confirmation_token'
- t.datetime 'confirmed_at'
- t.datetime 'confirmation_sent_at'
- t.string 'unconfirmed_email'
- t.integer 'failed_attempts', default: 0
- t.string 'unlock_token'
- t.datetime 'locked_at'
- t.datetime 'created_at'
- t.datetime 'updated_at'
- t.string 'name', limit: 70, null: false
- t.string 'authentication_token', limit: 64
+ create_table "users", force: :cascade do |t|
+ t.string "email", null: false
+ t.string "encrypted_password", null: false
+ t.string "reset_password_token"
+ t.datetime "reset_password_sent_at", precision: nil
+ t.datetime "remember_created_at", precision: nil
+ t.integer "sign_in_count", default: 0
+ t.datetime "current_sign_in_at", precision: nil
+ t.datetime "last_sign_in_at", precision: nil
+ t.string "current_sign_in_ip"
+ t.string "last_sign_in_ip"
+ t.string "confirmation_token"
+ t.datetime "confirmed_at", precision: nil
+ t.datetime "confirmation_sent_at", precision: nil
+ t.string "unconfirmed_email"
+ t.integer "failed_attempts", default: 0
+ t.string "unlock_token"
+ t.datetime "locked_at", precision: nil
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.string "name", limit: 70, null: false
+ t.string "authentication_token", limit: 64
+ t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
+ t.index ["email"], name: "index_users_on_email", unique: true
+ t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
+ t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
end
- add_index 'users', ['confirmation_token'], name: 'index_users_on_confirmation_token', unique: true, using: :btree
- add_index 'users', ['email'], name: 'index_users_on_email', unique: true, using: :btree
- add_index 'users', ['reset_password_token'], name: 'index_users_on_reset_password_token', unique: true, using: :btree
- add_index 'users', ['unlock_token'], name: 'index_users_on_unlock_token', unique: true, using: :btree
end
diff --git a/db/seeds.rb b/db/seeds.rb
new file mode 100755
index 00000000..cb958f08
--- /dev/null
+++ b/db/seeds.rb
@@ -0,0 +1,195 @@
+# frozen_string_literal: true
+
+# This file should ensure the existence of records required to run the application in every environment (production,
+# development, test). The code here should be idempotent so that it can be executed at any point in every environment.
+# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).
+#
+# Example:
+#
+# ["Action", "Comedy", "Drama", "Horror"].each do |genre_name|
+# MovieGenre.find_or_create_by!(name: genre_name)
+# end
+#
+
+require 'faker'
+require 'colorize'
+require_relative '../config/environment'
+
+module FnF
+ unless defined?(Seeds)
+ # rubocop: disable Rails/Output
+ class Seeds
+ unless defined?(SITE_ADMIN_PASSWORD)
+ SITE_ADMIN_PASSWORD = 'fubar'
+ SITE_ADMIN_EMAIL = 'site-admin@fnf.org'
+ DEFAULT_USER_COUNT = 10
+ DEFAULT_EVENT_COUNT = 2
+ HEADER_WIDTH = 100
+ end
+
+ attr_reader :user_count, :event_count
+ attr_accessor :users, :events, :site_admins, :ran
+
+ def initialize(user_count: DEFAULT_USER_COUNT, event_count: DEFAULT_EVENT_COUNT)
+ @user_count = user_count
+ @event_count = event_count
+ @users = []
+ @site_admins = []
+ @events = []
+ $stdout.sync = true
+ @ran = false
+ end
+
+ def run
+ return if ran?
+
+ create_site_admins
+ create_users
+ create_events
+ self.ran = true
+ end
+
+ def ran?
+ ran
+ end
+
+ private
+
+ def create_site_admins
+ header 'Creating Site Admins...'
+
+ site_admin_user = User.where(email: SITE_ADMIN_EMAIL).first
+
+ if site_admin_user && !site_admin_user.site_admin?
+ site_admin_user.destroy
+ end
+
+ site_admin_user ||= User.create!(
+ email: SITE_ADMIN_EMAIL,
+ password: SITE_ADMIN_PASSWORD,
+ password_confirmation: SITE_ADMIN_PASSWORD,
+ confirmed_at: Time.zone.now,
+ name: 'Site Administrator'
+ )
+
+ SiteAdmin.create!(user: site_admin_user) unless site_admin_user.site_admin?
+
+ @site_admins << site_admin_user
+
+ register_user(site_admin_user, new_user: true, password: SITE_ADMIN_PASSWORD)
+ end
+
+ def create_users
+ current_user_count = User.count
+ header "Current User Count: #{current_user_count}"
+
+ current_users = User.all.to_a
+
+ (0..user_count).to_a.each do |index|
+ password = Faker::Internet.password
+ new_user = true
+
+ if current_users[index]
+ password = nil
+ new_user = false
+ end
+
+ user = current_users[index] || User.create!(
+ email: Faker::Internet.email,
+ password:,
+ password_confirmation: password,
+ confirmed_at: Time.zone.now,
+ name: "#{Faker::Name.name}, The #{(index + 1).ordinalize}."
+ )
+
+ register_user(user, new_user:, password:)
+ end
+
+ header "Total Users Now: #{User.count}, Shown Users: #{users.size}"
+ end
+
+ def register_user(user, new_user: false, password: nil)
+ @users << user unless users.include?(user)
+
+ puts " New User: #{new_user ? 'Yes'.colorize(:light_green) : 'No'.colorize(:light_red)}"
+ puts " Name: #{user.name.colorize(:light_green)}"
+ puts " Email: #{user.email.colorize(:light_green)}"
+ puts " Password #{password ? password.colorize(:light_blue) : 'Unknown'.colorize(:light_red)}"
+ puts " Site Admin? #{user.site_admin? ? 'Yes'.colorize(:light_green) : 'No'.colorize(:light_red)}"
+ puts " Event Admin? #{user.events_administrated.empty? ? 'No'.colorize(:light_red) : 'Yes'.colorize(:light_green)}"
+ puts ' ——————————————————————————————————————————————————————————————— '
+ end
+
+ def create_events
+ header "Events (Total Count: #{Event.count})"
+
+ self.events = Event.all.to_a || []
+ (0..event_count).to_a.each do |index|
+ start_time = (Time.zone.today + Random.rand(2..3).months).to_time
+
+ event = events[index] || Event.create!(
+ adult_ticket_price: Faker::Commerce.price(range: 100...200),
+ allow_donations: true,
+ allow_financial_assistance: true,
+ cabin_price: Faker::Commerce.price(range: 150..300),
+ early_arrival_price: 25.00,
+ end_time: start_time + 3.days,
+ require_mailing_address: true,
+ kid_ticket_price: Faker::Commerce.price(range: 50...100),
+ late_departure_price: Faker::Commerce.price(range: 15...25),
+ max_adult_tickets_per_request: 4,
+ max_cabin_requests: 2,
+ max_cabins_per_request: 1,
+ max_kid_tickets_per_request: 4,
+ name: "FnF Event: #{Faker::Company.name}",
+ start_time:,
+ venue: Faker::Address.street_address,
+ ticket_sales_start_time: start_time - 1.month,
+ ticket_sales_end_time: start_time - 1.month + 1.week,
+ ticket_requests_end_time: start_time - 1.day,
+ tickets_require_approval: true
+ )
+
+ @events << event unless events.include?(event)
+
+ if event.admins.count.zero?
+ users.sample(Random.rand(2..4)).each do |u|
+ event.make_admin(u)
+ end
+ end
+
+ print_event(event)
+ end
+ end
+
+ def print_event(event)
+ puts
+ puts " Name: #{event.name.to_s.colorize(color: :magenta, mode: :bold)}"
+ puts " Start Time: #{event.start_time.to_s.colorize(:light_yellow)}"
+ puts " End Time: #{event.end_time.to_s.colorize(:light_yellow)}"
+ puts " Ticket Sales Start Time: #{event.ticket_sales_start_time.to_s.colorize(:light_yellow)}"
+ puts " Ticket Sales End Time: #{event.end_time.to_s.colorize(:light_yellow)}"
+ puts
+ puts ' Event Admins: '.colorize(background: :green)
+
+ event.reload.event_admins.each do |event_admin|
+ puts " #{event_admin.user.name.colorize(:light_green)} <#{event_admin.user.email.colorize(:light_green)}>"
+ end
+ end
+
+ def header(string)
+ puts "\n#{format(" %-#{HEADER_WIDTH}.#{HEADER_WIDTH}s".colorize(background: :light_blue), string)}"
+ end
+ end
+ end
+
+ class << self
+ attr_accessor :seeds
+ end
+
+ self.seeds ||= Seeds.new
+end
+
+FnF.seeds.run
+
+# rubocop: enable Rails/Output
diff --git a/db/structure.sql b/db/structure.sql
index b7b344eb..bcc364d4 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -1,10 +1,3 @@
---
--- PostgreSQL database dump
---
-
--- Dumped from database version 14.7 (Homebrew)
--- Dumped by pg_dump version 14.7 (Homebrew)
-
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
@@ -20,21 +13,33 @@ SET default_tablespace = '';
SET default_table_access_method = heap;
+--
+-- Name: ar_internal_metadata; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.ar_internal_metadata (
+ key character varying NOT NULL,
+ value character varying,
+ created_at timestamp(6) without time zone NOT NULL,
+ updated_at timestamp(6) without time zone NOT NULL
+);
+
+
--
-- Name: eald_payments; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.eald_payments (
- id integer NOT NULL,
- event_id integer,
+ id bigint NOT NULL,
+ event_id bigint,
stripe_charge_id character varying NOT NULL,
amount_charged_cents integer NOT NULL,
name character varying(255) NOT NULL,
email character varying(255) NOT NULL,
early_arrival_passes integer DEFAULT 0 NOT NULL,
late_departure_passes integer DEFAULT 0 NOT NULL,
- created_at timestamp without time zone,
- updated_at timestamp without time zone
+ created_at timestamp(6) without time zone NOT NULL,
+ updated_at timestamp(6) without time zone NOT NULL
);
@@ -43,7 +48,6 @@ CREATE TABLE public.eald_payments (
--
CREATE SEQUENCE public.eald_payments_id_seq
- AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
@@ -63,11 +67,11 @@ ALTER SEQUENCE public.eald_payments_id_seq OWNED BY public.eald_payments.id;
--
CREATE TABLE public.event_admins (
- id integer NOT NULL,
- event_id integer,
- user_id integer,
- created_at timestamp without time zone,
- updated_at timestamp without time zone
+ id bigint NOT NULL,
+ event_id bigint,
+ user_id bigint,
+ created_at timestamp(6) without time zone NOT NULL,
+ updated_at timestamp(6) without time zone NOT NULL
);
@@ -76,7 +80,6 @@ CREATE TABLE public.event_admins (
--
CREATE SEQUENCE public.event_admins_id_seq
- AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
@@ -96,12 +99,12 @@ ALTER SEQUENCE public.event_admins_id_seq OWNED BY public.event_admins.id;
--
CREATE TABLE public.events (
- id integer NOT NULL,
+ id bigint NOT NULL,
name character varying,
start_time timestamp without time zone,
end_time timestamp without time zone,
- created_at timestamp without time zone,
- updated_at timestamp without time zone,
+ created_at timestamp(6) without time zone NOT NULL,
+ updated_at timestamp(6) without time zone NOT NULL,
adult_ticket_price numeric(8,2),
kid_ticket_price numeric(8,2),
cabin_price numeric(8,2),
@@ -117,8 +120,8 @@ CREATE TABLE public.events (
ticket_sales_start_time timestamp without time zone,
ticket_sales_end_time timestamp without time zone,
ticket_requests_end_time timestamp without time zone,
- early_arrival_price numeric(8,2) DEFAULT 0,
- late_departure_price numeric(8,2) DEFAULT 0
+ early_arrival_price numeric(8,2) DEFAULT 0.0,
+ late_departure_price numeric(8,2) DEFAULT 0.0
);
@@ -127,7 +130,6 @@ CREATE TABLE public.events (
--
CREATE SEQUENCE public.events_id_seq
- AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
@@ -147,12 +149,12 @@ ALTER SEQUENCE public.events_id_seq OWNED BY public.events.id;
--
CREATE TABLE public.jobs (
- id integer NOT NULL,
- event_id integer NOT NULL,
+ id bigint NOT NULL,
+ event_id bigint NOT NULL,
name character varying(100) NOT NULL,
description character varying(512) NOT NULL,
- created_at timestamp without time zone,
- updated_at timestamp without time zone
+ created_at timestamp(6) without time zone NOT NULL,
+ updated_at timestamp(6) without time zone NOT NULL
);
@@ -161,7 +163,6 @@ CREATE TABLE public.jobs (
--
CREATE SEQUENCE public.jobs_id_seq
- AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
@@ -181,12 +182,12 @@ ALTER SEQUENCE public.jobs_id_seq OWNED BY public.jobs.id;
--
CREATE TABLE public.payments (
- id integer NOT NULL,
+ id bigint NOT NULL,
ticket_request_id integer NOT NULL,
stripe_charge_id character varying(255),
status character varying(1) DEFAULT 'P'::character varying NOT NULL,
- created_at timestamp without time zone,
- updated_at timestamp without time zone,
+ created_at timestamp(6) without time zone NOT NULL,
+ updated_at timestamp(6) without time zone NOT NULL,
explanation character varying
);
@@ -196,7 +197,6 @@ CREATE TABLE public.payments (
--
CREATE SEQUENCE public.payments_id_seq
- AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
@@ -216,13 +216,13 @@ ALTER SEQUENCE public.payments_id_seq OWNED BY public.payments.id;
--
CREATE TABLE public.price_rules (
- id integer NOT NULL,
+ id bigint NOT NULL,
type character varying,
- event_id integer,
+ event_id bigint,
price numeric(8,2),
trigger_value integer,
- created_at timestamp without time zone,
- updated_at timestamp without time zone
+ created_at timestamp(6) without time zone NOT NULL,
+ updated_at timestamp(6) without time zone NOT NULL
);
@@ -231,7 +231,6 @@ CREATE TABLE public.price_rules (
--
CREATE SEQUENCE public.price_rules_id_seq
- AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
@@ -260,12 +259,12 @@ CREATE TABLE public.schema_migrations (
--
CREATE TABLE public.shifts (
- id integer NOT NULL,
- time_slot_id integer NOT NULL,
- user_id integer NOT NULL,
+ id bigint NOT NULL,
+ time_slot_id bigint NOT NULL,
+ user_id bigint NOT NULL,
name character varying(70),
- created_at timestamp without time zone,
- updated_at timestamp without time zone
+ created_at timestamp(6) without time zone NOT NULL,
+ updated_at timestamp(6) without time zone NOT NULL
);
@@ -274,7 +273,6 @@ CREATE TABLE public.shifts (
--
CREATE SEQUENCE public.shifts_id_seq
- AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
@@ -294,10 +292,10 @@ ALTER SEQUENCE public.shifts_id_seq OWNED BY public.shifts.id;
--
CREATE TABLE public.site_admins (
- id integer NOT NULL,
+ id bigint NOT NULL,
user_id integer NOT NULL,
- created_at timestamp without time zone,
- updated_at timestamp without time zone
+ created_at timestamp(6) without time zone NOT NULL,
+ updated_at timestamp(6) without time zone NOT NULL
);
@@ -306,7 +304,6 @@ CREATE TABLE public.site_admins (
--
CREATE SEQUENCE public.site_admins_id_seq
- AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
@@ -326,19 +323,19 @@ ALTER SEQUENCE public.site_admins_id_seq OWNED BY public.site_admins.id;
--
CREATE TABLE public.ticket_requests (
- id integer NOT NULL,
+ id bigint NOT NULL,
adults integer DEFAULT 1 NOT NULL,
kids integer DEFAULT 0 NOT NULL,
cabins integer DEFAULT 0 NOT NULL,
needs_assistance boolean DEFAULT false NOT NULL,
notes character varying(500),
status character varying(1) NOT NULL,
- created_at timestamp without time zone,
- updated_at timestamp without time zone,
+ created_at timestamp(6) without time zone NOT NULL,
+ updated_at timestamp(6) without time zone NOT NULL,
user_id integer NOT NULL,
special_price numeric(8,2),
event_id integer NOT NULL,
- donation numeric(8,2) DEFAULT 0,
+ donation numeric(8,2) DEFAULT 0.0,
role character varying DEFAULT 'volunteer'::character varying NOT NULL,
role_explanation character varying(200),
previous_contribution character varying(250),
@@ -363,7 +360,6 @@ CREATE TABLE public.ticket_requests (
--
CREATE SEQUENCE public.ticket_requests_id_seq
- AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
@@ -383,13 +379,13 @@ ALTER SEQUENCE public.ticket_requests_id_seq OWNED BY public.ticket_requests.id;
--
CREATE TABLE public.time_slots (
- id integer NOT NULL,
- job_id integer NOT NULL,
+ id bigint NOT NULL,
+ job_id bigint NOT NULL,
start_time timestamp without time zone NOT NULL,
end_time timestamp without time zone NOT NULL,
slots integer NOT NULL,
- created_at timestamp without time zone,
- updated_at timestamp without time zone
+ created_at timestamp(6) without time zone NOT NULL,
+ updated_at timestamp(6) without time zone NOT NULL
);
@@ -398,7 +394,6 @@ CREATE TABLE public.time_slots (
--
CREATE SEQUENCE public.time_slots_id_seq
- AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
@@ -418,7 +413,7 @@ ALTER SEQUENCE public.time_slots_id_seq OWNED BY public.time_slots.id;
--
CREATE TABLE public.users (
- id integer NOT NULL,
+ id bigint NOT NULL,
email character varying NOT NULL,
encrypted_password character varying NOT NULL,
reset_password_token character varying,
@@ -436,10 +431,12 @@ CREATE TABLE public.users (
failed_attempts integer DEFAULT 0,
unlock_token character varying,
locked_at timestamp without time zone,
- created_at timestamp without time zone,
- updated_at timestamp without time zone,
+ created_at timestamp(6) without time zone NOT NULL,
+ updated_at timestamp(6) without time zone NOT NULL,
name character varying(70) NOT NULL,
- authentication_token character varying(64)
+ authentication_token character varying(64),
+ first text,
+ last text
);
@@ -448,7 +445,6 @@ CREATE TABLE public.users (
--
CREATE SEQUENCE public.users_id_seq
- AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
@@ -540,6 +536,14 @@ ALTER TABLE ONLY public.time_slots ALTER COLUMN id SET DEFAULT nextval('public.t
ALTER TABLE ONLY public.users ALTER COLUMN id SET DEFAULT nextval('public.users_id_seq'::regclass);
+--
+-- Name: ar_internal_metadata ar_internal_metadata_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.ar_internal_metadata
+ ADD CONSTRAINT ar_internal_metadata_pkey PRIMARY KEY (key);
+
+
--
-- Name: eald_payments eald_payments_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@@ -588,6 +592,14 @@ ALTER TABLE ONLY public.price_rules
ADD CONSTRAINT price_rules_pkey PRIMARY KEY (id);
+--
+-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.schema_migrations
+ ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
+
+
--
-- Name: shifts shifts_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@@ -628,6 +640,13 @@ ALTER TABLE ONLY public.users
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
+--
+-- Name: index_eald_payments_on_event_id; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_eald_payments_on_event_id ON public.eald_payments USING btree (event_id);
+
+
--
-- Name: index_event_admins_on_event_id_and_user_id; Type: INDEX; Schema: public; Owner: -
--
@@ -643,142 +662,121 @@ CREATE INDEX index_event_admins_on_user_id ON public.event_admins USING btree (u
--
--- Name: index_price_rules_on_event_id; Type: INDEX; Schema: public; Owner: -
+-- Name: index_jobs_on_event_id; Type: INDEX; Schema: public; Owner: -
--
-CREATE INDEX index_price_rules_on_event_id ON public.price_rules USING btree (event_id);
+CREATE INDEX index_jobs_on_event_id ON public.jobs USING btree (event_id);
--
--- Name: index_users_on_confirmation_token; Type: INDEX; Schema: public; Owner: -
+-- Name: index_price_rules_on_event_id; Type: INDEX; Schema: public; Owner: -
--
-CREATE UNIQUE INDEX index_users_on_confirmation_token ON public.users USING btree (confirmation_token);
+CREATE INDEX index_price_rules_on_event_id ON public.price_rules USING btree (event_id);
--
--- Name: index_users_on_email; Type: INDEX; Schema: public; Owner: -
+-- Name: index_shifts_on_time_slot_id; Type: INDEX; Schema: public; Owner: -
--
-CREATE UNIQUE INDEX index_users_on_email ON public.users USING btree (email);
+CREATE INDEX index_shifts_on_time_slot_id ON public.shifts USING btree (time_slot_id);
--
--- Name: index_users_on_reset_password_token; Type: INDEX; Schema: public; Owner: -
+-- Name: index_shifts_on_user_id; Type: INDEX; Schema: public; Owner: -
--
-CREATE UNIQUE INDEX index_users_on_reset_password_token ON public.users USING btree (reset_password_token);
+CREATE INDEX index_shifts_on_user_id ON public.shifts USING btree (user_id);
--
--- Name: index_users_on_unlock_token; Type: INDEX; Schema: public; Owner: -
+-- Name: index_time_slots_on_job_id; Type: INDEX; Schema: public; Owner: -
--
-CREATE UNIQUE INDEX index_users_on_unlock_token ON public.users USING btree (unlock_token);
+CREATE INDEX index_time_slots_on_job_id ON public.time_slots USING btree (job_id);
--
--- Name: unique_schema_migrations; Type: INDEX; Schema: public; Owner: -
+-- Name: index_users_on_confirmation_token; Type: INDEX; Schema: public; Owner: -
--
-CREATE UNIQUE INDEX unique_schema_migrations ON public.schema_migrations USING btree (version);
+CREATE UNIQUE INDEX index_users_on_confirmation_token ON public.users USING btree (confirmation_token);
--
--- PostgreSQL database dump complete
+-- Name: index_users_on_email; Type: INDEX; Schema: public; Owner: -
--
-SET search_path TO "$user", public;
-
-INSERT INTO schema_migrations (version) VALUES ('20130210233501');
-
-INSERT INTO schema_migrations (version) VALUES ('20130223202959');
-
-INSERT INTO schema_migrations (version) VALUES ('20130223204913');
-
-INSERT INTO schema_migrations (version) VALUES ('20130224204644');
-
-INSERT INTO schema_migrations (version) VALUES ('20130225033247');
-
-INSERT INTO schema_migrations (version) VALUES ('20130226010856');
-
-INSERT INTO schema_migrations (version) VALUES ('20130226221916');
-
-INSERT INTO schema_migrations (version) VALUES ('20130228052958');
-
-INSERT INTO schema_migrations (version) VALUES ('20130304020307');
-
-INSERT INTO schema_migrations (version) VALUES ('20130304021739');
-
-INSERT INTO schema_migrations (version) VALUES ('20130304022508');
-
-INSERT INTO schema_migrations (version) VALUES ('20130311213508');
-
-INSERT INTO schema_migrations (version) VALUES ('20130325024448');
-
-INSERT INTO schema_migrations (version) VALUES ('20130325051758');
-
-INSERT INTO schema_migrations (version) VALUES ('20130425052112');
-
-INSERT INTO schema_migrations (version) VALUES ('20130427054403');
-
-INSERT INTO schema_migrations (version) VALUES ('20130507051822');
-
-INSERT INTO schema_migrations (version) VALUES ('20130509021514');
-
-INSERT INTO schema_migrations (version) VALUES ('20130514035306');
-
-INSERT INTO schema_migrations (version) VALUES ('20130616002401');
-
-INSERT INTO schema_migrations (version) VALUES ('20130628042018');
-
-INSERT INTO schema_migrations (version) VALUES ('20130628050717');
-
-INSERT INTO schema_migrations (version) VALUES ('20130701042655');
-
-INSERT INTO schema_migrations (version) VALUES ('20130701045629');
-
-INSERT INTO schema_migrations (version) VALUES ('20130701054452');
-
-INSERT INTO schema_migrations (version) VALUES ('20130702041357');
-
-INSERT INTO schema_migrations (version) VALUES ('20130707204929');
-
-INSERT INTO schema_migrations (version) VALUES ('20130707222903');
-
-INSERT INTO schema_migrations (version) VALUES ('20130803212458');
-
-INSERT INTO schema_migrations (version) VALUES ('20140428041744');
-
-INSERT INTO schema_migrations (version) VALUES ('20140428045329');
-
-INSERT INTO schema_migrations (version) VALUES ('20140515053804');
-
-INSERT INTO schema_migrations (version) VALUES ('20140515054433');
-
-INSERT INTO schema_migrations (version) VALUES ('20140605034909');
-
-INSERT INTO schema_migrations (version) VALUES ('20140605045004');
-
-INSERT INTO schema_migrations (version) VALUES ('20140605052627');
+CREATE UNIQUE INDEX index_users_on_email ON public.users USING btree (email);
-INSERT INTO schema_migrations (version) VALUES ('20140605053705');
-INSERT INTO schema_migrations (version) VALUES ('20140605060026');
+--
+-- Name: index_users_on_reset_password_token; Type: INDEX; Schema: public; Owner: -
+--
-INSERT INTO schema_migrations (version) VALUES ('20140611044708');
+CREATE UNIQUE INDEX index_users_on_reset_password_token ON public.users USING btree (reset_password_token);
-INSERT INTO schema_migrations (version) VALUES ('20140611051614');
-INSERT INTO schema_migrations (version) VALUES ('20140616024138');
+--
+-- Name: index_users_on_unlock_token; Type: INDEX; Schema: public; Owner: -
+--
-INSERT INTO schema_migrations (version) VALUES ('20140616030905');
+CREATE UNIQUE INDEX index_users_on_unlock_token ON public.users USING btree (unlock_token);
-INSERT INTO schema_migrations (version) VALUES ('20140706232217');
-INSERT INTO schema_migrations (version) VALUES ('20150609064608');
+--
+-- PostgreSQL database dump complete
+--
-INSERT INTO schema_migrations (version) VALUES ('20160611234315');
+SET search_path TO "$user", public;
-INSERT INTO schema_migrations (version) VALUES ('20180527021019');
+INSERT INTO "schema_migrations" (version) VALUES
+('20240418004856'),
+('20240311182346'),
+('20180527021019'),
+('20160611234315'),
+('20150609064608'),
+('20140706232217'),
+('20140616030905'),
+('20140616024138'),
+('20140611051614'),
+('20140611044708'),
+('20140605060026'),
+('20140605053705'),
+('20140605052627'),
+('20140605045004'),
+('20140605034909'),
+('20140515054433'),
+('20140515053804'),
+('20140428045329'),
+('20140428041744'),
+('20130803212458'),
+('20130707222903'),
+('20130707204929'),
+('20130702041357'),
+('20130701054452'),
+('20130701045629'),
+('20130701042655'),
+('20130628050717'),
+('20130628042018'),
+('20130616002401'),
+('20130514035306'),
+('20130509021514'),
+('20130507051822'),
+('20130427054403'),
+('20130425052112'),
+('20130325051758'),
+('20130325024448'),
+('20130311213508'),
+('20130304022508'),
+('20130304021739'),
+('20130304020307'),
+('20130228052958'),
+('20130226221916'),
+('20130226010856'),
+('20130225033247'),
+('20130224204644'),
+('20130223204913'),
+('20130223202959'),
+('20130210233501');
diff --git a/development/config/.psqlrc b/development/config/.psqlrc
new file mode 100644
index 00000000..2538479a
--- /dev/null
+++ b/development/config/.psqlrc
@@ -0,0 +1,67 @@
+-- vim: set filetsype=sql
+-- don't output commands as we run them
+
+-- set how to output numbers (this turns on thousdand separators)
+\set PSQLRC_VERSION '1.0.2'
+\set PSQLRC_RELEASED 'Wed Feb 8 09:17:23 PST 2023'
+\set QUIET 1
+\encoding unicode
+\timing 0
+
+-- Autocomplete keywords (like SELECT) in upper-case, even if you started
+-- typing them in lower case.
+\set COMP_KEYWORD_CASE upper
+
+-- If a command is run more than once in a row, only store it once in the history.
+\set HISTCONTROL ignoredups
+
+-- Use a separate history file per-database.
+\set HISTFILE ~/.psql_history-:DBNAME
+
+-- Use automatic horizontal or vertical format depending on the data and terminal width.
+-- You can always use horizontal with "\x off" or always vertical with "\x on".
+-- Finally, you can use macros ":nowrap" and ":wrap" to either truncate long output strings at the
+-- terminal boundary, or wrap them if they don't fit.
+\x auto
+\set nowrap '\\! tput rmam'
+\set wrap '\\! tput smam'
+
+-- These macros load additional macros that map to various administrative queries about the
+-- database or individual tables.
+\set m '\\i ~/.psqlrc-macros'
+\set macros '\\i ~/.psqlrc-macros'
+
+-- Verbose error reports.
+\set VERBOSITY verbose
+
+\pset border 0
+\pset tuples_only 1
+
+\echo '\033[0;34m'
+\echo -n '❯ PostgreSQL Client Version: '
+\! psql --version | cut -d ' ' -f 3
+\echo -n '\033[0;33m'
+SELECT '❯ PostgreSQL Server Version: ' || (SELECT regexp_replace(version(), 'PostgreSQL ([\d.]+) .*', '\1', 'g') as current_pg_version);
+\echo -n '\033[0;32m'
+\echo '┌───────────────────────────────────────────────────────────────────────────────┐'
+\echo '│ To load and view macros, type \033[0;34m:macros\033[0;32m (or :m for short) │'
+\echo '│ To truncate wide results type \033[0;34m:nowrap\033[0;32m, or :wrap otherwise │'
+\echo '│ To layout query results vertically, type \033[0;34m"\\x on|off|auto"\033[0;0m │'
+\echo '└───────────────────────────────────────────────────────────────────────────────┘'
+\echo '\033[0;0m'
+
+\pset border 3
+\pset columns 0
+\pset format aligned
+\pset linestyle unicode
+\pset null ''
+\pset pager 0
+\pset tuples_only 1
+\timing 1
+\pset tuples_only 0
+
+SET search_path TO public,extensions;
+\set QUIET 0
+-- New Prompt, more compact, and works with back editing using readlines
+\set PROMPT1 '%[%033[0;30;33m%]%[%033[0;43;30m%] %`date "+%H:%M:%S%p"` %033[0;30;33m%]%033[0;43;37m%]%[%033[0;0;0m%]━%[%033[0;30;32m%]%033[0;42;30m%] %n@%M:%> %033[0;32m%%[%033[3;30;96m%]━%[%033[3;30;106m%]% %[%033[3;30;106m%]%/%x %033[0;96m%%[%033[0m%]\n❯ '
+\set PROMPT2 '%[%033[0;30;33m%] ⤷ %l%[%033[0m% ❯ '
\ No newline at end of file
diff --git a/development/config/.psqlrc.macros b/development/config/.psqlrc.macros
new file mode 100644
index 00000000..6c9a5578
--- /dev/null
+++ b/development/config/.psqlrc.macros
@@ -0,0 +1,136 @@
+-- vim: ft=sql
+\set QUIET 1
+\timing 0
+\echo '┌───────────────────────────────────────────────────────────────────────────┐'
+\echo '│ Loading shortcuts │'
+\echo '└───────────────────────────────────────────────────────────────────────────┘'
+\echo
+\pset border 0
+\set VERBOSITY verbose
+\pset tuples_only 1
+
+\echo 'Type :version — to see the PostgreSQL version.'
+\set version 'SELECT version();'
+
+\echo 'Type :extensions — to see the available extensions.'
+\set extensions 'select * from pg_available_extensions;'
+
+\echo 'Type :running — to see all currently executing queries'
+\set running 'select pid,client_addr,client_port,wait_event_type,wait_event,state, to_char((now() - query_start)::time, ''HH24:MI:SS'') as duration, substring(regexp_replace(substring(query, 0, 200), E''[\\n\\r\\\\s]+'', '' '', ''g'' ), 0, 120) as sql from pg_stat_activity where state != ''idle'' order by query_start asc;'
+
+\echo 'Type :vacuums — to see the currently running vacuums'
+\set vacuums 'select pid,client_addr,client_port,wait_event_type,wait_event,state, to_char(now()::time - query_start::time, ''HH24:MI:SS'') as duration, substring(regexp_replace(substring(query, 0, 200), E''[\\n\\r\\\\s]+'', '' '', ''g'' ), 0, 120) as sql from pg_stat_activity where state != ''idle'' and query like ''%vacuum%'' order by query_start asc;'
+
+\echo 'Type :vacuuming — to see the progress of the currently running vacuums'
+\set vacuuming 'select pa.pid, pa.state, pv.phase, to_char(pv.heap_blks_scanned * 100.0 / pv.heap_blks_total, ''999999999.99'')as completed_pct, to_char((now() - query_start)::time, ''HH24:MI:SS'') as duration, max_dead_tuples - num_dead_tuples, substring(regexp_replace(substring(query, 0, 200), E''[\n\r\\s]+'', '' '', ''g''), 0, 120) as sql from pg_stat_activity pa, pg_stat_progress_vacuum pv where pv.pid = pa.pid and query ilike ''%vacuum%'' and state != ''idle'' and query_start is not null order by query_start asc;'
+
+\echo 'Type :locks — to see all current database locks'
+\set locks 'select pid,client_addr,client_port,wait_event_type,wait_event,state, to_char(now()::time - query_start::time, ''HH24:MI:SS'') as duration, substring(regexp_replace(substring(query, 0, 200), E''[\\n\\r\\\\s]+'', '' '', ''g'' ), 0, 120) as sql from pg_stat_activity where state != ''idle'' and ( wait_event is not null or wait_event_type is not null) order by duration desc;'
+
+\echo 'Type :locks_detail - to see all current database locks with detail'
+\set locks_detail 'WITH RECURSIVE activity AS (SELECT pg_blocking_pids(a.pid) blocked_by, a.*, (CASE WHEN (SELECT 1 FROM pg_locks AS l WHERE locktype = ''advisory'' AND l.pid = a.pid) = 1 THEN TRUE ELSE FALSE END) AS has_advisory, age(clock_timestamp(), a.xact_start)::interval(0) AS tx_age, age(clock_timestamp(), a.state_change)::interval(0) AS state_age FROM pg_stat_activity AS a WHERE state IS DISTINCT FROM ''idle'' ), blockers AS (SELECT array_agg(DISTINCT c ORDER BY c) AS pids FROM (SELECT unnest(blocked_by) FROM activity) AS dt(c) ), tree AS (SELECT activity.*, 1 AS level, activity.pid AS top_blocker_pid, array[activity.pid] AS path, array[activity.pid]::int[] AS all_blockers_above FROM activity, blockers WHERE array[pid] <@ blockers.pids AND blocked_by = ''{}''::int[] UNION ALL SELECT activity.*, tree.level + 1 AS level, tree.top_blocker_pid, path || array[activity.pid] AS path, tree.all_blockers_above || array_agg(activity.pid) over () AS all_blockers_above FROM activity, tree WHERE NOT array[activity.pid] <@ tree.all_blockers_above AND activity.blocked_by <> ''{}''::int[] AND activity.blocked_by <@ tree.all_blockers_above ) SELECT pid, blocked_by, tx_age, state_age, backend_xid AS xid, backend_xmin AS xmin, replace(state, ''idle in transaction'', ''idletx'') AS state, client_addr, usename, wait_event_type || '':'' || wait_event AS wait, (SELECT COUNT(distinct t1.pid) FROM tree t1 WHERE array[tree.pid] <@ t1.path AND t1.pid <> tree.pid) AS blkd, has_advisory, format(''%s %s%s'', lpad(''['' || tree.pid::text || '']'', 7, '' ''), repeat(''.'', level - 1) || CASE WHEN level > 1 THEN '' '' END, left(query, 1000) ) AS query FROM tree ORDER BY top_blocker_pid, level, tree.pid;'
+
+\echo '——————————————————————————————————————————————————————————————————————————'
+
+\echo 'Type :dbage — to see frozen transaction ID age by database'
+\set dbage 'SELECT datname as database, age(datfrozenxid) as age, current_setting(''autovacuum_freeze_max_age'') as autovacuum_freeze_max_age FROM pg_database ORDER BY 2 DESC;'
+
+\echo 'Type :oldest LIMIT;'
+\echo ' — see top LIMIT tables with the oldest TX wraparound XID'
+
+\set oldest 'SELECT c.oid::regclass, age(c.relfrozenxid), pg_size_pretty(pg_total_relation_size(c.oid)) FROM pg_class c JOIN pg_namespace n on c.relnamespace = n.oid WHERE relkind IN (''r'', ''t'', ''m'') AND n.nspname NOT IN (''pg_toast'') ORDER BY 2 DESC LIMIT '
+
+\echo '——————————————————————————————————————————————————————————————————————————'
+
+\echo 'Type :top20 — to see top 20 time-consuming queries (shortened)'
+\set top20 'select to_char(total_exec_time / 1000.0, ''999,999,999'') total_secs,to_char(total_exec_time / calls / 1000, ''999,999.99'') as average_secs, calls, rows, substring(regexp_replace(query, E''[\\n\\r\\\\s]+'', '' '', ''g''), 0, 140) as sql from pg_stat_statements order by total_exec_time desc limit 20;'
+
+\echo 'Type :top LIMIT; — to see top LIMIT time-consuming queries (shortened)'
+\set top 'select to_char(total_exec_time / 1000.0, ''999,999,999'') total_secs,to_char(total_exec_time / calls / 1000, ''999,999.99'') as average_secs, calls, rows, substring(regexp_replace(query, E''[\\n\\r\\\\s]+'', '' '', ''g''), 0, 140) as sql from pg_stat_statements order by total_exec_time desc limit '
+
+\echo 'Type :top30full — to see top 30 time-consuming queries (full)'
+\set top30full 'select to_char(total_exec_time / 1000.0, ''999,999,999'') total_secs, to_char(total_exec_time / calls / 1000, ''999,999.99'') as average_secs, calls, rows, substring(regexp_replace(query, E''[\\n\\r\\\\s]+'', '' '', ''g'' ), 0, 1000) as sql from pg_stat_statements order by total_exec_time desc limit 30;'
+\echo '——————————————————————————————————————————————————————————————————————————'
+
+\echo 'Type :st1 — to set statement timeout to 1 hour'
+\set st1 'set statement_timeout=''1h'';'
+
+\echo 'Type :st10 — to set statement timeout to 10 hours'
+\set st10 'set statement_timeout=''10h'';'
+
+\echo '——————————————————————————————————————————————————————————————————————————'
+
+\echo 'Type :unused — to see unused indexes in this database'
+\set unused 'SELECT schemaname || ''.'' || relname AS table, indexrelname AS index, pg_size_pretty(pg_relation_size(i.indexrelid)) AS index_size, idx_scan as index_scans FROM pg_stat_user_indexes ui JOIN pg_index i ON ui.indexrelid = i.indexrelid WHERE NOT indisunique AND idx_scan < 50 AND pg_relation_size(relid) > 5 * 8192 ORDER BY pg_relation_size(i.indexrelid) / nullif(idx_scan, 0) DESC NULLS FIRST, pg_relation_size(i.indexrelid) DESC;'
+
+\echo 'Type :bigtables — to see the biggest tables in this database'
+\set bigtables ' SELECT nspname || ''.'' || relname AS "relation", pg_size_pretty(pg_relation_size(C.oid)) AS "size" FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) WHERE nspname NOT IN (''pg_catalog'', ''information_schema'') and C.relkind in ( ''t'', ''r'' ) and nspname not in (''pg_toast'') ORDER BY pg_relation_size(C.oid) DESC LIMIT 20;'
+
+\echo 'Type :bigindexes — to see the biggest indexes in this database'
+\set bigindexes ' SELECT nspname || ''.'' || relname AS "relation", pg_size_pretty(pg_relation_size(C.oid)) AS "size" FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) WHERE nspname NOT IN (''pg_catalog'', ''information_schema'') and C.relkind = ''i'' ORDER BY pg_relation_size(C.oid) DESC LIMIT 20;'
+
+\echo 'Type :bigrels — to see the largest tables including indexes'
+\set bigrels ' SELECT nspname || ''.'' || relname AS "relation", pg_size_pretty(pg_total_relation_size(C.oid)) AS "total_size" FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) WHERE nspname NOT IN (''pg_catalog'', ''information_schema'') AND C.relkind <> ''i'' AND nspname !~ ''^pg_toast'' ORDER BY pg_total_relation_size(C.oid) DESC LIMIT 20;'
+
+\echo 'Type :bigitems — to see the biggest public tables in this database with indexes'
+\set bigitems 'SELECT nspname || ''.'' || relname AS "relation", pg_size_pretty(pg_relation_size(C.oid)) AS "size" FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) WHERE nspname IN (''public'') ORDER BY pg_relation_size(C.oid) DESC LIMIT 10;'
+
+\echo 'Type :dbsizes — to see the sizes of the databases'
+\set dbsizes ' SELECT d.datname AS Name, pg_catalog.pg_get_userbyid(d.datdba) AS Owner, CASE WHEN pg_catalog.has_database_privilege(d.datname, ''CONNECT'') THEN pg_catalog.pg_size_pretty(pg_catalog.pg_database_size(d.datname)) ELSE ''No Access'' END AS SIZE FROM pg_catalog.pg_database d ORDER BY CASE WHEN pg_catalog.has_database_privilege(d.datname, ''CONNECT'') THEN pg_catalog.pg_database_size(d.datname) ELSE NULL END DESC nulls first LIMIT 20;'
+
+\echo 'Type :hitrate — to get your database cache hit rate ration'
+\set hitrate 'SELECT sum(idx_blks_read) as idx_read, sum(idx_blks_hit) as idx_hit, (sum(idx_blks_hit) - sum(idx_blks_read)) / sum(idx_blks_hit) as ratio FROM pg_statio_user_indexes;'
+
+\echo '——————————————————————————————————————————————————————————————————————————'
+\echo 'Table and Index Stats'
+
+\echo 'Type :tt ''TABLE''; — Run ":tt ''TABLE'';" to see table stats from pg_stat_user_tables'
+\set tt 'select * from pg_stat_user_tables where relname = '
+
+\echo 'Type :ti ''TABLE''; — Run ":ti ''TABLE''"; to see table indexes from pg_stat_user_indexes'
+\set ti 'select * from pg_stat_user_indexes where relname = '
+
+\echo '——————————————————————————————————————————————————————————————————————————'
+\echo 'Long Running Queries — Showing them and killing them.'
+
+\echo 'Type :stuck60s - to print SELECT queries taking longer than 60 seconds';
+\set stuck60s 'SELECT pid, query FROM pg_stat_activity pa WHERE (now() - pa.query_start) > interval ''60 seconds'' and pa.query ilike ''select%'';';
+
+\echo 'Type :kill60s - to kill SELECT queries taking longer than 60 seconds';
+\set kill60s 'SELECT pg_terminate_backend(pa.pid) FROM pg_stat_activity pa WHERE (now() - pa.query_start) > interval ''60 seconds'' and pa.query ilike ''select%'';';
+
+\echo 'Type :stuck N min''; - to print SELECT queries taking longer than N minutes';
+\echo ' which could be any interval argument, eg 1 hour etc.';
+\set stuck 'SELECT pid, query FROM pg_stat_activity pa WHERE pa.query ilike ''select%'' and (now() - pa.query_start) > interval ''';
+
+\echo 'Type :killq N min''; - to kill SELECT queries taking longer than N minutes';
+\set killq 'SELECT pg_terminate_backend(pa.pid) FROM pg_stat_activity pa WHERE pa.query ilike ''select%'' and (now() - pa.query_start) > interval ''';
+
+\echo '——————————————————————————————————————————————————————————————————————————'
+\echo 'Prompt Management'
+\echo 'Type :p0 - To return to the default prompt (requires Powerline Fonts)'
+\set p0 '\\set PROMPT1 ''%[%033[0;30;33m%]%[%033[0;43;30m%] %`date "+%H:%M:%S%p"` %033[0;30;33m%]%033[0;43;37m%]%[%033[0;0;0m%]━%[%033[0;30;32m%]%033[0;42;30m%] %n@%M:%> %033[0;32m%%[%033[3;30;96m%]━%[%033[3;30;106m%]% %[%033[3;30;106m%]%/%x %033[0;96m% %[%033[0m%]\n ❯ '''
+\echo 'Type :p1 - To switch to a simpler prompt theme #1'
+\set p1 '\\set PROMPT1 ''%[%033[0;90;47m%] %n@%M:%>->(%/) %[%033[0m%]\\n%# '''
+\echo 'Type :p2 - To switch to a simpler prompt theme #2'
+\set p2 '\\set PROMPT1 ''%[%033[0;92;40m%]%n@%M:%>->(%/) %[%033[0;93;40m%] %`date "+%H:%M:%S %p"`\\n%x%[%033[0;0;0m%] ❯ '''
+\echo '——————————————————————————————————————————————————————————————————————————'
+
+\set pgss 'create extension pg_stat_statements;'
+\echo 'Type :pgss to create an extension for pg_stat_statements (assuming its available)'
+\echo 'Type \\q to exit. '
+\echo
+\echo '┌───────────────────────────────────────────────────────────────────────────┐'
+\echo '│ Shortcuts Loaded ✔︎ │'
+\echo '└───────────────────────────────────────────────────────────────────────────┘'
+\echo
+\pset border 3
+\pset columns 0
+\pset format aligned
+\pset linestyle unicode
+\pset null ''
+\pset pager 0
+\pset tuples_only 1
+\timing 1
+\pset tuples_only 0
+\set QUIET 0
\ No newline at end of file
diff --git a/development/config/.vimrc b/development/config/.vimrc
new file mode 100644
index 00000000..67663fc1
--- /dev/null
+++ b/development/config/.vimrc
@@ -0,0 +1 @@
+export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
\ No newline at end of file
diff --git a/development/config/.vimrc.customizations b/development/config/.vimrc.customizations
new file mode 100644
index 00000000..40fbc893
--- /dev/null
+++ b/development/config/.vimrc.customizations
@@ -0,0 +1,262 @@
+" vim: ft=vim
+
+" Required for operations modifying multiple buffers like rename.
+set hidden
+
+" note that if you are using Plug mapping you should not use `noremap` mappings.
+nmap (lcn-menu)
+" Or map each action separately
+nmap K (lcn-hover)
+nmap gd (lcn-definition)
+nmap (lcn-rename)
+
+let g:airline#extensions#sorbet#auto = 0
+function! MyCustomAirlineInit() abort
+ let g:airline_section_x = airline#section#create(['filetype', g:airline_symbols.space, 'sorbet'])
+endfunction
+
+au User AirlineAfterInit ++once call MyCustomAirlineInit()"
+
+let g:codeowners_only_dot_github = 1
+
+"let g:user_emmet_install_global = 0
+"autocmd FileType html,css EmmetInstall
+"let g:user_emmet_leader_key=''
+
+" Once vim-javascript is installed you enable flow highlighting
+let g:javascript_plugin_flow = 1
+
+let g:rigel_airline = 1
+let g:airline_theme = 'powerlineish'
+
+let g:user_emmet_settings = {
+ \ 'ruby' : {
+ \ 'extends' : 'html',
+ \ 'filters' : 'c',
+ \ },
+ \ 'xml' : {
+ \ 'extends' : 'html',
+ \ },
+ \ 'haml' : {
+ \ 'extends' : 'html',
+ \ },
+ \}
+
+
+if executable('fzf')
+ if executable('ag')
+ let FZF_DEFAULT_COMMAND='ag -p ~/.gitignore -g ""'
+ endif
+ " Remap ctrl-P to execute fzf.
+ nnoremap :Files
+ " Show previews with fzf window.
+ command! -bang -nargs=? -complete=dir Files
+ \ call fzf#vim#files(, fzf#vim#with_preview(), 0)
+ let g:fzf_layout = { 'down': '~40%' }
+ nmap :FZF
+endif
+
+" Source custom configs (not under version control).
+if filereadable(glob("~/.vimrc.local"))
+ source ~/.vimrc.local
+endif
+
+let g:eruby_default_subtype = "yaml"
+
+let g:eruby_extensions = { "js": "javascriptreact", "yaml": "yaml" }
+
+fun! s:DetectRuby()
+ if getline(1) =~# '^#!.*\(ruby\|rake\)$'
+ set ft=ruby
+ endif
+endfun
+autocmd BufNewFile,BufRead * call s:DetectRuby()
+
+fun! s:DetectNode()
+ if getline(1) =~# '^#!.*node$'
+ set ft=javascript
+ endif
+endfun
+autocmd BufNewFile,BufRead * call s:DetectNode()
+
+fun! s:DetectBash()
+ " if getline(1) ~ '#!/usr/bin/env bash|#!/bin/bash|#!/bin/sh|#!/bin/zsh|#!/usr/bin/env sh'
+ if getline(1) =~# '^#!.*\(bash\|sh\)$'
+ set ft=bash
+ endif
+endfun
+autocmd BufNewFile,BufRead * call s:DetectBash()
+
+let g:rubycomplete_load_paths = ["./app/classes", "./app/models", "./app/controllers", "./lib", "./lib/tasks", "./spec"]
+
+set t_Co=256
+set termguicolors
+
+"colorscheme brogrammer
+"colorscheme ayu
+"colorscheme molokai
+colorscheme monokai_pro
+
+
+" CocVim
+"
+" May need for Vim (not Neovim) since coc.nvim calculates byte offset by count
+" utf-8 byte sequence
+set encoding=utf-8
+" Some servers have issues with backup files, see #649
+set nobackup
+set nowritebackup
+
+" Having longer updatetime (default is 4000 ms = 4s) leads to noticeable
+" delays and poor user experience
+set updatetime=300
+
+" Always show the signcolumn, otherwise it would shift the text each time
+" diagnostics appear/become resolved
+set signcolumn=yes
+
+" Use tab for trigger completion with characters ahead and navigate
+" NOTE: There's always complete item selected by default, you may want to enable
+" no select by `"suggest.noselect": true` in your configuration file
+" NOTE: Use command ':verbose imap ' to make sure tab is not mapped by
+" other plugin before putting this into your config
+inoremap
+ \ coc#pum#visible() ? coc#pum#next(1) :
+ \ CheckBackspace() ? "\" :
+ \ coc#refresh()
+inoremap coc#pum#visible() ? coc#pum#prev(1) : "\"
+
+" Make to accept selected completion item or notify coc.nvim to format
+" u breaks current undo, please make your own choice
+inoremap coc#pum#visible() ? coc#pum#confirm()
+ \: "\u\\=coc#on_enter()\"
+
+function! CheckBackspace() abort
+ let col = col('.') - 1
+ return !col || getline('.')[col - 1] =~# '\s'
+endfunction
+
+" Use to trigger completion
+if has('nvim')
+ inoremap coc#refresh()
+else
+ inoremap coc#refresh()
+endif
+
+" Use `[g` and `]g` to navigate diagnostics
+" Use `:CocDiagnostics` to get all diagnostics of current buffer in location list
+nmap [g (coc-diagnostic-prev)
+nmap ]g (coc-diagnostic-next)
+
+" GoTo code navigation
+nmap gd (coc-definition)
+nmap gy (coc-type-definition)
+nmap gi (coc-implementation)
+nmap gr (coc-references)
+
+" Use K to show documentation in preview window
+nnoremap K :call ShowDocumentation()
+
+function! ShowDocumentation()
+ if CocAction('hasProvider', 'hover')
+ call CocActionAsync('doHover')
+ else
+ call feedkeys('K', 'in')
+ endif
+endfunction
+
+" Highlight the symbol and its references when holding the cursor
+autocmd CursorHold * silent call CocActionAsync('highlight')
+
+" Symbol renaming
+nmap rn (coc-rename)
+
+" Formatting selected code
+xmap f (coc-format-selected)
+nmap f (coc-format-selected)
+
+augroup mygroup
+ autocmd!
+ " Setup formatexpr specified filetype(s)
+ autocmd FileType typescript,json setl formatexpr=CocAction('formatSelected')
+ " Update signature help on jump placeholder
+ autocmd User CocJumpPlaceholder call CocActionAsync('showSignatureHelp')
+augroup end
+
+" Applying code actions to the selected code block
+" Example: `aap` for current paragraph
+xmap a (coc-codeaction-selected)
+nmap a (coc-codeaction-selected)
+
+" Remap keys for applying code actions at the cursor position
+nmap ac (coc-codeaction-cursor)
+" Remap keys for apply code actions affect whole buffer
+nmap as (coc-codeaction-source)
+" Apply the most preferred quickfix action to fix diagnostic on the current line
+nmap qf (coc-fix-current)
+
+" Remap keys for applying refactor code actions
+nmap re (coc-codeaction-refactor)
+xmap r (coc-codeaction-refactor-selected)
+nmap r (coc-codeaction-refactor-selected)
+
+" Run the Code Lens action on the current line
+nmap cl (coc-codelens-action)
+
+" Map function and class text objects
+" NOTE: Requires 'textDocument.documentSymbol' support from the language server
+xmap if (coc-funcobj-i)
+omap if (coc-funcobj-i)
+xmap af (coc-funcobj-a)
+omap af (coc-funcobj-a)
+xmap ic (coc-classobj-i)
+omap ic (coc-classobj-i)
+xmap ac (coc-classobj-a)
+omap ac (coc-classobj-a)
+
+" Remap and to scroll float windows/popups
+if has('nvim-0.4.0') || has('patch-8.2.0750')
+ nnoremap coc#float#has_scroll() ? coc#float#scroll(1) : "\"
+ nnoremap coc#float#has_scroll() ? coc#float#scroll(0) : "\"
+ inoremap coc#float#has_scroll() ? "\=coc#float#scroll(1)\" : "\"
+ inoremap coc#float#has_scroll() ? "\=coc#float#scroll(0)\" : "\"
+ vnoremap coc#float#has_scroll() ? coc#float#scroll(1) : "\"
+ vnoremap coc#float#has_scroll() ? coc#float#scroll(0) : "\"
+endif
+
+" Use CTRL-S for selections ranges
+" Requires 'textDocument/selectionRange' support of language server
+nmap (coc-range-select)
+xmap (coc-range-select)
+
+" Add `:Format` command to format current buffer
+command! -nargs=0 Format :call CocActionAsync('format')
+
+" Add `:Fold` command to fold current buffer
+command! -nargs=? Fold :call CocAction('fold', )
+
+" Add `:OR` command for organize imports of the current buffer
+command! -nargs=0 OR :call CocActionAsync('runCommand', 'editor.action.organizeImport')
+
+" Add (Neo)Vim's native statusline support
+" NOTE: Please see `:h coc-status` for integrations with external plugins that
+" provide custom statusline: lightline.vim, vim-airline
+set statusline^=%{coc#status()}%{get(b:,'coc_current_function','')}
+
+" Mappings for CoCList
+" Show all diagnostics
+nnoremap a :CocList diagnostics
+" Manage extensions
+nnoremap e :CocList extensions
+" Show commands
+nnoremap c :CocList commands
+" Find symbol of current document
+nnoremap o :CocList outline
+" Search workspace symbols
+nnoremap s :CocList -I symbols
+" Do default action for next item
+nnoremap j :CocNext
+" Do default action for previous item
+nnoremap k :CocPrev
+" Resume latest coc list
+nnoremap p :CocListResume
diff --git a/development/config/config.Darwin.arm64 b/development/config/config.Darwin.arm64
new file mode 100644
index 00000000..7c354f09
--- /dev/null
+++ b/development/config/config.Darwin.arm64
@@ -0,0 +1,8 @@
+---
+BUNDLE_JOBS: "12"
+BUNDLE_BUILD__PG: "--with-pg-config=/opt/homebrew/opt/postgresql@15/bin/pg_config"
+BUNDLE_BUILD__LIBV8: "--with-system-v8"
+BUNDLE_BUILD__FFI: "--with-v8-dir=/opt/homebrew/opt/v8"
+BUNDLE_BUILD__THERUBYRACER: "--with-v8-dir=/opt/homebrew/opt/v8"
+BUNDLE_BUILD: "--with-cflags=-Wno-error=implicit-function-declaration --with-cppflags=-Wno-warning=compound-token-split-by-macro"
+BUNDLE_GEMFILE: "Gemfile"
diff --git a/development/config/vim-setup.tar.gz b/development/config/vim-setup.tar.gz
new file mode 100644
index 00000000..59161572
Binary files /dev/null and b/development/config/vim-setup.tar.gz differ
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index 8c57ec5d..00000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,43 +0,0 @@
-# Container environment variables are loaded in the following priority order:
-# 1. `environment` service option
-# 2. Shell environment variables
-# 3. `env_file` service option
-# 4. Dockerfile `ENV` directives
-#
-# Environment variables referenced in this Compose file are loaded in the
-# following priority order:
-# 1. Shell environment variables
-# 2. `.env` file
-
-version: '3'
-
-services:
- rails:
- build:
- context: .
- dockerfile: Dockerfile
- environment:
- - TICKET_DB_USER=ticket
- - TICKET_DB_PASSWORD=ticket
- - TICKET_DB_HOST=postgres
- - TICKET_DB_PORT=5432
- - TICKET_DB_NAME=ticket
- networks:
- - tickets
- ports:
- - '3000:3000'
-
- postgres:
- image: 'postgres:11-alpine'
- environment:
- - POSTGRES_USER=ticket
- - POSTGRES_PASSWORD=ticket
- - POSTGRES_DB=ticket
- networks:
- - tickets
- ports:
- - '5432:5432'
-
-networks:
- tickets:
-
diff --git a/docs/make-boot.png b/docs/make-boot.png
deleted file mode 100644
index 905351aa..00000000
Binary files a/docs/make-boot.png and /dev/null differ
diff --git a/lib/assets/.keep b/lib/assets/.keep
new file mode 100644
index 00000000..e69de29b
diff --git a/lib/extensions/time.rb b/lib/extensions/time.rb
deleted file mode 100644
index 3506eac2..00000000
--- a/lib/extensions/time.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-class Time
- DATE_FORMATS.merge!(
- friendly: '%A, %B %-d, %Y %l:%M %p %Z',
- month_day: '%B %-d',
- hmm: '%l:%M %p', # HMM = Hour minute meridian
- dhmm: '%A, %l:%M %p' # DayOfWeek, HMM
- )
-
- def self.from_picker(datetimepicker_string)
- strptime(datetimepicker_string, '%Y/%m/%d %H:%M:%S').to_time unless datetimepicker_string.blank?
- end
-end
diff --git a/lib/fnf/csv_reader.rb b/lib/fnf/csv_reader.rb
index 4a54169f..0467e43d 100644
--- a/lib/fnf/csv_reader.rb
+++ b/lib/fnf/csv_reader.rb
@@ -4,14 +4,14 @@
module FnF
class CSVReader
- attr_reader :path
+ attr_reader :csv_file
def initialize(path)
- @path = path
+ @csv_file = path
end
def read(skip_first: true, before_block: nil, after_block: nil, &_block)
- File.open(path) do |file|
+ File.open(csv_file) do |file|
first_line = true
before_block[] if before_block
CSV.foreach(file) do |row|
diff --git a/lib/fnf/events.rb b/lib/fnf/events.rb
deleted file mode 100644
index d1497499..00000000
--- a/lib/fnf/events.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-require_relative 'event_reporter'
-
-module FnF
- module Events
- end
-end
-
-require_relative 'events/ticket_request_event'
-require_relative 'events/ticket_request_approved_event'
diff --git a/lib/fnf/events/abstract_event.rb b/lib/fnf/events/abstract_event.rb
deleted file mode 100644
index acba909f..00000000
--- a/lib/fnf/events/abstract_event.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../event_reporter'
-require 'ventable'
-module FnF
- module Events
- class AbstractEvent
- attr_accessor :target, :user
-
- def initialize(user: nil, target: nil)
- self.target = target
- self.user = user
- end
-
- class << self
- # @param [Object] klass
- def inherited(klass)
- super(klass)
- klass.instance_eval do
- include Ventable::Event
-
- notifies ->(event) { EventReporter.metric(event, 1) }
-
- # For eg FnF::Events::TicketRequestEvent this should return :ticket_request
- def ventable_callback_method_name
- name.gsub(/^FnF::Events::/, '').underscore.downcase.to_sym
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/fnf/html_generator.rb b/lib/fnf/html_generator.rb
index 539fc809..b7c7ae69 100644
--- a/lib/fnf/html_generator.rb
+++ b/lib/fnf/html_generator.rb
@@ -1,21 +1,48 @@
# frozen_string_literal: true
require 'stringio'
+require 'active_support'
+require 'active_support/core_ext/object/blank'
+# rubocop: disable Rails/Output
+
+# @description
+# How this works: The HtmlGenerator is a StringIO subclass which means that any print/puts inside the class
+# simply appends to the string representation. We convert method_missing into HTML elements so you can call
+#
+# @example
+# span do
+# p do
+# a href: url do
+# "and so on"
+# end
+# end
+# end
module FnF
- # @description
- # How this works: The HTMLGenerator is a StringIO subclass which means that any print/puts inside the class
- # simply appends to the string representation. We convert method_missing into HTML elements so you can call
- #
- # @example
- # span do
- # p do
- # a href: url do
- # "and so on"
- # end
- # end
- # end
- class HTMLGenerator < StringIO
+ class HtmlGenerator < StringIO
+ attr_accessor :level
+ attr_reader :indent, :simple_css
+
+ def initialize(*args, simple_css: false, &)
+ super
+ @level = 0
+ @indent = 2
+ @simple_css = simple_css
+ end
+
+ def simple_css_header
+ return unless simple_css
+
+ header = <<~HTML
+
+
+
+
+ HTML
+
+ header.gsub(/^/, ' ' * level * indent)
+ end
+
HTML_ELEMENTS = %w[
html link meta style a ol ul li
strong em div span p body head script
@@ -29,36 +56,77 @@ class HTMLGenerator < StringIO
# @param [Hash{Symbol->Unknown}] attributes of the HTML element, eg: href: url
# @param [Proc] block optional block for the contents of the tag
def html_element(method, *args, **opts, &block)
- options = opts.map { |k, v| %( #{k}="#{v}" ) }.join
- options = " #{options}" if options
- self.print "<#{method}#{options}" if options
- process_block(block, method)
- puts if args.include?(:br)
+ options = opts.map { |k, v| %(#{k}="#{v}" ) }
+ html_arguments = ''
+ html_arguments += " #{args.join(' ')}" unless args.empty?
+ html_arguments += " #{options.join(' ')}" unless options.empty?
+
+ indent_element do
+ print "<#{method}"
+ print ' ' if html_arguments.present?
+ print html_arguments.strip
+
+ if block
+ print ">\n"
+
+ result = instance_exec(*block.parameters, &block)
+ if result.present? && result != self
+ indent_element do
+ puts result
+ end
+ end
+
+ print_element "#{method}>"
+ else
+ puts '/>'
+ end
+ end
+
+ self
end
HTML_ELEMENTS.each do |element|
define_method(element) { |*args, **opts, &block| html_element(element, *args, **opts, &block) }
end
- def render
- $stdout.puts(string)
+ def render(stream = $stdout, &)
+ stream.puts(generate(&))
end
- def generate(&block)
- instance_exec(&block)
+ def generate(*args, **, &block)
+ return unless block
+
+ element = args.shift unless args&.empty?
+ if element
+ puts "<#{element}>"
+ self.level += 1
+ end
+
+ puts simple_css_header if simple_css_header
+
+ self.level = element ? 0 : -1
+
+ instance_exec(*args, &block) if block_given?
+
+ puts "#{element}>" if element
+
+ string
end
private
- def process_block(block, method)
- if block
- print '>'
- result = instance_exec(&block)
- print result unless result.nil?
- print "#{method}>"
- else
- print '/>'
- end
+ def print_element(element)
+ print(' ' * level * indent).to_s
+ puts element
+ end
+
+ def indent_element
+ self.level += 1
+ print(' ' * level * indent).to_s
+ yield(self) if block_given?
+ self.level -= 1
end
end
end
+
+# rubocop: enable Rails/Output
diff --git a/lib/fnf/submission_links.rb b/lib/fnf/submission_links.rb
new file mode 100644
index 00000000..12334ec5
--- /dev/null
+++ b/lib/fnf/submission_links.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require_relative 'html_generator'
+require_relative 'csv_reader'
+require 'active_support'
+
+# rubocop: disable Rails/Exit
+module FnF
+ class SubmissionLinks
+ attr_reader :csv_file, :output_stream, :generator, :reader
+
+ def initialize(csv, html = nil, simple_css: false)
+ if csv.nil? || !File.exist?(csv)
+ warn "USAGE: #{$PROGRAM_NAME} csv-file [ --simple-css ] "
+ warn ' Where the CSV file has three columns: DJ Name, Real Name, URL'
+ warn ' If you pass --simple-css you will also get the with simple CSS included'
+ exit(1)
+ end
+
+ @csv_file = csv.is_a?(File) ? csv.path : csv
+
+ @output_stream = if html
+ File.open(html, 'w')
+ else
+ $stdout
+ end
+
+ @generator = FnF::HtmlGenerator.new(simple_css:)
+ @reader = FnF::CSVReader.new(csv_file)
+ end
+
+ def run
+ reader = @reader
+ result = generator.generate(:html) do
+ reader.read do |dj_name, real_name, url|
+ li do
+ strong { a(href: url, target: '_blank', ref: 'canonical') { dj_name } }
+ if dj_name == real_name
+ self
+ else
+ span class: 'djName' do
+ "(#{real_name.split(/ /).map(&:capitalize).join(' ')})"
+ end
+ end
+ end
+ end
+ end
+
+ output_stream.write(result)
+ output_stream.close if output_stream != $stdout
+ result
+ end
+ end
+end
+# rubocop: enable Rails/Exit
diff --git a/lib/tasks/.keep b/lib/tasks/.keep
new file mode 100644
index 00000000..e69de29b
diff --git a/lib/tasks/auto_annotate_models.rake b/lib/tasks/auto_annotate_models.rake
new file mode 100644
index 00000000..a9007b6f
--- /dev/null
+++ b/lib/tasks/auto_annotate_models.rake
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+# NOTE: only doing this in development as some production environments (Heroku)
+# NOTE: are sensitive to local FS writes, and besides -- it's just not proper
+# NOTE: to have a dev-mode tool do its thing in production.
+if Rails.env.development?
+ require 'annotate'
+
+ # rubocop: disable Rake/Desc
+ task set_annotation_options: :environment do
+ # You can override any of these by setting an environment variable of the
+ # same name.
+ Annotate.set_defaults(
+ 'active_admin' => 'false',
+ 'additional_file_patterns' => [],
+ 'routes' => 'false',
+ 'models' => 'true',
+ 'position_in_routes' => 'before',
+ 'position_in_class' => 'before',
+ 'position_in_test' => 'before',
+ 'position_in_fixture' => 'before',
+ 'position_in_factory' => 'before',
+ 'position_in_serializer' => 'before',
+ 'show_foreign_keys' => 'true',
+ 'show_complete_foreign_keys' => 'false',
+ 'show_indexes' => 'true',
+ 'simple_indexes' => 'false',
+ 'model_dir' => 'app/models',
+ 'root_dir' => '',
+ 'include_version' => 'false',
+ 'require' => '',
+ 'exclude_tests' => 'false',
+ 'exclude_fixtures' => 'false',
+ 'exclude_factories' => 'false',
+ 'exclude_serializers' => 'false',
+ 'exclude_scaffolds' => 'true',
+ 'exclude_controllers' => 'true',
+ 'exclude_helpers' => 'true',
+ 'exclude_sti_subclasses' => 'false',
+ 'ignore_model_sub_dir' => 'false',
+ 'ignore_columns' => nil,
+ 'ignore_routes' => nil,
+ 'ignore_unknown_models' => 'false',
+ 'hide_limit_column_types' => 'integer,bigint,boolean',
+ 'hide_default_column_types' => 'json,jsonb,hstore',
+ 'skip_on_db_migrate' => 'false',
+ 'format_bare' => 'true',
+ 'format_rdoc' => 'false',
+ 'format_yard' => 'false',
+ 'format_markdown' => 'false',
+ 'sort' => 'false',
+ 'force' => 'false',
+ 'frozen' => 'false',
+ 'classified_sort' => 'true',
+ 'trace' => 'false',
+ 'wrapper_open' => nil,
+ 'wrapper_close' => nil,
+ 'with_comment' => 'true'
+ )
+ end
+ # rubocop: enable Rake/Desc
+
+ Annotate.load_tasks
+end
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000..401e881a
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1840 @@
+{
+ "name": "app",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "app",
+ "dependencies": {
+ "@hotwired/stimulus": "^3.2.2",
+ "@hotwired/turbo-rails": "^8.0.4",
+ "@popperjs/core": "^2.11.8",
+ "autoprefixer": "^10.4.19",
+ "bootstrap": "^5.3.3",
+ "bootstrap-icons": "^1.11.3",
+ "esbuild": "^0.20.2",
+ "flatpickr": "^4.6.13",
+ "fs": "^0.0.1-security",
+ "jquery": "^3.7.1",
+ "nodemon": "^3.1.0",
+ "postcss": "^8.4.38",
+ "postcss-cli": "^11.0.0",
+ "sass": "^1.75.0",
+ "stripe": "^15.1.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
+ "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
+ "cpu": [
+ "ppc64"
+ ],
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
+ "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
+ "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
+ "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
+ "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
+ "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
+ "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
+ "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
+ "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
+ "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
+ "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
+ "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
+ "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
+ "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
+ "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
+ "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
+ "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
+ "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
+ "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
+ "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
+ "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
+ "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
+ "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@hotwired/stimulus": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@hotwired/stimulus/-/stimulus-3.2.2.tgz",
+ "integrity": "sha512-eGeIqNOQpXoPAIP7tC1+1Yc1yl1xnwYqg+3mzqxyrbE5pg5YFBZcA6YoTiByJB6DKAEsiWtl6tjTJS4IYtbB7A=="
+ },
+ "node_modules/@hotwired/turbo": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/@hotwired/turbo/-/turbo-8.0.4.tgz",
+ "integrity": "sha512-mlZEFUZrJnpfj+g/XeCWWuokvQyN68WvM78JM+0jfSFc98wegm259vCbC1zSllcspRwbgXK31ibehCy5PA78/Q==",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/@hotwired/turbo-rails": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/@hotwired/turbo-rails/-/turbo-rails-8.0.4.tgz",
+ "integrity": "sha512-GHCv5+B2VzYZZvMFpg/g9JLx/8pl/8chcubSB7T+Xn1zYOMqAKB6cT80vvWUzxdwfm/2KfaRysfDz+BmvtjFaw==",
+ "dependencies": {
+ "@hotwired/turbo": "^8.0.4",
+ "@rails/actioncable": "^7.0"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@popperjs/core": {
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
+ "node_modules/@rails/actioncable": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/@rails/actioncable/-/actioncable-7.1.3.tgz",
+ "integrity": "sha512-ojNvnoZtPN0pYvVFtlO7dyEN9Oml1B6IDM+whGKVak69MMYW99lC2NOWXWeE3bmwEydbP/nn6ERcpfjHVjYQjA=="
+ },
+ "node_modules/@sindresorhus/merge-streams": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz",
+ "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "20.12.6",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.6.tgz",
+ "integrity": "sha512-3KurE8taB8GCvZBPngVbp0lk5CKi8M9f9k1rsADh0Evdz5SzJ+Q+Hx9uHoFGsLnLnd1xmkDQr2hVhlA0Mn0lKQ==",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/abbrev": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.19",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
+ "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "browserslist": "^4.23.0",
+ "caniuse-lite": "^1.0.30001599",
+ "fraction.js": "^4.3.7",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.0.0",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/bootstrap": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
+ "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/twbs"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/bootstrap"
+ }
+ ],
+ "peerDependencies": {
+ "@popperjs/core": "^2.11.8"
+ }
+ },
+ "node_modules/bootstrap-icons": {
+ "version": "1.11.3",
+ "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz",
+ "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/twbs"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/bootstrap"
+ }
+ ]
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.23.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
+ "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001587",
+ "electron-to-chromium": "^1.4.668",
+ "node-releases": "^2.0.14",
+ "update-browserslist-db": "^1.0.13"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
+ "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001607",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001607.tgz",
+ "integrity": "sha512-WcvhVRjXLKFB/kmOFVwELtMxyhq3iM/MvmXcyCe2PNf166c39mptscOc/45TTS96n2gpNV2z7+NakArTWZCQ3w==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ]
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/dependency-graph": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz",
+ "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==",
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.4.730",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.730.tgz",
+ "integrity": "sha512-oJRPo82XEqtQAobHpJIR3zW5YO3sSRRkPz2an4yxi1UvqhsGm54vR/wzTFV74a3soDOJ8CKW7ajOOX5ESzddwg=="
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+ "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+ "dependencies": {
+ "get-intrinsic": "^1.2.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
+ "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.20.2",
+ "@esbuild/android-arm": "0.20.2",
+ "@esbuild/android-arm64": "0.20.2",
+ "@esbuild/android-x64": "0.20.2",
+ "@esbuild/darwin-arm64": "0.20.2",
+ "@esbuild/darwin-x64": "0.20.2",
+ "@esbuild/freebsd-arm64": "0.20.2",
+ "@esbuild/freebsd-x64": "0.20.2",
+ "@esbuild/linux-arm": "0.20.2",
+ "@esbuild/linux-arm64": "0.20.2",
+ "@esbuild/linux-ia32": "0.20.2",
+ "@esbuild/linux-loong64": "0.20.2",
+ "@esbuild/linux-mips64el": "0.20.2",
+ "@esbuild/linux-ppc64": "0.20.2",
+ "@esbuild/linux-riscv64": "0.20.2",
+ "@esbuild/linux-s390x": "0.20.2",
+ "@esbuild/linux-x64": "0.20.2",
+ "@esbuild/netbsd-x64": "0.20.2",
+ "@esbuild/openbsd-x64": "0.20.2",
+ "@esbuild/sunos-x64": "0.20.2",
+ "@esbuild/win32-arm64": "0.20.2",
+ "@esbuild/win32-ia32": "0.20.2",
+ "@esbuild/win32-x64": "0.20.2"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
+ "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+ "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+ "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/flatpickr": {
+ "version": "4.6.13",
+ "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.13.tgz",
+ "integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw=="
+ },
+ "node_modules/fraction.js": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fs": {
+ "version": "0.0.1-security",
+ "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
+ "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w=="
+ },
+ "node_modules/fs-extra": {
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
+ "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+ "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "hasown": "^2.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-stdin": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz",
+ "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/globby": {
+ "version": "14.0.1",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.1.tgz",
+ "integrity": "sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==",
+ "dependencies": {
+ "@sindresorhus/merge-streams": "^2.1.0",
+ "fast-glob": "^3.3.2",
+ "ignore": "^5.2.4",
+ "path-type": "^5.0.0",
+ "slash": "^5.1.0",
+ "unicorn-magic": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+ "dependencies": {
+ "get-intrinsic": "^1.1.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
+ "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
+ "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/ignore-by-default": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA=="
+ },
+ "node_modules/immutable": {
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz",
+ "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw=="
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/jquery": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
+ "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
+ },
+ "node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz",
+ "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+ "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "dependencies": {
+ "braces": "^3.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
+ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw=="
+ },
+ "node_modules/nodemon": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz",
+ "integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==",
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^3.1.2",
+ "pstree.remy": "^1.1.8",
+ "semver": "^7.5.3",
+ "simple-update-notifier": "^2.0.0",
+ "supports-color": "^5.5.0",
+ "touch": "^3.1.0",
+ "undefsafe": "^2.0.5"
+ },
+ "bin": {
+ "nodemon": "bin/nodemon.js"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nodemon"
+ }
+ },
+ "node_modules/nopt": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
+ "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
+ "dependencies": {
+ "abbrev": "1"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
+ "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/path-type": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz",
+ "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.38",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
+ "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.2.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-cli": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-11.0.0.tgz",
+ "integrity": "sha512-xMITAI7M0u1yolVcXJ9XTZiO9aO49mcoKQy6pCDFdMh9kGqhzLVpWxeD/32M/QBmkhcGypZFFOLNLmIW4Pg4RA==",
+ "dependencies": {
+ "chokidar": "^3.3.0",
+ "dependency-graph": "^0.11.0",
+ "fs-extra": "^11.0.0",
+ "get-stdin": "^9.0.0",
+ "globby": "^14.0.0",
+ "picocolors": "^1.0.0",
+ "postcss-load-config": "^5.0.0",
+ "postcss-reporter": "^7.0.0",
+ "pretty-hrtime": "^1.0.3",
+ "read-cache": "^1.0.0",
+ "slash": "^5.0.0",
+ "yargs": "^17.0.0"
+ },
+ "bin": {
+ "postcss": "index.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.0.3.tgz",
+ "integrity": "sha512-90pBBI5apUVruIEdCxZic93Wm+i9fTrp7TXbgdUCH+/L+2WnfpITSpq5dFU/IPvbv7aNiMlQISpUkAm3fEcvgQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "lilconfig": "^3.0.0",
+ "yaml": "^2.3.4"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "jiti": ">=1.21.0",
+ "postcss": ">=8.0.9"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-reporter": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.1.0.tgz",
+ "integrity": "sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "picocolors": "^1.0.0",
+ "thenby": "^1.3.4"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
+ },
+ "node_modules/pretty-hrtime": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
+ "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/pstree.remy": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w=="
+ },
+ "node_modules/qs": {
+ "version": "6.11.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
+ "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+ "dependencies": {
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/sass": {
+ "version": "1.75.0",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz",
+ "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==",
+ "dependencies": {
+ "chokidar": ">=3.0.0 <4.0.0",
+ "immutable": "^4.0.0",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ },
+ "bin": {
+ "sass": "sass.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+ "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
+ "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.4",
+ "object-inspect": "^1.13.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/simple-update-notifier": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/slash": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
+ "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+ "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/stripe": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/stripe/-/stripe-15.1.0.tgz",
+ "integrity": "sha512-yMsP9VWsQKkmR14E7MhtFDYKCoYCnhG/tLtiMnNiSiVisDezU2OKJ2T9myufYKl922H85nvCgaczyBLovaJTVA==",
+ "dependencies": {
+ "@types/node": ">=8.1.0",
+ "qs": "^6.11.0"
+ },
+ "engines": {
+ "node": ">=12.*"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/thenby": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz",
+ "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ=="
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/touch": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
+ "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
+ "dependencies": {
+ "nopt": "~1.0.10"
+ },
+ "bin": {
+ "nodetouch": "bin/nodetouch.js"
+ }
+ },
+ "node_modules/undefsafe": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="
+ },
+ "node_modules/undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
+ },
+ "node_modules/unicorn-magic": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz",
+ "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.0.13",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
+ "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "escalade": "^3.1.1",
+ "picocolors": "^1.0.0"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ },
+ "node_modules/yaml": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz",
+ "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "engines": {
+ "node": ">=12"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..16770d5c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,68 @@
+{
+ "name": "ticket-booth",
+ "private": true,
+ "dependencies": {
+ "@hotwired/stimulus": "^3.2.2",
+ "@hotwired/turbo-rails": "^8.0.4",
+ "@popperjs/core": "^2.11.8",
+ "@stimulus-components/rails-nested-form": "^5.0.0",
+ "@stripe/stripe-js": "^3.3.0",
+ "autoprefixer": "^10.4.19",
+ "bootstrap": "^5.3.3",
+ "bootstrap-icons": "^1.11.3",
+ "css-loader": "^7.1.1",
+ "esbuild": "^0.20.2",
+ "flatpickr": "^4.6.13",
+ "fs": "^0.0.1-security",
+ "jquery": "^3.7.1",
+ "mini-css-extract-plugin": "^2.9.0",
+ "nodemon": "^3.1.0",
+ "postcss": "^8.4.38",
+ "postcss-cli": "^11.0.0",
+ "sass": "^1.75.0",
+ "sass-loader": "^14.2.1"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.24.4",
+ "@babel/eslint-parser": "^7.24.1",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/preset-env": "^7.24.4",
+ "@types/babel__core": "^7",
+ "@types/babel__preset-env": "^7",
+ "@types/jquery": "^3",
+ "@typescript-eslint/eslint-plugin": "^7.7.0",
+ "@typescript-eslint/parser": "^7.7.0",
+ "cypress": "^13.7.3",
+ "eslint": "^9.0.0",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-plugin-cypress": "^2.15.2",
+ "eslint-plugin-prettier": "^5.1.3",
+ "prettier": "^3.2.5",
+ "tsc-watch": "^6.2.0",
+ "typescript": "^5.4.5"
+ },
+ "scripts": {
+ "build": "yarn run build:js; yarn run build:css",
+ "build:css": "yarn build:css:compile && yarn build:css:prefix",
+ "build:css:compile": "sass ./app/assets/stylesheets/application.scss:./app/assets/builds/application.css --no-source-map --load-path=node_modules",
+ "build:css:prefix": "postcss ./app/assets/builds/application.css --use=autoprefixer --output=./app/assets/builds/application.css",
+ "build:js": "esbuild app/javascript/*.* --bundle --sourcemap --ignore-annotations --format=esm --outdir=app/assets/builds --public-path=/assets",
+ "dev": "tsc-watch --noClear -p tsconfig.json --onSuccess \"yarn build:js\" --onFailure \"yarn failure:js\"",
+ "failure:js": "rm ./app/assets/builds/application.js && rm ./app/assets/builds/application.js.map",
+ "watch:css": "nodemon --watch ./app/assets/stylesheets/ --ext scss --exec \"yarn build:css\"",
+ "eslint:check": "yarn eslint .",
+ "prettier:check": "yarn prettier --check ."
+ },
+ "volta": {
+ "node": "20.12.2",
+ "yarn": "4.1.1"
+ },
+ "babel": {
+ "presets": [
+ "./webpack.babel.js"
+ ]
+ },
+ "browserslist": [
+ "defaults"
+ ]
+}
diff --git a/public/404.html b/public/404.html
index 9a48320a..2be3af26 100644
--- a/public/404.html
+++ b/public/404.html
@@ -2,25 +2,66 @@
The page you were looking for doesn't exist (404)
-
-
+
-
The page you were looking for doesn't exist.
-
You may have mistyped the address or the page may have moved.
+
+
The page you were looking for doesn't exist.
+
You may have mistyped the address or the page may have moved.
+
+
If you are the application owner check the logs for more information.
diff --git a/public/422.html b/public/422.html
index 83660ab1..c08eac0d 100644
--- a/public/422.html
+++ b/public/422.html
@@ -2,25 +2,66 @@
The change you wanted was rejected (422)
-
-
+
-
The change you wanted was rejected.
-
Maybe you tried to change something you didn't have access to.
+
+
The change you wanted was rejected.
+
Maybe you tried to change something you didn't have access to.
+
+
If you are the application owner check the logs for more information.
diff --git a/public/426.html b/public/426.html
new file mode 100644
index 00000000..4a0a84ac
--- /dev/null
+++ b/public/426.html
@@ -0,0 +1,66 @@
+
+
+
+ Your browser is not supported (426)
+
+
+
+
+
+
+
+
+
Your browser is not supported.
+
Please upgrade your browser to continue.
+
+
+
+
diff --git a/public/500.html b/public/500.html
index e2e2fbff..78a030af 100644
--- a/public/500.html
+++ b/public/500.html
@@ -1,52 +1,66 @@
-
-
-
-
- We're sorry.
-
- Something went wrong. We've received a notification and will be looking into it.
-
-
-
In the meantime, here's a cat video for your trouble:
- VIDEO
-
-
-
+
+ We're sorry, but something went wrong (500)
+
+
+
+
+
+
+
+
+
We're sorry, but something went wrong.
+
+
If you are the application owner check the logs for more information.
+
+
diff --git a/public/favicon.ico b/public/favicon.ico
deleted file mode 100644
index 206f08cb..00000000
Binary files a/public/favicon.ico and /dev/null differ
diff --git a/public/icons/android-chrome-192x192.png b/public/icons/android-chrome-192x192.png
new file mode 100644
index 00000000..a98b0040
Binary files /dev/null and b/public/icons/android-chrome-192x192.png differ
diff --git a/public/icons/android-chrome-512x512.png b/public/icons/android-chrome-512x512.png
new file mode 100644
index 00000000..a97eb40d
Binary files /dev/null and b/public/icons/android-chrome-512x512.png differ
diff --git a/public/icons/apple-touch-icon.png b/public/icons/apple-touch-icon.png
new file mode 100644
index 00000000..9b55e211
Binary files /dev/null and b/public/icons/apple-touch-icon.png differ
diff --git a/public/icons/favicon-16x16.png b/public/icons/favicon-16x16.png
new file mode 100644
index 00000000..3b4b0b9c
Binary files /dev/null and b/public/icons/favicon-16x16.png differ
diff --git a/public/icons/favicon-32x32.png b/public/icons/favicon-32x32.png
new file mode 100644
index 00000000..2a65018b
Binary files /dev/null and b/public/icons/favicon-32x32.png differ
diff --git a/public/icons/favicon.ico b/public/icons/favicon.ico
new file mode 100644
index 00000000..68bf3ddb
Binary files /dev/null and b/public/icons/favicon.ico differ
diff --git a/public/icons/nav-bar-logo.png b/public/icons/nav-bar-logo.png
new file mode 100644
index 00000000..67e5baf3
Binary files /dev/null and b/public/icons/nav-bar-logo.png differ
diff --git a/public/manifest.json b/public/manifest.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/public/manifest.json
@@ -0,0 +1 @@
+{}
diff --git a/public/robots.txt b/public/robots.txt
index 61edcc83..c19f78ab 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -1,3 +1 @@
-# Disable all spiders
-User-Agent: *
-Disallow: /
+# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
diff --git a/public/site.webmanifest b/public/site.webmanifest
new file mode 100644
index 00000000..2bf0579f
--- /dev/null
+++ b/public/site.webmanifest
@@ -0,0 +1,19 @@
+{
+ "name": "TicketBooth",
+ "short_name": "TB",
+ "icons": [
+ {
+ "src": "/icons/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/icons/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#000",
+ "display": "standalone"
+}
diff --git a/spec/classes/fnf/event_reporter_spec.rb b/spec/classes/fnf/event_reporter_spec.rb
new file mode 100644
index 00000000..f8c48fa8
--- /dev/null
+++ b/spec/classes/fnf/event_reporter_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+module FnF
+ RSpec.describe EventReporter do
+ # noinspection RailsParamDefResolve
+ let(:handler_name) { ::FnF::Events::BuildingOnFireEvent.send(:ventable_callback_method_name) }
+
+ before do
+ described_class.instance_eval do
+ def building_on_fire(event)
+ metric(event, 1)
+ end
+ end
+ end
+
+ it 'is correct handler name' do
+ expect(handler_name).to eq(:building_on_fire)
+ end
+
+ # rubocop: disable RSpec
+ describe '#event_name' do
+ before(:all) { ::FnF::Events::BuildingOnFireEvent.configure { notifies EventReporter } }
+
+ it 'invokes metric for all events' do
+ expect(described_class).to receive(:metric).with(FnF::Events::BuildingOnFireEvent, 1).once
+
+ Events.set_building_on_fire
+ end
+ end
+ # rubocop: enable RSpec
+ end
+end
diff --git a/spec/lib/fnf/events/events_spec.rb b/spec/classes/fnf/events_spec.rb
similarity index 60%
rename from spec/lib/fnf/events/events_spec.rb
rename to spec/classes/fnf/events_spec.rb
index d72ed4c0..1c9b5c0d 100644
--- a/spec/lib/fnf/events/events_spec.rb
+++ b/spec/classes/fnf/events_spec.rb
@@ -1,19 +1,20 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'rails_helper'
class Worker
@events = {}
class << self
- attr_accessor :events
+ attr_reader :events
def register(event)
- events[event.class.name] ||= 0
- events[event.class.name] += 1
+ @events ||= {}
+ @events[event.class.name] ||= 0
+ @events[event.class.name] += 1
end
- def building_on_fire_event(event)
+ def building_on_fire(event)
register(event)
end
end
@@ -21,16 +22,16 @@ def building_on_fire_event(event)
# using FnF::Events::BuildingOnFireEvent @see spec/support/events.rb
RSpec.describe 'Observers' do
+ subject { worker.events }
+
let(:worker) { Worker }
- let(:event) { ::FnF::Events::BuildingOnFireEvent }
+ let(:event) { FnF::Events::BuildingOnFireEvent }
before do
event.configure { notifies Worker }
- ::FnF::Events.set_building_on_fire!
+ FnF::Events.set_building_on_fire
end
- subject { worker.events }
-
it { is_expected.not_to be_empty }
end
diff --git a/spec/controllers/emails_controller_spec.rb b/spec/controllers/emails_controller_spec.rb
index 61f7e646..b3d64fe6 100644
--- a/spec/controllers/emails_controller_spec.rb
+++ b/spec/controllers/emails_controller_spec.rb
@@ -1,25 +1,27 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'rails_helper'
describe EmailsController, type: :controller do
describe 'GET #index' do
+ subject { get :index, params: { email: } }
+
let(:email) { nil }
+ let(:user) { create(:user, email: user_email) }
+ let(:user_email) { Faker::Internet.email }
+
+ before { user }
- before { get :index, email: email }
+ context 'when a proper email is provided' do
+ let(:email) { user_email }
- context 'when no email is provided' do
- it { response.status.should eq 404 }
+ it { is_expected.to have_http_status(:ok) }
end
context 'when non-existent email is provided' do
- let(:email) { 'thisemailwillneverexist@nowaynohownowhere.com' }
- it { response.status.should eq 404 }
- end
+ let(:email) { Faker::Internet.email(name: 'smith') }
- context 'when existing email is provided' do
- let(:email) { User.make!.email }
- it { response.status.should eq 200 }
+ it { is_expected.to have_http_status(:not_found) }
end
end
end
diff --git a/spec/controllers/events_controller_spec.rb b/spec/controllers/events_controller_spec.rb
index 9bb64fad..88b620bd 100644
--- a/spec/controllers/events_controller_spec.rb
+++ b/spec/controllers/events_controller_spec.rb
@@ -1,14 +1,15 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'rails_helper'
describe EventsController, type: :controller do
- let(:event) { Event.make! }
+ let(:event) { create(:event) }
let(:viewer) { nil }
+
before { sign_in viewer if viewer }
describe 'GET #show' do
- before { get :show, id: event.to_param }
+ before { get :show, params: { id: event.to_param } }
context 'when the user is not signed in' do
it { redirects_to new_event_ticket_request_path(event) }
@@ -16,14 +17,182 @@
context 'when the user is signed in' do
context 'and is not an event admin' do
- let(:viewer) { User.make! }
+ let(:viewer) { create(:user) }
+
it { redirects_to new_event_ticket_request_path(event) }
end
context 'and is an event admin' do
- let(:viewer) { EventAdmin.make!(event: event, user: User.make!).user }
+ let(:viewer) { create(:event_admin, event:).user }
+
+ it { succeeds }
+ end
+ end
+ end
+
+ describe 'GET #new' do
+ before { get :new }
+
+ context 'when the user is not signed in' do
+ it { redirects_to new_user_session_path }
+ end
+
+ context 'when the user is signed in' do
+ context 'and is not a site admin' do
+ let(:viewer) { create(:user) }
+
+ it { succeeds }
+ end
+
+ context 'and is a site admin' do
+ let(:viewer) { create(:site_admin).user }
+
it { succeeds }
end
end
end
+
+ describe 'POST #create' do
+ let(:new_event) { build(:event) }
+
+ let(:new_event_params) do
+ time_hash = {}
+ hash = new_event.attributes
+
+ hash.keys.grep(/_time/).each do |time_key|
+ time_hash[time_key.to_sym] = TimeHelper.to_string_for_flatpickr(hash[time_key].to_time) if hash[time_key]
+ end
+
+ hash.merge(time_hash)
+ end
+
+ let(:make_request) { -> { post :create, params: { event: new_event_params } } }
+
+ # rubocop: disable RSpec/AnyInstance
+ before do
+ allow_any_instance_of(described_class).to receive(:params).and_return(ActionController::Parameters.new(event: new_event_params))
+ end
+
+ it 'does not have a nil start_time' do
+ expect(new_event.start_time).not_to be_nil
+ end
+
+ it 'has a start time thats a proper datetime' do
+ expect(new_event.start_time.to_datetime).to be_a(DateTime)
+ end
+
+ # rubocop: enable RSpec/AnyInstance
+
+ describe '#permitted_params' do
+ subject(:permitted_keys) { permitted_event_keys }
+
+ let(:permitted_event_params) { described_class.new.send(:permitted_params)[:event].to_h.symbolize_keys }
+ let(:expected_keys) do
+ %i[
+ adult_ticket_price
+ allow_donations
+ allow_financial_assistance
+ cabin_price
+ early_arrival_price
+ end_time
+ kid_ticket_price
+ late_departure_price
+ max_adult_tickets_per_request
+ max_cabin_requests
+ max_cabins_per_request
+ max_kid_tickets_per_request
+ name
+ require_mailing_address
+ start_time
+ ticket_requests_end_time
+ ticket_sales_end_time
+ ticket_sales_start_time
+ tickets_require_approval
+ ]
+ end
+ let(:permitted_event_keys) { permitted_event_params.keys.sort }
+
+ it { expect(permitted_keys).to eql(expected_keys) }
+ end
+
+ context 'when the user is not signed in' do
+ before { make_request[] }
+
+ it { redirects_to new_user_session_path }
+ end
+
+ context 'when the user is signed in' do
+ context 'and is not a site admin' do
+ let(:viewer) { create(:user) }
+
+ before { make_request[] }
+
+ it { redirects_to root_path }
+ end
+
+ context 'and is a site admin' do
+ let(:viewer) { create(:site_admin).user }
+
+ it 'creates an event' do
+ expect { make_request[] }.to change(Event, :count).by(1)
+ end
+
+ describe 'flash messages' do
+ before { make_request[] }
+
+ its(:flash) { is_expected.to be_empty }
+
+ it { redirects_to event_path(Event.last) }
+ end
+
+ describe 'new_event_params' do
+ subject { new_event_params }
+
+ it { is_expected.to include(:start_time, :end_time) }
+
+ it 'has start times are not nil' do
+ expect(new_event_params[:start_time]).not_to be_nil
+ end
+
+ it 'has end times are not nil' do
+ expect(new_event_params[:end_time]).not_to be_nil
+ end
+
+ it 'has start_time' do
+ expect(new_event_params[:start_time]).to eql(TimeHelper.to_string_for_flatpickr(new_event.start_time.to_time))
+ end
+
+ it 'has end_time' do
+ expect(new_event_params[:end_time]).to eql(TimeHelper.to_string_for_flatpickr(new_event.end_time.to_time))
+ end
+
+ it 'is properly formatted' do
+ expect(new_event_params[:start_time]).to match(%r{\d{2}/\d{2}/\d{4}, \d{2}:\d{2} (AM|PM)})
+ end
+ end
+
+ describe 'newly created event' do
+ before { make_request[] }
+
+ describe 'event' do
+ subject { Event.where(name: new_event.name).first }
+
+ it { is_expected.not_to be_nil }
+ it { is_expected.to be_valid }
+ it { is_expected.to be_persisted }
+ end
+ end
+
+ describe 'event creation' do
+ subject(:response) { make_request[] }
+
+ let(:created_event) { subject.instance_variable_get(:@event) }
+
+ it { expect { response }.to change(Event, :count) }
+ it { is_expected.to have_http_status(:redirect) }
+ it { is_expected.to redirect_to(event_path(Event.last)) }
+ end
+ end
+ end
+ end
end
diff --git a/spec/controllers/ticket_requests_controller_spec.rb b/spec/controllers/ticket_requests_controller_spec.rb
index 7dcbb7fe..0d4bd3f4 100644
--- a/spec/controllers/ticket_requests_controller_spec.rb
+++ b/spec/controllers/ticket_requests_controller_spec.rb
@@ -1,184 +1,254 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'rails_helper'
+# rubocop: disable RSpec
describe TicketRequestsController, type: :controller do
+ let(:user) { create(:user) }
+ let(:event) { create(:event) }
+
let(:viewer) { nil }
+
before { sign_in viewer if viewer }
describe 'GET #index' do
- let(:event) { Event.make! }
- let(:ticket_requests) { TicketRequest.make_list! 3 }
- before { get :index, event_id: event.to_param }
+ subject { get(:index, params: { event_id: event.to_param }) }
+
+ let(:ticket_requests) { create_list(:ticket_request, event:, count: 3) }
context 'when viewer not signed in' do
- it { redirects }
+ it { is_expected.to have_http_status(:redirect) }
end
context 'when viewer is a normal user' do
- let(:viewer) { User.make! }
- it { redirects }
+ let(:viewer) { create(:user) }
+
+ it { is_expected.to have_http_status(:redirect) }
end
context 'when viewer is the event admin' do
- let(:viewer) { EventAdmin.make!(event: event).user }
- it { succeeds }
+ let(:viewer) { create(:event_admin, event:).user }
+
+ it { is_expected.to have_http_status(:ok) }
end
context 'when viewer is a site admin' do
- let(:viewer) { User.make! :site_admin }
- it { succeeds }
+ let(:viewer) { create(:site_admin).user }
+
+ it { is_expected.to have_http_status(:ok) }
end
end
describe 'GET #show' do
- let(:user) { User.make! }
- let(:ticket_request) { TicketRequest.make! user: user }
-
- before do
- get :show, event_id: ticket_request.event.to_param,
- id: ticket_request.to_param
+ subject do
+ get :show, params: { event_id: ticket_request.event.to_param,
+ id: ticket_request.to_param }
end
+ let(:ticket_request) { create(:ticket_request, user:) }
+
context 'when viewer not signed in' do
- it { redirects }
+ it { is_expected.to have_http_status(:redirect) }
end
context 'when viewer is not the owner of the ticket request' do
- let(:viewer) { User.make! }
- it { redirects_to root_path }
+ let(:viewer) { create(:user) }
+
+ it { is_expected.to have_http_status(:redirect) }
end
context 'when viewer is the owner of the ticket request' do
let(:viewer) { user }
- it { succeeds }
+
+ it { is_expected.to have_http_status(:ok) }
end
context 'when viewer is an event admin' do
- let(:viewer) { EventAdmin.make!(event: ticket_request.event).user }
- it { succeeds }
+ let(:viewer) { create(:event_admin, event: ticket_request.event).user }
+
+ it { is_expected.to have_http_status(:ok) }
end
context 'when viewer is a site admin' do
- let(:viewer) { User.make! :site_admin }
- it { succeeds }
+ let(:viewer) { create(:site_admin).user }
+
+ it { is_expected.to have_http_status(:ok) }
end
end
describe 'GET #new' do
- let(:event) { Event.make! }
- before { get :new, event_id: event.to_param }
+ let(:event) { create(:event) }
+
+ before { get :new, params: { event_id: event.to_param } }
context 'when viewer not signed in' do
it { succeeds }
end
context 'when viewer signed in' do
- let(:viewer) { User.make! }
+ let(:viewer) { create(:user) }
+
it { succeeds }
end
end
describe 'GET #edit' do
- let(:user) { User.make! }
- let(:ticket_request) { TicketRequest.make! user: user }
-
- before do
- get :edit, event_id: ticket_request.event.to_param,
- id: ticket_request.to_param
+ subject do
+ get :edit, params: { event_id: ticket_request.event.to_param,
+ id: ticket_request.to_param }
end
+ let(:ticket_request) { create(:ticket_request, event:) }
+ let(:user) { ticket_request.user }
+
context 'when viewer not signed in' do
- it { redirects }
+ it { is_expected.to have_http_status(:redirect) }
end
context 'when viewer is not owner of the ticket request' do
- let(:viewer) { User.make! }
- it { redirects_to new_event_ticket_request_path(ticket_request.event) }
+ let(:viewer) { create(:user) }
+
+ it { is_expected.to redirect_to(new_event_ticket_request_path(ticket_request.event)) }
end
context 'when viewer is owner of the ticket request' do
let(:viewer) { user }
+
it { succeeds }
end
context 'when viewer is the event admin' do
- let(:viewer) { EventAdmin.make!(event: ticket_request.event).user }
+ let(:viewer) { create(:event_admin, event: ticket_request.event).user }
+
it { succeeds }
end
context 'when viewer is a site admin' do
- let(:viewer) { User.make! :site_admin }
+ let(:viewer) { create(:site_admin).user }
+
it { succeeds }
end
end
describe 'POST #create' do
- let(:event) { Event.make! }
- let(:valid_params) { TicketRequest.valid_attributes event_id: event.to_param }
+ let(:event) { create(:event, tickets_require_approval: true) }
- def make_request
- post :create, event_id: event.to_param, ticket_request: valid_params
- end
+ let(:ticket_request_params) { build(:ticket_request, event:).as_json }
- context 'when event ticket sales are closed' do
- before do
- Time.warp(event.end_time + 1.hour) { make_request }
- end
+ let(:post_params) { { event_id: event.id, ticket_request: ticket_request_params } }
- it 'renders an error message' do
- flash[:error].should_not be_nil
- end
+ let(:make_request) do
+ lambda { |params = {}|
+ post_params.merge!(params) unless params.empty?
+ post :create, params: post_params
+ }
end
- context 'when viewer already signed in' do
- let(:user) { User.make! }
- let(:viewer) { user }
+ describe 'without Ventable callbacks', :ventable_disabled do
+ describe 'ticket_request_params' do
+ subject { ticket_request_params }
- it 'creates a ticket request' do
- expect { make_request }.to change { TicketRequest.count }.by(1)
+ it { is_expected.to be_a(Hash) }
+ it { is_expected.to include('event_id' => event.id) }
+ it { is_expected.not_to include('ticket_request' => ['status']) }
end
- it 'assigned the ticket request to the viewer' do
- expect { make_request }.to change { viewer.ticket_requests.count }.by(1)
+ context 'when event ticket sales are closed' do
+ it 'has no error message before the request' do
+ Timecop.freeze(event.end_time + 1.hour) do
+ make_request.call
+ expect(flash[:error]).to be('Sorry, but ticket sales have closed')
+ end
+ end
end
- end
- context 'when viewer is not signed in' do
- let(:email) { Sham.email_address.downcase }
- let(:password) { Sham.string(8) }
+ context 'when viewer already signed in' do
+ subject { make_request.call }
- let(:user_attributes) do
- {
- name: Sham.words(2),
- email: email,
- password: password
- }
- end
+ let(:viewer) { user }
- let(:valid_params) do
- TicketRequest.valid_attributes event_id: event.to_param,
- user_attributes: user_attributes
- end
+ before { TicketRequest.where(user_id: viewer.id).destroy_all }
- it 'creates a user with the specified email address' do
- make_request
- User.find_by_email(email).should_not be_nil
- end
+ describe '#create HTTP status' do
+ it { succeeds }
- it 'creates a ticket request' do
- expect { make_request }.to change { TicketRequest.count }.by(1)
- end
+ it 'has no errors' do
+ expect(flash[:error]).to be_nil
+ end
+ end
- it 'assigns the ticket request to the created user' do
- make_request
- User.find_by_email(email).ticket_requests.count.should == 1
+ describe 'database state' do
+ subject(:response) { make_request[] }
+
+ it 'creates a ticket request' do
+ expect { subject }.to(change(TicketRequest, :count))
+ end
+
+ it 'assigned the ticket request to the viewer' do
+ expect { subject }.to change { viewer.ticket_requests.count }.by(1)
+ end
+ end
end
- it 'signs in the created user' do
- make_request
- subject.current_user.should == User.find_by_email(email)
+ context 'when viewer is not signed in' do
+ subject { make_request[user_attributes] }
+
+ let(:email) { Faker::Internet.email }
+ let(:name) { "#{Faker::Name.first_name} #{Faker::Name.last_name}" }
+ let(:password) { Faker::Internet.password }
+
+ let(:user_attributes) do
+ {
+ name:,
+ email:,
+ password:,
+ password_confirmation: password
+ }
+ end
+
+ let(:created_user) { User.find_by(email:) }
+ let(:users_request_count) { -> { created_user&.ticket_requests&.count } }
+
+ it { succeeds }
+
+ it 'has no errors' do
+ expect(flash[:error]).to be_nil
+ end
+
+ describe 'ticket requests count' do
+ describe 'user creation' do
+ it 'creates a user' do
+ expect { subject }.to change(User, :count).by(1)
+ end
+ end
+
+ it 'creates a ticket request' do
+ expect { subject }.to change(TicketRequest, :count).by(1)
+ end
+
+ it 'creates a user' do
+ expect { subject }.to change(User, :count).by(1)
+ end
+
+ describe 'creates a ticket request that' do
+ before { subject }
+ it 'belongs to the created user' do
+ expect(created_user.ticket_requests.count).to be(1)
+ end
+ end
+ end
+
+ describe '#current_user' do
+ subject { controller&.current_user }
+
+ before { make_request[user_attributes] }
+
+ it { is_expected.not_to be_nil }
+
+ it { is_expected.to eql(created_user) }
+ end
end
end
end
end
+# rubocop: enable RSpec
diff --git a/spec/examples.txt b/spec/examples.txt
new file mode 100644
index 00000000..d868bd1a
--- /dev/null
+++ b/spec/examples.txt
@@ -0,0 +1,5 @@
+example_id | status | run_time |
+--------------------------------------------- | ------- | --------------- |
+./spec/helpers/home_helper_spec.rb[1:1] | pending | 0 seconds |
+./spec/requests/home_spec.rb[1:1:1] | failed | 0.3845 seconds |
+./spec/views/home/index.html.erb_spec.rb[1:1] | pending | 0.00001 seconds |
diff --git a/spec/factories/eald_payment.rb b/spec/factories/eald_payment.rb
new file mode 100644
index 00000000..2cedea1a
--- /dev/null
+++ b/spec/factories/eald_payment.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :eald_payment do
+ event
+ name { Faker::Name.name }
+ early_arrival_passes { 10 }
+ amount_charged_cents { 1000 }
+ late_departure_passes { 5 }
+ email { Faker::Internet.email }
+ stripe_charge_id { Faker::Alphanumeric.alphanumeric(number: 10) }
+ end
+end
diff --git a/spec/factories/event.rb b/spec/factories/event.rb
index 3d07d20c..abbdea33 100644
--- a/spec/factories/event.rb
+++ b/spec/factories/event.rb
@@ -2,25 +2,31 @@
FactoryBot.define do
factory :event do
- name { Sham.words(3) }
+ name { Faker::FunnyName.three_word_name }
start_time { 1.year.from_now }
- end_time { 1.year.from_now + 1.day }
+ end_time { (1.year.from_now + 1.day) }
- adult_ticket_price { Random.rand(0..100) }
- kid_ticket_price nil
- cabin_price nil
- max_adult_tickets_per_request { Random.rand(1..4) }
- max_kid_tickets_per_request nil
- max_cabins_per_request nil
- tickets_require_approval true
+ adult_ticket_price { Random.rand(100..150) }
+ kid_ticket_price { Random.rand(40..50) }
+ cabin_price { nil }
+ max_adult_tickets_per_request { Random.rand(2..4) }
+ max_kid_tickets_per_request { 2 }
+ max_cabins_per_request { nil }
+ tickets_require_approval { true }
trait :kids_tickets_available do
- kid_ticket_price { Random.rand(0..10) }
+ kid_ticket_price { Random.rand(10) }
end
trait :cabins_available do
- cabin_price { Random.rand(0..100) }
- max_cabins_per_request { Random.rand(1..2) }
+ cabin_price { Random.rand(100) }
+ max_cabins_per_request { Random.rand(1...1) }
+ end
+
+ trait :with_admins do
+ after(:create) do |event|
+ create_list(:event_admin, 2, event:)
+ end
end
end
end
diff --git a/spec/factories/payment.rb b/spec/factories/payment.rb
index 7a8c1350..5f51e928 100644
--- a/spec/factories/payment.rb
+++ b/spec/factories/payment.rb
@@ -3,6 +3,6 @@
FactoryBot.define do
factory :payment do
ticket_request
- status Payment::STATUS_IN_PROGRESS
+ status { Payment::STATUS_IN_PROGRESS }
end
end
diff --git a/spec/factories/ticket_request.rb b/spec/factories/ticket_request.rb
index 6275fb16..e30b41da 100644
--- a/spec/factories/ticket_request.rb
+++ b/spec/factories/ticket_request.rb
@@ -2,25 +2,26 @@
FactoryBot.define do
factory :ticket_request do
- adults { Random.rand(1..4) }
- kids { Random.rand(0..2) }
- cabins { Random.rand(0..2) }
+ adults { event&.max_adult_tickets_per_request || 1 }
+ kids { Random.rand(2) }
+ cabins { Random.rand(2) }
needs_assistance { [true, false].sample }
- notes { Sham.words(10) }
- agrees_to_terms true
+ notes { Faker::Lorem.paragraph }
+ agrees_to_terms { true }
+
user
event
- trait :pending do |_ticket_request|
- status TicketRequest::STATUS_PENDING
+ trait :pending do |*|
+ status { TicketRequest::STATUS_PENDING }
end
- trait :approved do |_ticket_request|
- status TicketRequest::STATUS_AWAITING_PAYMENT
+ trait :approved do |*|
+ status { TicketRequest::STATUS_AWAITING_PAYMENT }
end
- trait :declined do |_ticket_request|
- status TicketRequest::STATUS_DECLINED
+ trait :declined do |*|
+ status { TicketRequest::STATUS_DECLINED }
end
end
end
diff --git a/spec/factories/user.rb b/spec/factories/user.rb
index 6637346d..353426aa 100644
--- a/spec/factories/user.rb
+++ b/spec/factories/user.rb
@@ -2,14 +2,14 @@
FactoryBot.define do
factory :user do
- name { Sham.words(2) }
- email { Sham.email_address }
- password 'password'
+ first { Faker::Name.first_name }
+ last { Faker::Name.last_name }
+ name { "#{first} #{last}" }
+ email { Faker::Internet.email }
+ password { Faker::Internet.password(min_length: 8) }
trait :site_admin do
- after :create do |user|
- SiteAdmin.make! user: user
- end
+ site_admin
end
end
end
diff --git a/spec/fixtures/chill_sets.html b/spec/fixtures/chill_sets.html
index a10b5b7b..b4cef7f2 100644
--- a/spec/fixtures/chill_sets.html
+++ b/spec/fixtures/chill_sets.html
@@ -1,20 +1,186 @@
-Stuart Hill Robinson
-Redstickman (Michael Redstickman)
-Dummy Splash (Ian Dugas)
-Sage Farris
-Just B (Bryan Cooke)
-DJDIO (Tim Dionne)
-Whiskey Devil (Rachel Reynolds)
-DMak (Dave Mak)
-Sneaky Hippie (Jason Peace)
-Actual Rafiq (Rafiq Gulamani)
-Sinukus (Stephen Wilson)
-Dao & Pwny (Ethan Dao Clairville)
-Treedub (Chris Willmore)
-Cindy B (Cindy Berman-hayden)
-River Green (Brett Greenbaum)
-phredpdx (Fred Heutte)
-Audionista (Ana L Valle)
-JDN (J.d. Northrup)
-Expedition Alpha (Jessika Vermette And Jordan Paul)
-
+
+
+
+
+ Stuart Hill Robinson
+
+
+
+
+
+
+ Redstickman
+
+
+
+ (Michael Redstickman)
+
+
+
+
+
+ Dummy Splash
+
+
+
+ (Ian Dugas)
+
+
+
+
+
+ Sage Farris
+
+
+
+
+
+
+ Just B
+
+
+
+ (Bryan Cooke)
+
+
+
+
+
+ DJDIO
+
+
+
+ (Tim Dionne)
+
+
+
+
+
+ Whiskey Devil
+
+
+
+ (Rachel Reynolds)
+
+
+
+
+
+ DMak
+
+
+
+ (Dave Mak)
+
+
+
+
+
+ Sneaky Hippie
+
+
+
+ (Jason Peace)
+
+
+
+
+
+ Actual Rafiq
+
+
+
+ (Rafiq Gulamani)
+
+
+
+
+
+ Sinukus
+
+
+
+ (Stephen Wilson)
+
+
+
+
+
+ Dao & Pwny
+
+
+
+ (Ethan Dao Clairville)
+
+
+
+
+
+ Treedub
+
+
+
+ (Chris Willmore)
+
+
+
+
+
+ Cindy B
+
+
+
+ (Cindy Berman-hayden)
+
+
+
+
+
+ River Green
+
+
+
+ (Brett Greenbaum)
+
+
+
+
+
+ phredpdx
+
+
+
+ (Fred Heutte)
+
+
+
+
+
+ Audionista
+
+
+
+ (Ana L Valle)
+
+
+
+
+
+ JDN
+
+
+
+ (J.d. Northrup)
+
+
+
+
+
+ Expedition Alpha
+
+
+
+ (Jessika Vermette And Jordan Paul)
+
+
+
diff --git a/spec/lib/fnf/csv_reader_spec.rb b/spec/lib/fnf/csv_reader_spec.rb
index 0228fcd4..4f626fba 100644
--- a/spec/lib/fnf/csv_reader_spec.rb
+++ b/spec/lib/fnf/csv_reader_spec.rb
@@ -17,13 +17,13 @@ def increment!
end
RSpec.describe CSVReader do
- let(:path) { 'spec/fixtures/chill_sets.csv' }
+ let(:csv_file) { 'spec/fixtures/chill_sets.csv' }
let(:counter) { Counter.new }
let(:block) { -> { counter.increment! } }
- subject { described_class.new(path) }
+ subject { described_class.new(csv_file) }
- it 'should yield each line of the CSV' do
+ it 'yields each line of the CSV' do
subject.read { block[] }
expect(counter.counter).to eq(19)
end
diff --git a/spec/lib/fnf/event_reporter_spec.rb b/spec/lib/fnf/event_reporter_spec.rb
deleted file mode 100644
index 6d2735c6..00000000
--- a/spec/lib/fnf/event_reporter_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-require 'fnf/csv_reader'
-
-module FnF
- RSpec.describe EventReporter do
- describe '#event_name' do
- before do
- expect(EventReporter).to receive(:metric).once
- end
-
- it 'should invoke metric for all events' do
- Events.set_building_on_fire!
- end
- end
- end
-end
diff --git a/spec/lib/fnf/html_generator_spec.rb b/spec/lib/fnf/html_generator_spec.rb
index f8aa13ba..825b04f0 100644
--- a/spec/lib/fnf/html_generator_spec.rb
+++ b/spec/lib/fnf/html_generator_spec.rb
@@ -1,21 +1,76 @@
# frozen_string_literal: true
-require 'spec_helper'
require 'fnf/html_generator'
+# rubocop: disable Layout/HeredocIndentation
module FnF
- RSpec.describe HTMLGenerator do
- let(:generator) { described_class.new }
- before do
- generator.generate do
- html do
+ RSpec.describe HtmlGenerator do
+ let(:generator_without_simple_css) { described_class.new(simple_css: false) }
+ let(:expected_without_simple_css) do
+ <<~HTML
+
+
+
+
+
+
+ link 1
+
+
+
+
+
+
+ link 2
+
+
+
+
+
+
+ HTML
+ end
+
+ let(:generator_with_simple_css) { described_class.new(simple_css: true) }
+ let(:expected_with_simple_css) do
+ <<~HTML
+
+
+
+
+
+
+
+
+
+
+ link 1
+
+
+
+
+
+
+ link 2
+
+
+
+
+
+
+ HTML
+ end
+
+ describe 'with a simple css header' do
+ let(:html_block) do
+ generator_with_simple_css.generate(:html) do
body do
ol do
2.times do |number|
li do
strong do
- a href: 'https://google.com' do
- number
+ a href: 'https://google.com', target: '_blank' do
+ "link #{number + 1}"
end
end
end
@@ -25,14 +80,37 @@ module FnF
end
end
end
- end
- let(:expected) do
- '0 1 '
+ subject { html_block }
+
+ it { is_expected.to eq(expected_with_simple_css) }
end
- subject { generator.string }
+ describe 'without simple css header' do
+ let(:html_block) do
+ generator_without_simple_css.generate(:html) do
+ body do
+ ol do
+ 2.times do |number|
+ li do
+ strong do
+ a href: 'https://google.com', target: '_blank' do
+ "link #{number + 1}"
+ end
+ end
+ end
+ end
+ nil
+ end
+ end
+ end
+ end
+
+ subject { html_block }
- it { is_expected.to eq(expected) }
+ it { is_expected.to eq(expected_without_simple_css) }
+ end
end
end
+
+# rubocop: enable Layout/HeredocIndentation
diff --git a/spec/lib/fnf/music_submissions_spec.rb b/spec/lib/fnf/music_submissions_spec.rb
index 2a32b893..af1ce693 100644
--- a/spec/lib/fnf/music_submissions_spec.rb
+++ b/spec/lib/fnf/music_submissions_spec.rb
@@ -2,19 +2,20 @@
require 'rspec'
require 'tempfile'
-require 'spec_helper'
-RSpec.describe 'bin/music-submissions' do
+RSpec.describe 'music-submissions-links' do
+ subject { File.read(result) }
+
let(:pwd) { Dir.pwd }
let(:script) { "#{pwd}/bin/music-submission-links" }
let(:result) { Tempfile.new('result') }
let(:expected) { File.read('spec/fixtures/chill_sets.html') }
before do
+ FileUtils.rm_f(result)
+
system("bash -c '#{script} #{pwd}/spec/fixtures/chill_sets.csv > #{result.path}'")
end
- subject { File.read(result) }
-
it { is_expected.to eq(expected) }
end
diff --git a/spec/lib/fnf/submission_links_spec.rb b/spec/lib/fnf/submission_links_spec.rb
new file mode 100644
index 00000000..afa1a236
--- /dev/null
+++ b/spec/lib/fnf/submission_links_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'rspec'
+require 'tempfile'
+
+require 'fnf/submission_links'
+
+module FnF
+ RSpec.describe SubmissionLinks do
+ subject(:output_path) { output.path }
+
+ let(:submission_links) { described_class.new(csv, output) }
+ let(:pwd) { ::Dir.pwd }
+ let(:csv) { "#{pwd}/spec/fixtures/chill_sets.csv" }
+ let(:output) { ::Tempfile.new('output') }
+ let(:expected) { ::File.read("#{pwd}/spec/fixtures/chill_sets.html") }
+
+ before { submission_links.run }
+
+ it { expect(File.exist?(output_path)).to be true }
+
+ describe 'output contents' do
+ subject { File.read(output.path) }
+
+ it { is_expected.to eql(expected) }
+ end
+ end
+end
diff --git a/spec/mailers/eald_payment_mailer_spec.rb b/spec/mailers/eald_payment_mailer_spec.rb
new file mode 100644
index 00000000..cd890d80
--- /dev/null
+++ b/spec/mailers/eald_payment_mailer_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe EaldPaymentMailer do
+ let(:payment) { create(:eald_payment) }
+ let(:event) { payment.event }
+
+ describe '#eald_payment_received' do
+ subject(:mail) { described_class.eald_payment_received(payment) }
+
+ let(:expected_subject) { "Your payment for #{event.name} Early Arrival/Late Departure passes has been received" }
+
+ its(:subject) { is_expected.to eql(expected_subject) }
+
+ its(:to) { is_expected.to eql([payment.email]) }
+
+ its('body.encoded') { is_expected.to match(event.name) }
+ end
+end
diff --git a/spec/mailers/payment_mailer_spec.rb b/spec/mailers/payment_mailer_spec.rb
index c164eec1..381cd96b 100644
--- a/spec/mailers/payment_mailer_spec.rb
+++ b/spec/mailers/payment_mailer_spec.rb
@@ -1,35 +1,24 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'rails_helper'
describe PaymentMailer do
- let(:user) { User.make! }
- let(:event) { Event.make! name: 'Test Event' }
-
- let(:ticket_request) do
- TicketRequest.make! event: event,
- user: user
- end
-
- let(:payment) { Payment.make! ticket_request: ticket_request }
+ let(:ticket_request) { create(:ticket_request) }
+ let(:user) { ticket_request.user }
+ let(:event) { ticket_request.event }
+ let(:payment) { create(:payment, ticket_request:) }
describe '#payment_received' do
- let(:mail) { described_class.payment_received(payment) }
+ subject(:mail) { described_class.payment_received(payment) }
+
+ let(:expected_subject) { "Your payment for #{event.name} has been received" }
- it 'renders the subject' do
- mail.subject.should == 'Your payment for Test Event has been received'
- end
+ its(:subject) { is_expected.to eql(expected_subject) }
- it 'sends to the owner of the ticket request' do
- mail.to.should == [user.email]
- end
+ its(:to) { is_expected.to eql([user.email]) }
- it "includes the user's name" do
- mail.body.encoded.should match(user.first_name)
- end
+ its('body.encoded') { is_expected.to match(user.first_name) }
- it "includes the event's name" do
- mail.body.encoded.should match('Test Event')
- end
+ its('body.encoded') { is_expected.to match(event.name) }
end
end
diff --git a/spec/mailers/ticket_request_mailer_spec.rb b/spec/mailers/ticket_request_mailer_spec.rb
index 4b2b164a..ee13eea4 100644
--- a/spec/mailers/ticket_request_mailer_spec.rb
+++ b/spec/mailers/ticket_request_mailer_spec.rb
@@ -1,72 +1,48 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'rails_helper'
describe TicketRequestMailer do
- let(:user) { User.make! }
- let(:event) { Event.make! name: 'Test Event' }
+ let(:user) { ticket_request.user }
+ let(:event) { ticket_request.event }
+ let(:ticket_request) { create(:ticket_request, special_price: price) }
let(:price) { nil }
- let(:ticket_request) do
- TicketRequest.make! event: event,
- user: user,
- special_price: price
- end
-
describe '#request_received' do
- let(:mail) { described_class.request_received(ticket_request) }
+ subject(:mail) { described_class.request_received(ticket_request) }
- it 'renders the subject' do
- mail.subject.should == 'Test Event ticket request confirmation'
- end
+ its(:subject) { is_expected.to eql "#{event.name} ticket request confirmation" }
- it 'renders the receiver email' do
- mail.to.should == [user.email]
- end
+ its(:to) { is_expected.to eql [user.email] }
- it "includes the user's name" do
- mail.body.encoded.should match(user.first_name)
- end
+ its('body.encoded') { is_expected.to match(user.first_name) }
- it "includes the event's name" do
- mail.body.encoded.should match('Test Event')
- end
+ its('body.encoded') { is_expected.to match(event.name) }
end
describe '#request_approved' do
- let(:mail) { described_class.request_approved(ticket_request) }
+ subject(:mail) { described_class.request_approved(ticket_request) }
+
let(:body) { mail.body.encoded }
- it 'renders the subject' do
- mail.subject.should == 'Your Test Event ticket request has been approved!'
- end
+ its(:subject) { is_expected.to eq "Your #{event.name} ticket request has been approved!" }
- it 'renders the receiver email' do
- mail.to.should == [user.email]
- end
+ its(:to) { is_expected.to eq [user.email] }
- it "includes the user's name" do
- body.should match(user.first_name)
- end
+ its(:body) { is_expected.to match(user.first_name) }
- it "includes the event's name" do
- body.should match('Test Event')
- end
+ its('body.decoded') { is_expected.to match(event.name) }
context 'when the ticket request is free' do
let(:price) { 0 }
- it 'includes the correct phrase' do
- body.should match("You're good to go!")
- end
+ its(:body) { is_expected.to match("You're good to go!") }
end
context 'when the ticket request is not free' do
let(:price) { 10 }
- it 'includes the word "purchase"' do
- body.should match('purchase')
- end
+ its(:body) { is_expected.to match('purchase') }
end
end
end
diff --git a/spec/models/event_admin_spec.rb b/spec/models/event_admin_spec.rb
index 8ca95efa..bf68cc03 100644
--- a/spec/models/event_admin_spec.rb
+++ b/spec/models/event_admin_spec.rb
@@ -1,30 +1,69 @@
# frozen_string_literal: true
-require 'spec_helper'
+# == Schema Information
+#
+# Table name: event_admins
+#
+# id :bigint not null, primary key
+# created_at :datetime not null
+# updated_at :datetime not null
+# event_id :bigint
+# user_id :bigint
+#
+# Indexes
+#
+# index_event_admins_on_event_id_and_user_id (event_id,user_id) UNIQUE
+# index_event_admins_on_user_id (user_id)
+require 'rails_helper'
describe EventAdmin do
- it 'has a valid factory' do
- EventAdmin.make.should be_valid
+ subject(:event_admin) { event_admin_maker[nil_method] }
+
+ let(:nil_method) { nil }
+ let(:event) { event_admin.event }
+ let(:user) { event_admin.user }
+
+ let(:event_admin_maker) do
+ lambda do |nil_method = nil|
+ create(:event_admin).tap { |ea| nil_method ? ea.send("#{nil_method}=", nil) : ea }
+ end
end
describe 'validations' do
+ describe 'valid admin' do
+ it { is_expected.to be_valid }
+ end
+
describe '#user' do
- it { should accept_values_for(:user_id, User.make!.id) }
- it { should_not accept_values_for(:user_id, nil) }
+ describe 'nil user' do
+ subject(:event_admin_no_user) { event_admin_maker.call(:user_id) }
- context 'when the user is already an admin for the event' do
- let(:event) { Event.make! }
- let(:event_admin) { EventAdmin.make! event: event }
- let(:user) { event_admin.user }
- subject { EventAdmin.make event: event }
+ it 'invalidates nil user_id' do
+ expect { event_admin_no_user.validate! }.to raise_error(ArgumentError)
+ end
+ end
- it { should_not accept_values_for(:user_id, user.id) }
+ describe 'nil event' do
+ subject(:event_admin_without_event) { event_admin_maker.call(:event_id) }
+
+ it 'invalidates nil user_id' do
+ expect { event_admin_without_event.validate! }.to raise_error(ArgumentError)
+ end
end
- end
- describe '#event' do
- it { should accept_values_for(:event_id, Event.make!.id) }
- it { should_not accept_values_for(:event_id, nil) }
+ describe 'event admin uniqueness' do
+ it 'has already event_admin for this event' do
+ expect(described_class.where(event:, user:).count).to eq(1)
+ end
+
+ describe 'when the user is already an admin for the event' do
+ subject(:another_event_admin) { create(:event_admin, event:, user:) }
+
+ it 'raises validation error' do
+ expect { another_event_admin }.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+ end
end
end
end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index dc76120e..da74dd92 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -1,165 +1,210 @@
# frozen_string_literal: true
-require 'spec_helper'
-
-describe Event do
- it 'has a valid factory' do
- Event.make.should be_valid
- end
+# == Schema Information
+#
+# Table name: events
+#
+# id :bigint not null, primary key
+# adult_ticket_price :decimal(8, 2)
+# allow_donations :boolean default(FALSE), not null
+# allow_financial_assistance :boolean default(FALSE), not null
+# cabin_price :decimal(8, 2)
+# early_arrival_price :decimal(8, 2) default(0.0)
+# end_time :datetime
+# kid_ticket_price :decimal(8, 2)
+# late_departure_price :decimal(8, 2) default(0.0)
+# max_adult_tickets_per_request :integer
+# max_cabin_requests :integer
+# max_cabins_per_request :integer
+# max_kid_tickets_per_request :integer
+# name :string
+# photo :string
+# require_mailing_address :boolean default(FALSE), not null
+# start_time :datetime
+# ticket_requests_end_time :datetime
+# ticket_sales_end_time :datetime
+# ticket_sales_start_time :datetime
+# tickets_require_approval :boolean default(TRUE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+
+require 'rails_helper'
+
+RSpec.describe Event do
+ subject(:event) { build(:event) }
+
+ it { is_expected.to be_valid }
describe 'normalization' do
- it { should normalize(:name) }
- it { should normalize(:name).from(' Trim Spaces ').to('Trim Spaces') }
- it { should normalize(:name).from('Squish Spaces').to('Squish Spaces') }
+ it { is_expected.to normalize(:name) }
+ it { is_expected.to normalize(:name).from(' Trim Spaces ').to('Trim Spaces') }
+ it { is_expected.to normalize(:name).from('Squish Spaces').to('Squish Spaces') }
end
describe 'validations' do
describe '#name' do
- it { should accept_values_for(:name, 'CloudWatch 2013') }
- it { should_not accept_values_for(:name, nil, '', Sham.string(101)) }
+ it { is_expected.to accept_values_for(:name, 'CloudWatch 2013') }
+ it { is_expected.not_to accept_values_for(:name, nil, '', 'x' * 101) }
end
describe '#start_time' do
- it { should accept_values_for(:start_time, Time.now) }
- it { should_not accept_values_for(:start_time, nil, '') }
+ it { is_expected.to accept_values_for(:start_time, Time.zone.now) }
+ it { is_expected.not_to accept_values_for(:start_time, nil, '') }
end
describe '#end_time' do
- it { should accept_values_for(:start_time, Time.now) }
- it { should_not accept_values_for(:end_time, nil, '') }
+ it { is_expected.to accept_values_for(:start_time, Time.zone.now) }
+ it { is_expected.not_to accept_values_for(:end_time, nil, '') }
end
context 'when start time is before end time' do
- subject { Event.make start_time: Time.now, end_time: 1.day.from_now }
- it { should be_valid }
+ subject { build(:event, start_time: Time.zone.now, end_time: 1.day.from_now) }
+
+ it { is_expected.to be_valid }
end
context 'when start time is after end time' do
- subject { Event.make start_time: 1.day.from_now, end_time: Time.now }
- it { should_not be_valid }
+ subject { build(:event, start_time: 1.day.from_now, end_time: Time.zone.now) }
+
+ it { is_expected.not_to be_valid }
end
describe '#ticket_sales_start_time' do
- it { should accept_values_for(:ticket_sales_start_time, Time.now, nil, '') }
+ it { is_expected.to accept_values_for(:ticket_sales_start_time, Time.zone.now, nil, '') }
end
describe '#ticket_sales_end_time' do
- it { should accept_values_for(:ticket_sales_start_time, Time.now, nil, '') }
+ it { is_expected.to accept_values_for(:ticket_sales_start_time, Time.zone.now, nil, '') }
end
context 'when ticket sales start time is before end time' do
subject do
- Event.make ticket_sales_start_time: Time.now, ticket_sales_end_time: 1.day.from_now
+ build(:event, ticket_sales_start_time: Time.zone.now, ticket_sales_end_time: 1.day.from_now)
end
- it { should be_valid }
+ it { is_expected.to be_valid }
end
context 'when ticket sales start time is after end time' do
subject do
- Event.make ticket_sales_start_time: 1.day.from_now, ticket_sales_end_time: Time.now
+ build(:event, ticket_sales_start_time: 1.day.from_now, ticket_sales_end_time: Time.zone.now)
end
- it { should_not be_valid }
+ it { is_expected.not_to be_valid }
end
describe '#adult_ticket_price' do
- it { should accept_values_for(:adult_ticket_price, 0, 50) }
- it { should_not accept_values_for(:adult_ticket_price, nil, '', -1) }
+ it { is_expected.to accept_values_for(:adult_ticket_price, 0, 50) }
+ it { is_expected.not_to accept_values_for(:adult_ticket_price, nil, '', -1) }
end
describe '#kid_ticket_price' do
- it { should accept_values_for(:kid_ticket_price, nil, '', 0, 50) }
- it { should_not accept_values_for(:kid_ticket_price, -1) }
+ it { is_expected.to accept_values_for(:kid_ticket_price, nil, '', 0, 50) }
+ it { is_expected.not_to accept_values_for(:kid_ticket_price, -1) }
end
describe '#cabin_price' do
- it { should accept_values_for(:cabin_price, nil, '', 0, 50) }
- it { should_not accept_values_for(:cabin_price, -1) }
+ it { is_expected.to accept_values_for(:cabin_price, nil, '', 0, 50) }
+ it { is_expected.not_to accept_values_for(:cabin_price, -1) }
end
describe '#max_adult_tickets_per_request' do
- it { should accept_values_for(:max_adult_tickets_per_request, nil, '', 50) }
- it { should_not accept_values_for(:max_adult_tickets_per_request, 0, -1) }
+ it { is_expected.to accept_values_for(:max_adult_tickets_per_request, nil, '', 50) }
+ it { is_expected.not_to accept_values_for(:max_adult_tickets_per_request, 0, -1) }
end
describe '#max_kid_tickets_per_request' do
context 'when kid_ticket_price is set' do
- subject { Event.make kid_ticket_price: 10 }
- it { should accept_values_for(:max_kid_tickets_per_request, nil, '', 10) }
- it { should_not accept_values_for(:max_kid_tickets_per_request, 0, -1) }
+ subject { build(:event, kid_ticket_price: 10) }
+
+ it { is_expected.to accept_values_for(:max_kid_tickets_per_request, nil, '', 10) }
+ it { is_expected.not_to accept_values_for(:max_kid_tickets_per_request, 0, -1) }
end
context 'when kid_ticket_price is not set' do
- subject { Event.make kid_ticket_price: nil }
- it { should_not accept_values_for(:max_kid_tickets_per_request, 10) }
+ subject { build(:event, kid_ticket_price: nil) }
+
+ it { is_expected.not_to accept_values_for(:max_kid_tickets_per_request, 10) }
end
end
describe '#max_cabins_per_request' do
context 'when cabin_price is set' do
- subject { Event.make cabin_price: 10 }
- it { should accept_values_for(:max_cabins_per_request, nil, '', 10) }
- it { should_not accept_values_for(:max_cabins_per_request, 0, -1) }
+ subject { build(:event, cabin_price: 10) }
+
+ it { is_expected.to accept_values_for(:max_cabins_per_request, nil, '', 10) }
+ it { is_expected.not_to accept_values_for(:max_cabins_per_request, 0, -1) }
end
context 'when cabin_price is not set' do
- subject { Event.make cabin_price: nil }
- it { should_not accept_values_for(:max_cabins_per_request, 10) }
+ subject { build(:event, cabin_price: nil) }
+
+ it { is_expected.not_to accept_values_for(:max_cabins_per_request, 10) }
end
end
describe '#max_cabin_requests' do
context 'when cabin_price is set' do
- subject { Event.make cabin_price: 10 }
- it { should accept_values_for(:max_cabin_requests, nil, '', 10) }
- it { should_not accept_values_for(:max_cabin_requests, 0, -1) }
+ subject { build(:event, cabin_price: 10) }
+
+ it { is_expected.to accept_values_for(:max_cabin_requests, nil, '', 10) }
+ it { is_expected.not_to accept_values_for(:max_cabin_requests, 0, -1) }
end
context 'when cabin_price is not set' do
- subject { Event.make cabin_price: nil }
- it { should accept_values_for(:max_cabin_requests, nil, '') }
- it { should_not accept_values_for(:max_cabin_requests, 10) }
+ subject { build(:event, cabin_price: nil) }
+
+ it { is_expected.to accept_values_for(:max_cabin_requests, nil, '') }
+ it { is_expected.not_to accept_values_for(:max_cabin_requests, 10) }
end
end
end
describe '#admin?' do
- let(:event) { Event.make! }
subject { event.admin?(user) }
- context 'when given a normal user' do
- let(:user) { User.make! }
- it { should be false }
+ let(:event) { build(:event) }
+
+ describe 'site admin' do
+ let(:user) { create(:user, :site_admin) }
+
+ it { is_expected.to be true }
end
- context 'when given a site admin' do
- let(:user) { User.make! :site_admin }
- it { should be true }
+ context 'when given a normal user' do
+ let(:user) { build(:user) }
+
+ it { is_expected.to be false }
end
- context 'when given an event admin' do
- let(:user) { EventAdmin.make!(event: event).user }
- it { should be true }
+ context 'when given a event admin' do
+ let(:event) { create(:event, :with_admins) }
+ let(:user) { event.admins.first }
+
+ it { is_expected.to be true }
end
context 'when given an admin of another event' do
- let(:user) { EventAdmin.make!.user }
- it { should be false }
+ let(:another_event) { create(:event, :with_admins) }
+
+ let(:user) { another_event.admins.first }
+
+ it { is_expected.to be false }
end
end
describe '#cabins_available?' do
+ subject { event.cabins_available? }
+
+ let(:event) { build(:event, cabin_price:, max_cabin_requests:) }
+
let(:cabin_price) { nil }
let(:max_cabin_requests) { nil }
- let(:event) do
- Event.make! cabin_price: cabin_price, max_cabin_requests: max_cabin_requests
- end
- subject { event.cabins_available? }
context 'when no cabin price is set' do
let(:cabin_price) { nil }
- it { should be false }
+
+ it { is_expected.to be false }
end
context 'when cabin price is set' do
@@ -167,46 +212,47 @@
context 'when no maximum specified for the number of cabin requests' do
let(:max_cabin_requests) { nil }
- it { should be true }
+
+ it { is_expected.to be true }
end
context 'when a maximum is specified for the number of cabin requests' do
let(:max_cabin_requests) { 10 }
context 'when there are fewer cabins requested than the maximum' do
- it { should be true }
+ it { is_expected.to be true }
end
context 'when the number of cabins requested has met or exceeded the maximum' do
before do
- TicketRequest.make! event: event, cabins: max_cabin_requests
+ create(:ticket_request, event:, cabins: max_cabin_requests)
end
- it { should be false }
+ it { is_expected.to be false }
end
end
end
end
describe '#ticket_sales_open?' do
+ subject { event.ticket_sales_open? }
+
let(:start_time) { 1.day.from_now }
let(:end_time) { 2.days.from_now }
let(:ticket_sale_start_time) { nil }
let(:ticket_sale_end_time) { nil }
let(:event) do
- Event.make! start_time: start_time,
- end_time: end_time,
- ticket_sales_start_time: ticket_sale_start_time,
- ticket_sales_end_time: ticket_sale_end_time
+ build(:event, start_time:, end_time:,
+ ticket_sales_start_time: ticket_sale_start_time,
+ ticket_sales_end_time: ticket_sale_end_time)
end
- subject { event.ticket_sales_open? }
-
context 'when the event has ended' do
let(:start_time) { 2.days.ago }
- let(:end_time) { 1.days.ago }
- it { should be false }
+ let(:end_time) { 1.day.ago }
+
+ it { is_expected.to be false }
end
context 'when the ticket sale start time is specified' do
@@ -214,40 +260,45 @@
let(:ticket_sale_start_time) { 1.day.ago }
context 'and the ticket sale end time is not specified' do
- it { should be true }
+ it { is_expected.to be true }
end
context 'and the ticket sale end time has not passed' do
let(:ticket_sale_end_time) { 1.day.from_now }
- it { should be true }
+
+ it { is_expected.to be true }
end
context 'and the ticket sale end time has passed' do
let(:ticket_sale_end_time) { 1.hour.ago }
- it { should be false }
+
+ it { is_expected.to be false }
end
end
context 'and the start time has not passed' do
let(:ticket_sale_start_time) { 1.day.from_now }
- it { should be false }
+
+ it { is_expected.to be false }
end
end
context 'when the ticket sale start time is not specified' do
context 'and the ticket sale end time is not specified' do
- it { should be true }
+ it { is_expected.to be true }
end
context 'and the ticket sale end time is specified' do
context 'and the ticket sale end time has passed' do
let(:ticket_sale_end_time) { 1.day.ago }
- it { should be false }
+
+ it { is_expected.to be false }
end
context 'and the ticket sale end time has not passed' do
let(:ticket_sale_end_time) { 1.hour.from_now }
- it { should be true }
+
+ it { is_expected.to be true }
end
end
end
diff --git a/spec/models/site_admin_spec.rb b/spec/models/site_admin_spec.rb
index cf9a9b2e..d9158674 100644
--- a/spec/models/site_admin_spec.rb
+++ b/spec/models/site_admin_spec.rb
@@ -1,21 +1,34 @@
# frozen_string_literal: true
-require 'spec_helper'
+# == Schema Information
+#
+# Table name: site_admins
+#
+# id :bigint not null, primary key
+# created_at :datetime not null
+# updated_at :datetime not null
+# user_id :integer not null
-describe SiteAdmin do
- it 'has a valid factory' do
- SiteAdmin.make.should be_valid
- end
+require 'rails_helper'
+describe SiteAdmin do
describe 'validations' do
- describe '#user' do
- it { should accept_values_for(:user_id, User.make!.id) }
- it { should_not accept_values_for(:user_id, nil) }
+ describe '#site_admin' do
+ subject(:site_admin) { create(:site_admin) }
+
+ let(:user) { site_admin.user }
+
+ describe 'when the user is already a site admin' do
+ it { is_expected.to accept_values_for(:user, user) }
+ it { is_expected.not_to accept_values_for(:user, nil) }
+ end
- context 'when the user is already a site admin' do
- let(:user) { User.make! :site_admin }
+ context 'when the user is not a site admin, to become one it must create a mew site admin row' do
+ let(:site_admin2) { create(:site_admin, user:) }
- it { should_not accept_values_for(:user_id, user.id) }
+ it 'is not able to save' do
+ expect { site_admin2 }.to raise_error(ActiveRecord::RecordInvalid)
+ end
end
end
end
diff --git a/spec/models/ticket_request_spec.rb b/spec/models/ticket_request_spec.rb
index e8b71147..e6b980fb 100644
--- a/spec/models/ticket_request_spec.rb
+++ b/spec/models/ticket_request_spec.rb
@@ -1,166 +1,204 @@
# frozen_string_literal: true
-require 'spec_helper'
+# == Schema Information
+#
+# Table name: ticket_requests
+#
+# id :bigint not null, primary key
+# address_line1 :string(200)
+# address_line2 :string(200)
+# admin_notes :string(512)
+# adults :integer default(1), not null
+# agrees_to_terms :boolean
+# cabins :integer default(0), not null
+# car_camping :boolean
+# car_camping_explanation :string(200)
+# city :string(50)
+# country_code :string(4)
+# donation :decimal(8, 2) default(0.0)
+# early_arrival_passes :integer default(0), not null
+# guests :text
+# kids :integer default(0), not null
+# late_departure_passes :integer default(0), not null
+# needs_assistance :boolean default(FALSE), not null
+# notes :string(500)
+# previous_contribution :string(250)
+# role :string default("volunteer"), not null
+# role_explanation :string(200)
+# special_price :decimal(8, 2)
+# state :string(50)
+# status :string(1) not null
+# zip_code :string(32)
+# created_at :datetime not null
+# updated_at :datetime not null
+# event_id :integer not null
+# user_id :integer not null
+
+require 'rails_helper'
describe TicketRequest do
- it 'has a valid factory' do
- TicketRequest.make.should be_valid
- end
-
describe 'validations' do
subject { ticket_request }
describe '#user' do
- let(:ticket_request) { TicketRequest.make user: user }
+ let(:ticket_request) { build(:ticket_request, user:) }
context 'when not present' do
let(:user) { nil }
- it { should_not be_valid }
+
+ it { is_expected.not_to be_valid }
end
context 'when user exists' do
- let(:user) { User.make! }
- it { should be_valid }
- end
+ let(:user) { build(:user) }
- context 'when user is new' do
- let(:user) { User.make }
- it { should be_valid }
+ it { is_expected.to be_valid }
end
context 'when the user has other ticket requests' do
- let(:event) { Event.make! }
- let(:user) { User.make! }
- let(:ticket_request) { TicketRequest.make event: event, user: user }
+ let(:event) { create(:event) }
+ let(:user) { create(:user) }
+
+ let(:ticket_request) { build(:ticket_request, event:, user:) }
context 'and they already have a request for this event' do
- before do
- TicketRequest.make! event: event, user: user
- end
+ before { create(:ticket_request, event:, user:) }
- # TODO: Decide whether we would rather allow editing of ticket
- # requests instead of making multiple requests
- it { should be_valid }
+ it { is_expected.to be_valid }
end
context 'and they have created requests only for other events' do
- before do
- TicketRequest.make user: user
- end
+ before { create(:ticket_request, user:) }
- it { should be_valid }
+ it { is_expected.to be_valid }
end
end
end
describe '#event' do
- let(:ticket_request) { TicketRequest.make event: event }
+ let(:ticket_request) { build(:ticket_request, event:) }
context 'when not present' do
let(:event) { nil }
- it { should_not be_valid }
+
+ it { is_expected.not_to be_valid }
end
context 'when event exists' do
- let(:event) { Event.make! }
+ let(:event) { create(:event) }
- it { should be_valid }
+ it { is_expected.to be_valid }
end
end
describe '#adults' do
- let(:ticket_request) { TicketRequest.make adults: adults }
+ let(:ticket_request) { build(:ticket_request, adults:) }
context 'when not present' do
let(:adults) { nil }
- it { should_not be_valid }
+
+ it { is_expected.not_to be_valid }
end
context 'when not a number' do
let(:adults) { 'not a number' }
- it { should_not be_valid }
+
+ it { is_expected.not_to be_valid }
end
context 'when a number' do
let(:adults) { 2 }
- it { should be_valid }
+
+ it { is_expected.to be_valid }
end
end
describe '#kids' do
- let(:ticket_request) { TicketRequest.make kids: kids }
+ let(:ticket_request) { build(:ticket_request, kids:) }
context 'when not present' do
let(:kids) { nil }
- it { should be_valid }
+
+ it { is_expected.to be_valid }
end
context 'when not a number' do
let(:kids) { 'not a number' }
- it { should_not be_valid }
+
+ it { is_expected.not_to be_valid }
end
context 'when a number' do
let(:kids) { 2 }
- it { should be_valid }
+
+ it { is_expected.to be_valid }
end
end
describe '#cabins' do
- let(:ticket_request) { TicketRequest.make cabins: cabins }
+ let(:ticket_request) { build(:ticket_request, cabins:) }
context 'when not present' do
let(:cabins) { nil }
- it { should be_valid }
+
+ it { is_expected.to be_valid }
end
context 'when not a number' do
let(:cabins) { 'not a number' }
- it { should_not be_valid }
+
+ it { is_expected.not_to be_valid }
end
context 'when a number' do
let(:cabins) { 2 }
- it { should be_valid }
+
+ it { is_expected.to be_valid }
end
end
describe '#notes' do
- let(:ticket_request) { TicketRequest.make notes: notes }
+ let(:ticket_request) { build(:ticket_request, notes:) }
context 'when not present' do
let(:notes) { nil }
- it { should be_valid }
+
+ it { is_expected.to be_valid }
end
context 'when longer than 500 characters' do
- let(:notes) { Sham.string(501) }
- it { should_not be_valid }
+ let(:notes) { Faker::Alphanumeric.alpha(number: 501) }
+
+ it { is_expected.not_to be_valid }
end
describe 'normalization' do
- subject { TicketRequest.new }
- it { should normalize(:notes) }
- it { should normalize(:notes).from(' Blah ').to('Blah') }
- it { should normalize(:notes).from('Blah Blah').to('Blah Blah') }
+ subject { build(:ticket_request) }
+
+ it { is_expected.to normalize(:notes) }
+
+ it { is_expected.to normalize(:notes).from(' Blah ').to('Blah') }
+
+ it { is_expected.to normalize(:notes).from('Blah Blah').to('Blah Blah') }
end
end
describe '#special_price' do
- let(:ticket_request) { TicketRequest.make special_price: special_price }
+ let(:ticket_request) { create(:ticket_request, special_price:) }
context 'when not present' do
let(:special_price) { nil }
- it { should be_valid }
+
+ it { is_expected.to be_valid }
end
end
end
describe '#create' do
- let(:ticket_request) { TicketRequest.make! event: event }
+ let(:ticket_request) { create(:ticket_request, event:) }
context 'when the event requires approval for tickets' do
- let(:event) { Event.make! tickets_require_approval: true }
+ let(:event) { create(:event, tickets_require_approval: true) }
it 'sets the default status to pending' do
ticket_request.status.should == TicketRequest::STATUS_PENDING
@@ -168,7 +206,7 @@
end
context 'when the event does not require approval for tickets' do
- let(:event) { Event.make! tickets_require_approval: false }
+ let(:event) { create(:event, tickets_require_approval: false) }
it 'sets the default status to awaiting payment' do
ticket_request.status.should == TicketRequest::STATUS_AWAITING_PAYMENT
@@ -177,65 +215,74 @@
end
describe '#can_view?' do
- let(:requester) { User.make! }
- subject { TicketRequest.make(user: requester).can_view?(user) }
+ subject { create(:ticket_request, user: requester).can_view?(user) }
+
+ let(:requester) { create(:user) }
context 'when the user is a site admin' do
- let(:user) { User.make! :site_admin }
- it { should be true }
+ let(:user) { create(:site_admin).user }
+
+ it { is_expected.to be true }
end
context 'when the user is the ticket request creator' do
let(:user) { requester }
- it { should be true }
+
+ it { is_expected.to be true }
end
context 'when the user is anybody else' do
- let(:user) { User.make! }
- it { should be false }
+ let(:user) { create(:user) }
+
+ it { is_expected.to be false }
end
end
describe '#pending?' do
context 'when the ticket request is pending' do
- subject { TicketRequest.make(:pending).pending? }
- it { should be true }
+ subject { create(:ticket_request, status: TicketRequest::STATUS_PENDING) }
+
+ it { is_expected.to be_pending }
end
end
describe '#approved?' do
context 'when the ticket request is approved' do
- subject { TicketRequest.make(:approved).approved? }
- it { should be true }
+ subject { create(:ticket_request, status: TicketRequest::STATUS_AWAITING_PAYMENT) }
+
+ it { is_expected.to be_approved }
end
end
describe '#approve' do
- subject { TicketRequest.make(:pending, special_price: price) }
+ subject { create(:ticket_request, special_price: price, status: TicketRequest::STATUS_PENDING) }
- before do
- subject.approve
- end
+ before { subject.approve }
context 'when the ticket request has a price of zero dollars' do
let(:price) { 0 }
- it { should be_completed }
+
+ it { is_expected.to be_completed }
end
context 'when the ticket request has a price greater than zero dollars' do
let(:price) { 10 }
- it { should be_approved }
+
+ it { is_expected.to be_approved }
end
end
describe '#declined?' do
context 'when the ticket request is declined' do
- subject { TicketRequest.make(:declined).declined? }
- it { should be true }
+ subject { build(:ticket_request, status: TicketRequest::STATUS_DECLINED) }
+
+ it { is_expected.to be_declined }
end
end
describe '#price' do
+ subject { ticket_request.price }
+
let(:adult_price) { 10 }
let(:adults) { 2 }
let(:kid_price) { nil }
@@ -244,52 +291,54 @@
let(:cabins) { nil }
let(:special_price) { nil }
let(:event) do
- Event.make!(
- adult_ticket_price: adult_price,
- kid_ticket_price: kid_price,
- cabin_price: cabin_price
- )
+ build(:event,
+ adult_ticket_price: adult_price,
+ kid_ticket_price: kid_price,
+ cabin_price:)
end
let(:ticket_request) do
- TicketRequest.make(
- event: event,
- adults: adults,
- kids: kids,
- cabins: cabins,
- special_price: special_price
- )
+ build(:ticket_request,
+ event:,
+ adults:,
+ kids:,
+ cabins:,
+ special_price:)
end
- subject { ticket_request.price }
context 'when kid ticket price is not set on the event' do
- it { should == adult_price * adults }
+ it { is_expected.to eql(adult_price * adults) }
end
context 'when the ticket request includes kids' do
let(:kids) { 2 }
let(:kid_price) { 10 }
- it { should == (adult_price * adults) + (kid_price * kids) }
+
+ it { is_expected.to eql((adult_price * adults) + (kid_price * kids)) }
end
context 'when the ticket request does not include kids' do
let(:kids) { nil }
- it { should == adult_price * adults }
+
+ it { is_expected.to eql(adult_price * adults) }
end
context 'when the ticket request includes cabins' do
let(:cabins) { 2 }
let(:cabin_price) { 100 }
- it { should == (adult_price * adults) + (cabin_price * cabins) }
+
+ it { is_expected.to eql((adult_price * adults) + (cabin_price * cabins)) }
end
context 'when the ticket request does not include cabins' do
let(:cabins) { nil }
- it { should == adult_price * adults }
+
+ it { is_expected.to eql(adult_price * adults) }
end
context 'when a special price is set' do
let(:special_price) { BigDecimal(99.99, 10) }
- it { should == special_price }
+
+ it { is_expected.to eql(special_price) }
end
context 'when custom price rules are defined' do
@@ -298,26 +347,28 @@
let(:custom_price) { 5 }
before do
- PriceRule::KidsEqualTo.create! event: event,
- trigger_value: trigger_value,
+ PriceRule::KidsEqualTo.create! event:,
+ trigger_value:,
price: custom_price
end
context 'and the rule does not apply' do
let(:kids) { trigger_value - 1 }
- it { should == (adult_price * adults) + (kid_price * kids) }
+
+ it { is_expected.to eql((adult_price * adults) + (kid_price * kids)) }
end
context 'and the rule applies' do
let(:kids) { trigger_value }
- it { should == (adult_price * adults) + 5 }
+
+ it { is_expected.to eql((adult_price * adults) + 5) }
end
end
end
describe '#total_tickets' do
- it 'is the sum of the number of adults and children' do
- TicketRequest.make(adults: 3, kids: 2).total_tickets == 5
- end
+ subject(:ticket_request) { create(:ticket_request, adults: 3, kids: 2) }
+
+ its(:total_tickets) { is_expected.to be(5) }
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 56fc842b..b2b48ff1 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1,125 +1,170 @@
# frozen_string_literal: true
-require 'spec_helper'
+# == Schema Information
+#
+# Table name: users
+#
+# id :bigint not null, primary key
+# authentication_token :string(64)
+# confirmation_sent_at :datetime
+# confirmation_token :string
+# confirmed_at :datetime
+# current_sign_in_at :datetime
+# current_sign_in_ip :string
+# email :string not null
+# encrypted_password :string not null
+# failed_attempts :integer default(0)
+# first :text
+# last :text
+# last_sign_in_at :datetime
+# last_sign_in_ip :string``
+# locked_at :datetime
+# name :string(70) not null
+# remember_created_at :datetime
+# reset_password_sent_at :datetime
+# reset_password_token :string
+# sign_in_count :integer default(0)
+# unconfirmed_email :string
+# unlock_token :string
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# index_users_on_confirmation_token (confirmation_token) UNIQUE
+# index_users_on_email (email) UNIQUE
+# index_users_on_reset_password_token (reset_password_token) UNIQUE
+# index_users_on_unlock_token (unlock_token) UNIQUE
+#
+
+require 'rails_helper'
describe User do
+ let(:last) { nil }
+ let(:first) { nil }
+
it 'has a valid factory' do
- User.make.should be_valid
+ expect(build(:user)).to be_valid
end
describe 'validation' do
- describe '#name' do
- let(:user) { User.make name: name }
- subject { user }
+ describe '#first and #last' do
+ subject(:user) { build(:user, first:, last:) }
context 'when not present' do
- let(:name) { nil }
- it { should_not be_valid }
+ it { is_expected.not_to be_valid }
end
context 'when empty' do
- let(:name) { '' }
- it { should_not be_valid }
- end
+ let(:first) { '' }
+ let(:last) { '' }
- context 'when longer than 70 characters' do
- let(:name) { "#{Sham.string(35)} #{Sham.string(35)}" }
- it { should_not be_valid }
+ it { is_expected.not_to be_valid }
end
- context 'when just a first name' do
- let(:name) { 'John' }
- it { should_not be_valid }
+ context 'when only first name exists' do
+ let(:first) { Faker::Name.first_name }
+
+ it { is_expected.not_to be_valid }
end
- context 'when both first and last name' do
- let(:name) { 'John Smith' }
- it { should be_valid }
+ context 'when only the last name exists' do
+ let(:last) { 'Smith' }
+
+ it { is_expected.not_to be_valid }
end
context 'when first, middle and last name' do
- let(:name) { 'John Jacob Smith' }
- it { should be_valid }
+ let(:first) { 'John Jacob' }
+ let(:last) { 'Smith' }
+
+ it { is_expected.to be_valid }
end
context 'when multiples spaces are between names' do
- let(:name) { 'John Smith' }
+ let(:first) { 'John Jacob' }
+ let(:last) { 'Smith' }
it 'condenses multiple spaces into a single space' do
user.valid?
- user.name.should == 'John Smith'
+ user.name.should == 'John Jacob Smith'
end
- it { should be_valid }
+ it { is_expected.to be_valid }
end
context 'when leading or trailing whitespace exists' do
- let(:name) { ' John Smith ' }
+ let(:first) { ' John Jacob ' }
+ let(:last) { 'Smith' }
it 'removes the surrounding whitespace' do
user.valid?
- user.name.should == 'John Smith'
+ user.name.should == 'John Jacob Smith'
end
- it { should be_valid }
+ it { is_expected.to be_valid }
end
end
describe '#email' do
- let(:user) { User.make email: email }
- subject { user }
+ subject(:user) { build(:user, email:) }
context 'when not present' do
let(:email) { nil }
- it { should_not be_valid }
+
+ it { is_expected.not_to be_valid }
end
context 'when empty' do
let(:email) { '' }
- it { should_not be_valid }
+
+ it { is_expected.not_to be_valid }
end
context 'when email longer than 254 characters' do
- let(:email) { "#{Sham.string(243)}@example.com" }
- it { should_not be_valid }
+ let(:email) { "#{Faker::Alphanumeric.alpha(number: 254)}@example.com" }
+
+ it { is_expected.not_to be_valid }
end
context 'when not in a valid format' do
let(:email) { 'a_fake_email' }
- it { should_not be_valid }
+
+ it { is_expected.not_to be_valid }
end
context 'when in a valid format' do
let(:email) { 'email@example.com' }
- it { should be_valid }
+
+ it { is_expected.to be_valid }
end
end
end
describe '#first_name' do
- let(:first_name) { 'John' }
- let(:last_name) { 'Smith' }
- let(:user) { User.make name: [first_name, last_name].join(' ') }
+ let(:first) { 'John' }
+ let(:last) { 'Smith' }
+ let(:user) { create(:user, first:, last:) }
it 'returns the first name' do
- user.first_name.should == first_name
+ user.name.should == "#{first} #{last}"
end
end
describe '#site_admin?' do
context 'when user is a site admin' do
- let(:user) { User.make! :site_admin }
+ let(:user) { create(:site_admin).user }
it 'returns true' do
- user.should be_site_admin
+ expect(user).to be_site_admin
end
end
context 'when user is not a site admin' do
- let(:user) { User.make! }
+ let(:user) { create(:user) }
it 'returns false' do
- user.should_not be_site_admin
+ expect(user).not_to be_site_admin
end
end
end
@@ -130,11 +175,11 @@
end
context 'when user is a site admin' do
- let(:user) { User.make! :site_admin }
- let(:site_admin) { user.site_admin }
+ let(:site_admin) { create(:site_admin) }
+ let(:user) { site_admin.user }
it 'destroys the associated site admin' do
- site_admin.should be_destroyed
+ expect(site_admin).to be_destroyed
end
end
end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
new file mode 100644
index 00000000..5818ba64
--- /dev/null
+++ b/spec/rails_helper.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require_relative 'spec_helper'
+
+ENV['RAILS_ENV'] = 'test'
+
+require File.expand_path('../config/environment', __dir__)
+
+require 'rspec/rails'
+require 'accept_values_for'
+require 'stripe_mock'
+require 'timeout'
+
+def example_with_timeout(example)
+ Timeout.timeout(20) { example.run }
+end
+
+RSpec.configure do |config|
+ config.include FactoryBot::Syntax::Methods
+
+ config.before do
+ @stripe_test_helper = StripeMock.create_test_helper
+ StripeMock.start
+ end
+
+ config.after { StripeMock.stop }
+
+ config.before :example, :ventable_disabled do
+ Ventable.disable
+ end
+
+ config.after :example, :ventable_disabled do
+ Ventable.enable
+ end
+
+ config.around { |example| example_with_timeout(example) }
+
+ config.use_transactional_fixtures = true
+
+ config.expect_with(:rspec) do |c|
+ c.syntax = %i[should expect]
+ end
+end
+
+Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
diff --git a/spec/requests/home_spec.rb b/spec/requests/home_spec.rb
new file mode 100644
index 00000000..3dfc9a73
--- /dev/null
+++ b/spec/requests/home_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe 'Homes', type: :request do
+ describe 'GET /' do
+ it 'returns http success' do
+ get '/'
+ expect(response).to have_http_status(:success)
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index f2de9fbb..28fc6d65 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -3,35 +3,14 @@
ENV['RAILS_ENV'] = 'test'
require 'simplecov'
-require 'codecov' if ENV['CODECOV_TOKEN']
SimpleCov.start 'rails'
-require File.expand_path('../config/environment', __dir__)
-require 'rspec/rails'
-require 'accept_values_for'
-require 'stripe_mock'
+require 'rspec'
+require 'rspec/its'
require 'timeout'
-
-Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].sort.each { |f| require f }
-
-def example_with_timeout(example)
- ::Timeout.timeout(20) { example.run }
-end
-
-TicketBooth::Application.configure do
- config.action_mailer.delivery_method = :test
-end
+require 'faker'
RSpec.configure do |config|
- config.before(:each) do
- @stripe_test_helper = StripeMock.create_test_helper
- StripeMock.start
- end
-
- config.after(:each) { StripeMock.stop }
-
- config.around(:each) { |example| example_with_timeout(example) }
-
config.expect_with(:rspec) do |c|
c.syntax = %i[should expect]
end
diff --git a/spec/support/events.rb b/spec/support/events.rb
index 5f72be91..04e4ebc8 100644
--- a/spec/support/events.rb
+++ b/spec/support/events.rb
@@ -1,20 +1,20 @@
# frozen_string_literal: true
require 'ventable'
-require 'fnf/events/abstract_event'
+require Rails.root.join('app/classes/fnf/events/abstract_event')
+require Rails.root.join('app/classes/fnf/event_reporter')
module FnF
module Events
StructWithName = Struct.new(:name)
TARGET = StructWithName.new('Statute of Liberty')
- USER = User.new(name: 'Iynqvzve Mryrafxl')
+ USER = User.new(name: 'John Doe')
- class BuildingOnFireEvent < AbstractEvent
- end
+ class BuildingOnFireEvent < AbstractEvent; end
class << self
- def set_building_on_fire!
+ def set_building_on_fire
BuildingOnFireEvent.new(user: USER, target: TARGET).fire!
end
end
diff --git a/spec/support/factory_bot.rb b/spec/support/factory_bot.rb
deleted file mode 100644
index 2159531b..00000000
--- a/spec/support/factory_bot.rb
+++ /dev/null
@@ -1,119 +0,0 @@
-# frozen_string_literal: true
-
-require 'factory_bot'
-
-# Convenience methods added to invoke FactoryBot factories by sending
-# messages directly to ActiveRecord classes.
-#
-# We use these rather than the one provided by FactoryBot itself
-# (factory_bot/syntax/make) because we want wrappers for all four methods,
-# not just "create", and we also want the option of customising the returned
-# instance via a block.
-module ActiveRecord
- class Base
- class << self
- # Wrapper for FactoryBot.build
- def make(*args, &block)
- make_with(name.underscore, *args, &block)
- end
-
- # Like #make, but allows the caller to explicitly specify a factory name.
- def make_with(factory_name, *args, &block)
- factory_bot_delegate :build, factory_name, *args, &block
- end
-
- # Wrapper for FactoryBot.build_list
- def make_list(count, *args, &block)
- factory_bot_delegate :build_list, name.underscore, count, *args, &block
- end
-
- # Wrapper for FactoryBot.create
- def make!(*args, &block)
- make_with!(name.underscore, *args, &block)
- end
-
- # Like #make!, but allows the caller to explicitly specify a factory name.
- def make_with!(factory_name, *args, &block)
- factory_bot_delegate :create, factory_name, *args, &block
- end
-
- # Wrapper for FactoryBot.build_list
- def make_list!(count, *args, &block)
- factory_bot_delegate :create_list, name.underscore, count, *args, &block
- end
-
- # Wrapper for FactoryBot.attributes_for
- def valid_attributes(*args, &block)
- valid_attributes_with(name.underscore, *args, &block)
- end
-
- # Like #valid_attributes, but allows the caller to explicitly specify a
- # factory name.
- def valid_attributes_with(factory_name, *args, &block)
- factory_bot_delegate :attributes_for, factory_name, *args, &block
- end
-
- private
-
- def factory_bot_delegate(method, factory_name, *args)
- object = FactoryBot.__send__(method, factory_name, *args)
- yield object if block_given?
- object
- end
- end
- end
-end
-
-# Shams were a feature of Factory Bot until version 4.0. This is a thin wrapper
-# that allows us to continue using Shams (as they're very simple).
-module Sham
- def self.method_missing(method, *args, &block)
- raise ArgumentError, "Sham.#{method} called with #{args.inspect}" if args.any?
-
- if block_given? # defining a Sham
- FactoryBot.define do
- sequence method, &block
- end
- else # using a Sham
- FactoryBot.generate method
- end
- end
-end
-
-def Sham.email_address
- "#{Sham.string(12)}@#{Sham.string(10)}.com"
-end
-
-Sham.positive_integer { |n| n }
-
-Sham.street_address do |n|
- "#{n} #{Sham.words(2)} Street"
-end
-
-# Produce a zip code, starting at 10,000.
-Sham.zip_code do |n|
- format('%.5d', (n + 10_000))
-end
-
-# Generate a random string of length k.
-def Sham.string(k)
- chars = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
- 1.upto(k).reduce('') { |string, _| string + chars[rand(chars.size - 1)] }
-end
-
-# Generate a random but prounounceable word.
-# Modified from: http://snipplr.com/view.php?codeview&id=1247
-def Sham.word(n = [2, 4, 6].sample)
- c = %w[b c d f g h j k l m n p qu r s t v w x z] +
- %w[ch cr fr ph pr sh sl sp st th tr]
- v = %w[a e i o u y]
- e = %w[ch nd ng nk nt ph rd sh sp st]
- suf = rand > 0.5 ? e.sample : ''
- f = false
- (0...n).map { (f = !f) ? c.sample : v.sample }.join + suf
-end
-
-# Generate a sentence of n random but pronounceable words.
-def Sham.words(n = 1)
- (0...n).map { Sham.word }.join(' ').capitalize
-end
diff --git a/spec/support/response_helpers.rb b/spec/support/response_helpers.rb
index 1b675b22..ad183f31 100644
--- a/spec/support/response_helpers.rb
+++ b/spec/support/response_helpers.rb
@@ -4,15 +4,15 @@
# write.
module ResponseHelpers
def succeeds
- response.status.should eq 200
+ expect(response).to have_http_status(:ok)
end
def redirects
- response.should be_redirect
+ expect(response).to have_http_status(:redirect)
end
def redirects_to(path)
- response.should redirect_to(path)
+ expect(response).to redirect_to(path)
end
end
diff --git a/spec/support/time_extensions.rb b/spec/support/time_extensions.rb
index 10d69ab8..0903dbf5 100644
--- a/spec/support/time_extensions.rb
+++ b/spec/support/time_extensions.rb
@@ -5,14 +5,14 @@ class Time
class << self
alias old_now now
def now
- @fake_now && !@fake_now.empty? ? @fake_now.last.dup : old_now
+ @fake_now.present? ? @fake_now.last.dup : old_now
end
def now=(_t)
raise 'Time.now=() is deprecated, use Time.warp with a block instead'
end
- def warp(t = Time.now)
+ def warp(t = Time.zone.now)
raise ArgumentError, 'Time.warp requires a block' unless block_given?
raise ArgumentError, 'Time.warp passed nil' if t.nil?
raise ArgumentError, '`t` must respond to #to_time' unless t.respond_to?(:to_time)
@@ -26,7 +26,7 @@ def warp(t = Time.now)
end
def passes(t)
- raise 'Time.passes may only be used inside a Time.warp block' if @fake_now.nil? || @fake_now.empty?
+ raise 'Time.passes may only be used inside a Time.warp block' if @fake_now.blank?
old_fake = @fake_now.pop || old_now
@fake_now.push(old_fake + t)
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..f427c44b
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "declaration": false,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "lib": ["es2019", "dom"],
+ "jsx": "react",
+ "module": "es6",
+ "moduleResolution": "node",
+ "baseUrl": ".",
+ "paths": {
+ "*": ["node_modules/*", "app/packs/*"]
+ },
+ "sourceMap": true,
+ "target": "es2019",
+ "noEmit": true
+ },
+ "exclude": ["**/*.spec.ts", "node_modules", "vendor", "public"],
+ "compileOnSave": false
+}
diff --git a/vendor/.keep b/vendor/.keep
new file mode 100644
index 00000000..e69de29b
diff --git a/vendor/assets/javascripts/bootstrap-datetimepicker.min.js b/vendor/assets/javascripts/bootstrap-datetimepicker.min.js
deleted file mode 100644
index 92f1f89e..00000000
--- a/vendor/assets/javascripts/bootstrap-datetimepicker.min.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * @license
- * =========================================================
- * bootstrap-datetimepicker.js
- * http://www.eyecon.ro/bootstrap-datepicker
- * =========================================================
- * Copyright 2012 Stefan Petre
- *
- * Contributions:
- * - Andrew Rowls
- * - Thiago de Arruda
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * =========================================================
- */
-(function($){var smartPhone=window.orientation!=undefined;var DateTimePicker=function(element,options){this.id=dpgId++;this.init(element,options)};var dateToDate=function(dt){if(typeof dt==="string"){return new Date(dt)}return dt};DateTimePicker.prototype={constructor:DateTimePicker,init:function(element,options){var icon;if(!(options.pickTime||options.pickDate))throw new Error("Must choose at least one picker");this.options=options;this.$element=$(element);this.language=options.language in dates?options.language:"en";this.pickDate=options.pickDate;this.pickTime=options.pickTime;this.isInput=this.$element.is("input");this.component=false;if(this.$element.is(".input-append")||this.$element.is(".input-prepend"))this.component=this.$element.find(".add-on");this.format=options.format;if(!this.format){if(this.isInput)this.format=this.$element.data("format");else this.format=this.$element.find("input").data("format");if(!this.format)this.format="MM/dd/yyyy"}this._compileFormat();if(this.component){icon=this.component.find("i")}if(this.pickTime){if(icon&&icon.length)this.timeIcon=icon.data("time-icon");if(!this.timeIcon)this.timeIcon="icon-time";icon.addClass(this.timeIcon)}if(this.pickDate){if(icon&&icon.length)this.dateIcon=icon.data("date-icon");if(!this.dateIcon)this.dateIcon="icon-calendar";icon.removeClass(this.timeIcon);icon.addClass(this.dateIcon)}this.widget=$(getTemplate(this.timeIcon,options.pickDate,options.pickTime,options.pick12HourFormat,options.pickSeconds)).appendTo("body");this.minViewMode=options.minViewMode||this.$element.data("date-minviewmode")||0;if(typeof this.minViewMode==="string"){switch(this.minViewMode){case"months":this.minViewMode=1;break;case"years":this.minViewMode=2;break;default:this.minViewMode=0;break}}this.viewMode=options.viewMode||this.$element.data("date-viewmode")||0;if(typeof this.viewMode==="string"){switch(this.viewMode){case"months":this.viewMode=1;break;case"years":this.viewMode=2;break;default:this.viewMode=0;break}}this.startViewMode=this.viewMode;this.weekStart=options.weekStart||this.$element.data("date-weekstart")||0;this.weekEnd=this.weekStart===0?6:this.weekStart-1;this.setStartDate(options.startDate||this.$element.data("date-startdate"));this.setEndDate(options.endDate||this.$element.data("date-enddate"));this.fillDow();this.fillMonths();this.fillHours();this.fillMinutes();this.fillSeconds();this.update();this.showMode();this._attachDatePickerEvents()},show:function(e){this.widget.show();this.height=this.component?this.component.outerHeight():this.$element.outerHeight();this.place();this.$element.trigger({type:"show",date:this._date});this._attachDatePickerGlobalEvents();if(e){e.stopPropagation();e.preventDefault()}},disable:function(){this.$element.find("input").prop("disabled",true);this._detachDatePickerEvents()},enable:function(){this.$element.find("input").prop("disabled",false);this._attachDatePickerEvents()},hide:function(){var collapse=this.widget.find(".collapse");for(var i=0;i'+dates[this.language].daysMin[dowCnt++%7]+""}html+="";this.widget.find(".datepicker-days thead").append(html)},fillMonths:function(){var html="";var i=0;while(i<12){html+=''+dates[this.language].monthsShort[i++]+" "}this.widget.find(".datepicker-months td").append(html)},fillDate:function(){var year=this.viewDate.getUTCFullYear();var month=this.viewDate.getUTCMonth();var currentDate=UTCDate(this._date.getUTCFullYear(),this._date.getUTCMonth(),this._date.getUTCDate(),0,0,0,0);var startYear=typeof this.startDate==="object"?this.startDate.getUTCFullYear():-Infinity;var startMonth=typeof this.startDate==="object"?this.startDate.getUTCMonth():-1;var endYear=typeof this.endDate==="object"?this.endDate.getUTCFullYear():Infinity;var endMonth=typeof this.endDate==="object"?this.endDate.getUTCMonth():12;this.widget.find(".datepicker-days").find(".disabled").removeClass("disabled");this.widget.find(".datepicker-months").find(".disabled").removeClass("disabled");this.widget.find(".datepicker-years").find(".disabled").removeClass("disabled");this.widget.find(".datepicker-days th:eq(1)").text(dates[this.language].months[month]+" "+year);var prevMonth=UTCDate(year,month-1,28,0,0,0,0);var day=DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(),prevMonth.getUTCMonth());prevMonth.setUTCDate(day);prevMonth.setUTCDate(day-(prevMonth.getUTCDay()-this.weekStart+7)%7);if(year==startYear&&month<=startMonth||year=endMonth||year>endYear){this.widget.find(".datepicker-days th:eq(2)").addClass("disabled")}var nextMonth=new Date(prevMonth.valueOf());nextMonth.setUTCDate(nextMonth.getUTCDate()+42);nextMonth=nextMonth.valueOf();var html=[];var clsName;while(prevMonth.valueOf()")}clsName="";if(prevMonth.getUTCFullYear()year||prevMonth.getUTCFullYear()==year&&prevMonth.getUTCMonth()>month){clsName+=" new"}if(prevMonth.valueOf()===currentDate.valueOf()){clsName+=" active"}if(prevMonth.valueOf()+864e5<=this.startDate){clsName+=" disabled"}if(prevMonth.valueOf()>this.endDate){clsName+=" disabled"}html.push(''+prevMonth.getUTCDate()+" ");if(prevMonth.getUTCDay()===this.weekEnd){html.push("")}prevMonth.setUTCDate(prevMonth.getUTCDate()+1)}this.widget.find(".datepicker-days tbody").empty().append(html.join(""));var currentYear=this._date.getUTCFullYear();var months=this.widget.find(".datepicker-months").find("th:eq(1)").text(year).end().find("span").removeClass("active");if(currentYear===year){months.eq(this._date.getUTCMonth()).addClass("active")}if(currentYear-1endYear){this.widget.find(".datepicker-months th:eq(2)").addClass("disabled")}for(var i=0;i<12;i++){if(year==startYear&&startMonth>i||yearendYear){$(months[i]).addClass("disabled")}}html="";year=parseInt(year/10,10)*10;var yearCont=this.widget.find(".datepicker-years").find("th:eq(1)").text(year+"-"+(year+9)).end().find("td");this.widget.find(".datepicker-years").find("th").removeClass("disabled");if(startYear>year){this.widget.find(".datepicker-years").find("th:eq(0)").addClass("disabled")}if(endYearendYear?" disabled":"")+'">'+year+"";year+=1}yearCont.html(html)},fillHours:function(){var table=this.widget.find(".timepicker .timepicker-hours table");table.parent().hide();var html="";if(this.options.pick12HourFormat){var current=1;for(var i=0;i<3;i+=1){html+="";for(var j=0;j<4;j+=1){var c=current.toString();html+=''+padLeft(c,2,"0")+" ";current++}html+=" "}}else{var current=0;for(var i=0;i<6;i+=1){html+="";for(var j=0;j<4;j+=1){var c=current.toString();html+=''+padLeft(c,2,"0")+" ";current++}html+=" "}}table.html(html)},fillMinutes:function(){var table=this.widget.find(".timepicker .timepicker-minutes table");table.parent().hide();var html="";var current=0;for(var i=0;i<5;i++){html+="";for(var j=0;j<4;j+=1){var c=current.toString();html+=''+padLeft(c,2,"0")+" ";current+=3}html+=" "}table.html(html)},fillSeconds:function(){var table=this.widget.find(".timepicker .timepicker-seconds table");table.parent().hide();var html="";var current=0;for(var i=0;i<5;i++){html+="";for(var j=0;j<4;j+=1){var c=current.toString();html+=''+padLeft(c,2,"0")+" ";current+=3}html+=" "}table.html(html)},fillTime:function(){if(!this._date)return;var timeComponents=this.widget.find(".timepicker span[data-time-component]");var table=timeComponents.closest("table");var is12HourFormat=this.options.pick12HourFormat;var hour=this._date.getUTCHours();var period="AM";if(is12HourFormat){if(hour>=12)period="PM";if(hour===0)hour=12;else if(hour!=12)hour=hour%12;this.widget.find(".timepicker [data-action=togglePeriod]").text(period)}hour=padLeft(hour.toString(),2,"0");var minute=padLeft(this._date.getUTCMinutes().toString(),2,"0");var second=padLeft(this._date.getUTCSeconds().toString(),2,"0");timeComponents.filter("[data-time-component=hours]").text(hour);timeComponents.filter("[data-time-component=minutes]").text(minute);timeComponents.filter("[data-time-component=seconds]").text(second)},click:function(e){e.stopPropagation();e.preventDefault();this._unset=false;var target=$(e.target).closest("span, td, th");if(target.length===1){if(!target.is(".disabled")){switch(target[0].nodeName.toLowerCase()){case"th":switch(target[0].className){case"switch":this.showMode(1);break;case"prev":case"next":var vd=this.viewDate;var navFnc=DPGlobal.modes[this.viewMode].navFnc;var step=DPGlobal.modes[this.viewMode].navStep;if(target[0].className==="prev")step=step*-1;vd["set"+navFnc](vd["get"+navFnc]()+step);this.fillDate();this.set();break}break;case"span":if(target.is(".month")){var month=target.parent().find("span").index(target);this.viewDate.setUTCMonth(month)}else{var year=parseInt(target.text(),10)||0;this.viewDate.setUTCFullYear(year)}if(this.viewMode!==0){this._date=UTCDate(this.viewDate.getUTCFullYear(),this.viewDate.getUTCMonth(),this.viewDate.getUTCDate(),this._date.getUTCHours(),this._date.getUTCMinutes(),this._date.getUTCSeconds(),this._date.getUTCMilliseconds());this.notifyChange()}this.showMode(-1);this.fillDate();this.set();break;case"td":if(target.is(".day")){var day=parseInt(target.text(),10)||1;var month=this.viewDate.getUTCMonth();var year=this.viewDate.getUTCFullYear();if(target.is(".old")){if(month===0){month=11;year-=1}else{month-=1}}else if(target.is(".new")){if(month==11){month=0;year+=1}else{month+=1}}this._date=UTCDate(year,month,day,this._date.getUTCHours(),this._date.getUTCMinutes(),this._date.getUTCSeconds(),this._date.getUTCMilliseconds());this.viewDate=UTCDate(year,month,Math.min(28,day),0,0,0,0);this.fillDate();this.set();this.notifyChange()}break}}}},actions:{incrementHours:function(e){this._date.setUTCHours(this._date.getUTCHours()+1)},incrementMinutes:function(e){this._date.setUTCMinutes(this._date.getUTCMinutes()+1)},incrementSeconds:function(e){this._date.setUTCSeconds(this._date.getUTCSeconds()+1)},decrementHours:function(e){this._date.setUTCHours(this._date.getUTCHours()-1)},decrementMinutes:function(e){this._date.setUTCMinutes(this._date.getUTCMinutes()-1)},decrementSeconds:function(e){this._date.setUTCSeconds(this._date.getUTCSeconds()-1)},togglePeriod:function(e){var hour=this._date.getUTCHours();if(hour>=12)hour-=12;else hour+=12;this._date.setUTCHours(hour)},showPicker:function(){this.widget.find(".timepicker > div:not(.timepicker-picker)").hide();this.widget.find(".timepicker .timepicker-picker").show()},showHours:function(){this.widget.find(".timepicker .timepicker-picker").hide();this.widget.find(".timepicker .timepicker-hours").show()},showMinutes:function(){this.widget.find(".timepicker .timepicker-picker").hide();this.widget.find(".timepicker .timepicker-minutes").show()},showSeconds:function(){this.widget.find(".timepicker .timepicker-picker").hide();this.widget.find(".timepicker .timepicker-seconds").show()},selectHour:function(e){var tgt=$(e.target);var value=parseInt(tgt.text(),10);if(this.options.pick12HourFormat){var current=this._date.getUTCHours();if(current>=12){if(value!=12)value=(value+12)%24}else{if(value===12)value=0;else value=value%12}}this._date.setUTCHours(value);this.actions.showPicker.call(this)},selectMinute:function(e){var tgt=$(e.target);var value=parseInt(tgt.text(),10);this._date.setUTCMinutes(value);this.actions.showPicker.call(this)},selectSecond:function(e){var tgt=$(e.target);var value=parseInt(tgt.text(),10);this._date.setUTCSeconds(value);this.actions.showPicker.call(this)}},doAction:function(e){e.stopPropagation();e.preventDefault();if(!this._date)this._date=UTCDate(1970,0,0,0,0,0,0);var action=$(e.currentTarget).data("action");var rv=this.actions[action].apply(this,arguments);this.set();this.fillTime();this.notifyChange();return rv},stopEvent:function(e){e.stopPropagation();e.preventDefault()},keydown:function(e){var self=this,k=e.which,input=$(e.target);if(k==8||k==46){setTimeout(function(){self._resetMaskPos(input)})}},keypress:function(e){var k=e.which;if(k==8||k==46){return}var input=$(e.target);var c=String.fromCharCode(k);var val=input.val()||"";val+=c;var mask=this._mask[this._maskPos];if(!mask){return false}if(mask.end!=val.length){return}if(!mask.pattern.test(val.slice(mask.start))){val=val.slice(0,val.length-1);while((mask=this._mask[this._maskPos])&&mask.character){val+=mask.character;this._maskPos++}val+=c;if(mask.end!=val.length){input.val(val);return false}else{if(!mask.pattern.test(val.slice(mask.start))){input.val(val.slice(0,mask.start));return false}else{input.val(val);this._maskPos++;return false}}}else{this._maskPos++}},change:function(e){var input=$(e.target);var val=input.val();if(this._formatPattern.test(val)){this.update();this.setValue(this._date.getTime());this.notifyChange();this.set()}else if(val&&val.trim()){this.setValue(this._date.getTime());if(this._date)this.set();else input.val("")}else{if(this._date){this.setValue(null);this.notifyChange();this._unset=true}}this._resetMaskPos(input)},showMode:function(dir){if(dir){this.viewMode=Math.max(this.minViewMode,Math.min(2,this.viewMode+dir))}this.widget.find(".datepicker > div").hide().filter(".datepicker-"+DPGlobal.modes[this.viewMode].clsName).show()},destroy:function(){this._detachDatePickerEvents();this._detachDatePickerGlobalEvents();this.widget.remove();this.$element.removeData("datetimepicker");this.component.removeData("datetimepicker")},formatDate:function(d){return this.format.replace(formatReplacer,function(match){var methodName,property,rv,len=match.length;if(match==="ms")len=1;property=dateFormatComponents[match].property;if(property==="Hours12"){rv=d.getUTCHours();if(rv===0)rv=12;else if(rv!==12)rv=rv%12}else if(property==="Period12"){if(d.getUTCHours()>=12)return"PM";else return"AM"}else{methodName="get"+property;rv=d[methodName]()}if(methodName==="getUTCMonth")rv=rv+1;if(methodName==="getUTCYear")rv=rv+1900-2e3;return padLeft(rv.toString(),len,"0")})},parseDate:function(str){var match,i,property,methodName,value,parsed={};if(!(match=this._formatPattern.exec(str)))return null;for(i=1;ival.length){this._maskPos=i;break}else if(this._mask[i].end===val.length){this._maskPos=i+1;break}}},_finishParsingDate:function(parsed){var year,month,date,hours,minutes,seconds,milliseconds;year=parsed.UTCFullYear;if(parsed.UTCYear)year=2e3+parsed.UTCYear;if(!year)year=1970;if(parsed.UTCMonth)month=parsed.UTCMonth-1;else month=0;date=parsed.UTCDate||1;hours=parsed.UTCHours||0;minutes=parsed.UTCMinutes||0;seconds=parsed.UTCSeconds||0;milliseconds=parsed.UTCMilliseconds||0;if(parsed.Hours12){hours=parsed.Hours12}if(parsed.Period12){if(/pm/i.test(parsed.Period12)){if(hours!=12)hours=(hours+12)%24}else{hours=hours%12}}return UTCDate(year,month,date,hours,minutes,seconds,milliseconds)},_compileFormat:function(){var match,component,components=[],mask=[],str=this.format,propertiesByIndex={},i=0,pos=0;while(match=formatComponent.exec(str)){component=match[0];if(component in dateFormatComponents){i++;propertiesByIndex[i]=dateFormatComponents[component].property;components.push("\\s*"+dateFormatComponents[component].getPattern(this)+"\\s*");mask.push({pattern:new RegExp(dateFormatComponents[component].getPattern(this)),property:dateFormatComponents[component].property,start:pos,end:pos+=component.length})}else{components.push(escapeRegExp(component));mask.push({pattern:new RegExp(escapeRegExp(component)),character:component,start:pos,end:++pos})}str=str.slice(component.length)}this._mask=mask;this._maskPos=0;this._formatPattern=new RegExp("^\\s*"+components.join("")+"\\s*$");this._propertiesByIndex=propertiesByIndex},_attachDatePickerEvents:function(){var self=this;this.widget.on("click",".datepicker *",$.proxy(this.click,this));this.widget.on("click","[data-action]",$.proxy(this.doAction,this));this.widget.on("mousedown",$.proxy(this.stopEvent,this));if(this.pickDate&&this.pickTime){this.widget.on("click.togglePicker",".accordion-toggle",function(e){e.stopPropagation();var $this=$(this);var $parent=$this.closest("ul");var expanded=$parent.find(".collapse.in");var closed=$parent.find(".collapse:not(.in)");if(expanded&&expanded.length){var collapseData=expanded.data("collapse");if(collapseData&&collapseData.transitioning)return;expanded.collapse("hide");closed.collapse("show");$this.find("i").toggleClass(self.timeIcon+" "+self.dateIcon);self.$element.find(".add-on i").toggleClass(self.timeIcon+" "+self.dateIcon)}})}if(this.isInput){this.$element.on({focus:$.proxy(this.show,this),change:$.proxy(this.change,this)});if(this.options.maskInput){this.$element.on({keydown:$.proxy(this.keydown,this),keypress:$.proxy(this.keypress,this)})}}else{this.$element.on({change:$.proxy(this.change,this)},"input");if(this.options.maskInput){this.$element.on({keydown:$.proxy(this.keydown,this),keypress:$.proxy(this.keypress,this)},"input")}if(this.component){this.component.on("click",$.proxy(this.show,this))}else{this.$element.on("click",$.proxy(this.show,this))}}},_attachDatePickerGlobalEvents:function(){$(window).on("resize.datetimepicker"+this.id,$.proxy(this.place,this));if(!this.isInput){$(document).on("mousedown.datetimepicker"+this.id,$.proxy(this.hide,this))}},_detachDatePickerEvents:function(){this.widget.off("click",".datepicker *",this.click);this.widget.off("click","[data-action]");this.widget.off("mousedown",this.stopEvent);if(this.pickDate&&this.pickTime){this.widget.off("click.togglePicker")}if(this.isInput){this.$element.off({focus:this.show,change:this.change});if(this.options.maskInput){this.$element.off({keydown:this.keydown,keypress:this.keypress})}}else{this.$element.off({change:this.change},"input");if(this.options.maskInput){this.$element.off({keydown:this.keydown,keypress:this.keypress},"input")}if(this.component){this.component.off("click",this.show)}else{this.$element.off("click",this.show)}}},_detachDatePickerGlobalEvents:function(){$(window).off("resize.datetimepicker"+this.id);if(!this.isInput){$(document).off("mousedown.datetimepicker"+this.id)}}};$.fn.datetimepicker=function(option,val){return this.each(function(){var $this=$(this),data=$this.data("datetimepicker"),options=typeof option==="object"&&option;if(!data){$this.data("datetimepicker",data=new DateTimePicker(this,$.extend({},$.fn.datetimepicker.defaults,options)))}if(typeof option==="string")data[option](val)})};$.fn.datetimepicker.defaults={maskInput:false,pickDate:true,pickTime:true,pick12HourFormat:false,pickSeconds:true,startDate:-Infinity,endDate:Infinity};$.fn.datetimepicker.Constructor=DateTimePicker;var dpgId=0;var dates=$.fn.datetimepicker.dates={en:{days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],daysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat","Sun"],daysMin:["Su","Mo","Tu","We","Th","Fr","Sa","Su"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]}};var dateFormatComponents={dd:{property:"UTCDate",getPattern:function(){return"(0?[1-9]|[1-2][0-9]|3[0-1])\\b"}},MM:{property:"UTCMonth",getPattern:function(){return"(0?[1-9]|1[0-2])\\b"}},yy:{property:"UTCYear",getPattern:function(){return"(\\d{2})\\b"}},yyyy:{property:"UTCFullYear",getPattern:function(){return"(\\d{4})\\b"}},hh:{property:"UTCHours",getPattern:function(){return"(0?[0-9]|1[0-9]|2[0-3])\\b"}},mm:{property:"UTCMinutes",getPattern:function(){return"(0?[0-9]|[1-5][0-9])\\b"}},ss:{property:"UTCSeconds",getPattern:function(){return"(0?[0-9]|[1-5][0-9])\\b"}},ms:{property:"UTCMilliseconds",getPattern:function(){return"([0-9]{1,3})\\b"}},HH:{property:"Hours12",getPattern:function(){return"(0?[1-9]|1[0-2])\\b"}},PP:{property:"Period12",getPattern:function(){return"(AM|PM|am|pm|Am|aM|Pm|pM)\\b"}}};var keys=[];for(var k in dateFormatComponents)keys.push(k);keys[keys.length-1]+="\\b";keys.push(".");var formatComponent=new RegExp(keys.join("\\b|"));keys.pop();var formatReplacer=new RegExp(keys.join("\\b|"),"g");function escapeRegExp(str){return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}function padLeft(s,l,c){if(l