mirror of
https://github.com/System-End/theseus.git
synced 2026-04-19 15:28:19 +00:00
INITIAL GOSH DANG COMMIT :3333
This commit is contained in:
commit
c405c68a7d
657 changed files with 30205 additions and 0 deletions
58
.annotaterb.yml
Normal file
58
.annotaterb.yml
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
:position: before
|
||||
:position_in_additional_file_patterns: before
|
||||
:position_in_class: before
|
||||
:position_in_factory: before
|
||||
:position_in_fixture: before
|
||||
:position_in_routes: before
|
||||
:position_in_serializer: before
|
||||
:position_in_test: before
|
||||
:classified_sort: true
|
||||
:exclude_controllers: true
|
||||
:exclude_factories: false
|
||||
:exclude_fixtures: false
|
||||
:exclude_helpers: true
|
||||
:exclude_scaffolds: true
|
||||
:exclude_serializers: false
|
||||
:exclude_sti_subclasses: false
|
||||
:exclude_tests: false
|
||||
:force: false
|
||||
:format_markdown: false
|
||||
:format_rdoc: false
|
||||
:format_yard: false
|
||||
:frozen: false
|
||||
:ignore_model_sub_dir: false
|
||||
:ignore_unknown_models: false
|
||||
:include_version: false
|
||||
:show_check_constraints: false
|
||||
:show_complete_foreign_keys: false
|
||||
:show_foreign_keys: true
|
||||
:show_indexes: true
|
||||
:simple_indexes: false
|
||||
:sort: false
|
||||
:timestamp: false
|
||||
:trace: false
|
||||
:with_comment: true
|
||||
:with_column_comments: true
|
||||
:with_table_comments: true
|
||||
:active_admin: false
|
||||
:command:
|
||||
:debug: false
|
||||
:hide_default_column_types: ''
|
||||
:hide_limit_column_types: ''
|
||||
:ignore_columns:
|
||||
:ignore_routes:
|
||||
:models: true
|
||||
:routes: false
|
||||
:skip_on_db_migrate: false
|
||||
:target_action: :do_annotations
|
||||
:wrapper:
|
||||
:wrapper_close:
|
||||
:wrapper_open:
|
||||
:classes_default_to_s: []
|
||||
:additional_file_patterns: []
|
||||
:model_dir:
|
||||
- app/models
|
||||
:require: []
|
||||
:root_dir:
|
||||
- ''
|
||||
51
.dockerignore
Normal file
51
.dockerignore
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.
|
||||
|
||||
# Ignore git directory.
|
||||
/.git/
|
||||
/.gitignore
|
||||
|
||||
# Ignore bundler config.
|
||||
/.bundle
|
||||
|
||||
# Ignore all environment files.
|
||||
/.env*
|
||||
|
||||
# Ignore all default key files.
|
||||
/config/master.key
|
||||
/config/credentials/*.key
|
||||
|
||||
# Ignore all logfiles and tempfiles.
|
||||
/log/*
|
||||
/tmp/*
|
||||
!/log/.keep
|
||||
!/tmp/.keep
|
||||
|
||||
# Ignore pidfiles, but keep the directory.
|
||||
/tmp/pids/*
|
||||
!/tmp/pids/.keep
|
||||
|
||||
# Ignore storage (uploaded files in development and any SQLite databases).
|
||||
/storage/*
|
||||
!/storage/.keep
|
||||
/tmp/storage/*
|
||||
!/tmp/storage/.keep
|
||||
|
||||
# Ignore assets.
|
||||
/node_modules/
|
||||
/app/assets/builds/*
|
||||
!/app/assets/builds/.keep
|
||||
/public/assets
|
||||
|
||||
# Ignore CI service files.
|
||||
/.github
|
||||
|
||||
# Ignore Kamal files.
|
||||
/config/deploy*.yml
|
||||
/.kamal
|
||||
|
||||
# Ignore development files
|
||||
/.devcontainer
|
||||
|
||||
# Ignore Docker-related files
|
||||
/.dockerignore
|
||||
/Dockerfile*
|
||||
9
.gitattributes
vendored
Normal file
9
.gitattributes
vendored
Normal file
|
|
@ -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
|
||||
12
.github/dependabot.yml
vendored
Normal file
12
.github/dependabot.yml
vendored
Normal file
|
|
@ -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
|
||||
54
.gitignore
vendored
Normal file
54
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
||||
#
|
||||
# Temporary files generated by your text editor or operating system
|
||||
# belong in git's global ignore instead:
|
||||
# `$XDG_CONFIG_HOME/git/ignore` or `~/.config/git/ignore`
|
||||
|
||||
# Ignore bundler config.
|
||||
/.bundle
|
||||
|
||||
# Ignore all environment files.
|
||||
/.env*
|
||||
|
||||
# Ignore all logfiles and tempfiles.
|
||||
/log/*
|
||||
/tmp/*
|
||||
!/log/.keep
|
||||
!/tmp/.keep
|
||||
|
||||
# Ignore pidfiles, but keep the directory.
|
||||
/tmp/pids/*
|
||||
!/tmp/pids/
|
||||
!/tmp/pids/.keep
|
||||
|
||||
# Ignore storage (uploaded files in development and any SQLite databases).
|
||||
/storage/*
|
||||
!/storage/.keep
|
||||
/tmp/storage/*
|
||||
!/tmp/storage/
|
||||
!/tmp/storage/.keep
|
||||
|
||||
/public/assets
|
||||
|
||||
# Ignore master key for decrypting credentials and more.
|
||||
/config/master.key
|
||||
|
||||
/app/assets/builds/*
|
||||
!/app/assets/builds/.keep
|
||||
|
||||
/node_modules
|
||||
|
||||
/config/credentials/production.key
|
||||
|
||||
**/.DS_Store
|
||||
|
||||
# Vite Ruby
|
||||
/public/vite*
|
||||
node_modules
|
||||
# Vite uses dotenv and suggests to ignore local-only env files. See
|
||||
# https://vitejs.dev/guide/env-and-mode.html#env-files
|
||||
*.local
|
||||
|
||||
*.key
|
||||
|
||||
app/frontend/images/template_previews/*
|
||||
3
.kamal/hooks/docker-setup.sample
Executable file
3
.kamal/hooks/docker-setup.sample
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "Docker set up on $KAMAL_HOSTS..."
|
||||
3
.kamal/hooks/post-app-boot.sample
Executable file
3
.kamal/hooks/post-app-boot.sample
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "Booted app version $KAMAL_VERSION on $KAMAL_HOSTS..."
|
||||
14
.kamal/hooks/post-deploy.sample
Executable file
14
.kamal/hooks/post-deploy.sample
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/sh
|
||||
|
||||
# A sample post-deploy hook
|
||||
#
|
||||
# These environment variables are available:
|
||||
# KAMAL_RECORDED_AT
|
||||
# KAMAL_PERFORMER
|
||||
# KAMAL_VERSION
|
||||
# KAMAL_HOSTS
|
||||
# KAMAL_ROLE (if set)
|
||||
# KAMAL_DESTINATION (if set)
|
||||
# KAMAL_RUNTIME
|
||||
|
||||
echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds"
|
||||
3
.kamal/hooks/post-proxy-reboot.sample
Executable file
3
.kamal/hooks/post-proxy-reboot.sample
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "Rebooted kamal-proxy on $KAMAL_HOSTS"
|
||||
3
.kamal/hooks/pre-app-boot.sample
Executable file
3
.kamal/hooks/pre-app-boot.sample
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "Booting app version $KAMAL_VERSION on $KAMAL_HOSTS..."
|
||||
51
.kamal/hooks/pre-build.sample
Executable file
51
.kamal/hooks/pre-build.sample
Executable file
|
|
@ -0,0 +1,51 @@
|
|||
#!/bin/sh
|
||||
|
||||
# A sample pre-build hook
|
||||
#
|
||||
# Checks:
|
||||
# 1. We have a clean checkout
|
||||
# 2. A remote is configured
|
||||
# 3. The branch has been pushed to the remote
|
||||
# 4. The version we are deploying matches the remote
|
||||
#
|
||||
# These environment variables are available:
|
||||
# KAMAL_RECORDED_AT
|
||||
# KAMAL_PERFORMER
|
||||
# KAMAL_VERSION
|
||||
# KAMAL_HOSTS
|
||||
# KAMAL_ROLE (if set)
|
||||
# KAMAL_DESTINATION (if set)
|
||||
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "Git checkout is not clean, aborting..." >&2
|
||||
git status --porcelain >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
first_remote=$(git remote)
|
||||
|
||||
if [ -z "$first_remote" ]; then
|
||||
echo "No git remote set, aborting..." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
current_branch=$(git branch --show-current)
|
||||
|
||||
if [ -z "$current_branch" ]; then
|
||||
echo "Not on a git branch, aborting..." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1)
|
||||
|
||||
if [ -z "$remote_head" ]; then
|
||||
echo "Branch not pushed to remote, aborting..." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$KAMAL_VERSION" != "$remote_head" ]; then
|
||||
echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
47
.kamal/hooks/pre-connect.sample
Executable file
47
.kamal/hooks/pre-connect.sample
Executable file
|
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
# A sample pre-connect check
|
||||
#
|
||||
# Warms DNS before connecting to hosts in parallel
|
||||
#
|
||||
# These environment variables are available:
|
||||
# KAMAL_RECORDED_AT
|
||||
# KAMAL_PERFORMER
|
||||
# KAMAL_VERSION
|
||||
# KAMAL_HOSTS
|
||||
# KAMAL_ROLE (if set)
|
||||
# KAMAL_DESTINATION (if set)
|
||||
# KAMAL_RUNTIME
|
||||
|
||||
hosts = ENV["KAMAL_HOSTS"].split(",")
|
||||
results = nil
|
||||
max = 3
|
||||
|
||||
elapsed = Benchmark.realtime do
|
||||
results = hosts.map do |host|
|
||||
Thread.new do
|
||||
tries = 1
|
||||
|
||||
begin
|
||||
Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME)
|
||||
rescue SocketError
|
||||
if tries < max
|
||||
puts "Retrying DNS warmup: #{host}"
|
||||
tries += 1
|
||||
sleep rand
|
||||
retry
|
||||
else
|
||||
puts "DNS warmup failed: #{host}"
|
||||
host
|
||||
end
|
||||
end
|
||||
|
||||
tries
|
||||
end
|
||||
end.map(&:value)
|
||||
end
|
||||
|
||||
retries = results.sum - hosts.size
|
||||
nopes = results.count { |r| r == max }
|
||||
|
||||
puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ]
|
||||
109
.kamal/hooks/pre-deploy.sample
Executable file
109
.kamal/hooks/pre-deploy.sample
Executable file
|
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
# A sample pre-deploy hook
|
||||
#
|
||||
# Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds.
|
||||
#
|
||||
# Fails unless the combined status is "success"
|
||||
#
|
||||
# These environment variables are available:
|
||||
# KAMAL_RECORDED_AT
|
||||
# KAMAL_PERFORMER
|
||||
# KAMAL_VERSION
|
||||
# KAMAL_HOSTS
|
||||
# KAMAL_COMMAND
|
||||
# KAMAL_SUBCOMMAND
|
||||
# KAMAL_ROLE (if set)
|
||||
# KAMAL_DESTINATION (if set)
|
||||
|
||||
# Only check the build status for production deployments
|
||||
if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production"
|
||||
exit 0
|
||||
end
|
||||
|
||||
require "bundler/inline"
|
||||
|
||||
# true = install gems so this is fast on repeat invocations
|
||||
gemfile(true, quiet: true) do
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "octokit"
|
||||
gem "faraday-retry"
|
||||
end
|
||||
|
||||
MAX_ATTEMPTS = 72
|
||||
ATTEMPTS_GAP = 10
|
||||
|
||||
def exit_with_error(message)
|
||||
$stderr.puts message
|
||||
exit 1
|
||||
end
|
||||
|
||||
class GithubStatusChecks
|
||||
attr_reader :remote_url, :git_sha, :github_client, :combined_status
|
||||
|
||||
def initialize
|
||||
@remote_url = `git config --get remote.origin.url`.strip.delete_prefix("https://github.com/")
|
||||
@git_sha = `git rev-parse HEAD`.strip
|
||||
@github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"])
|
||||
refresh!
|
||||
end
|
||||
|
||||
def refresh!
|
||||
@combined_status = github_client.combined_status(remote_url, git_sha)
|
||||
end
|
||||
|
||||
def state
|
||||
combined_status[:state]
|
||||
end
|
||||
|
||||
def first_status_url
|
||||
first_status = combined_status[:statuses].find { |status| status[:state] == state }
|
||||
first_status && first_status[:target_url]
|
||||
end
|
||||
|
||||
def complete_count
|
||||
combined_status[:statuses].count { |status| status[:state] != "pending"}
|
||||
end
|
||||
|
||||
def total_count
|
||||
combined_status[:statuses].count
|
||||
end
|
||||
|
||||
def current_status
|
||||
if total_count > 0
|
||||
"Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..."
|
||||
else
|
||||
"Build not started..."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
$stdout.sync = true
|
||||
|
||||
puts "Checking build status..."
|
||||
attempts = 0
|
||||
checks = GithubStatusChecks.new
|
||||
|
||||
begin
|
||||
loop do
|
||||
case checks.state
|
||||
when "success"
|
||||
puts "Checks passed, see #{checks.first_status_url}"
|
||||
exit 0
|
||||
when "failure"
|
||||
exit_with_error "Checks failed, see #{checks.first_status_url}"
|
||||
when "pending"
|
||||
attempts += 1
|
||||
end
|
||||
|
||||
exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS
|
||||
|
||||
puts checks.current_status
|
||||
sleep(ATTEMPTS_GAP)
|
||||
checks.refresh!
|
||||
end
|
||||
rescue Octokit::NotFound
|
||||
exit_with_error "Build status could not be found"
|
||||
end
|
||||
3
.kamal/hooks/pre-proxy-reboot.sample
Executable file
3
.kamal/hooks/pre-proxy-reboot.sample
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "Rebooting kamal-proxy on $KAMAL_HOSTS..."
|
||||
17
.kamal/secrets
Normal file
17
.kamal/secrets
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Secrets defined here are available for reference under registry/password, env/secret, builder/secrets,
|
||||
# and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either
|
||||
# password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git.
|
||||
|
||||
# Example of extracting secrets from 1password (or another compatible pw manager)
|
||||
# SECRETS=$(kamal secrets fetch --adapter 1password --account your-account --from Vault/Item KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY)
|
||||
# KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD ${SECRETS})
|
||||
# RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY ${SECRETS})
|
||||
|
||||
# Use a GITHUB_TOKEN if private repositories are needed for the image
|
||||
# GITHUB_TOKEN=$(gh config get -h github.com oauth_token)
|
||||
|
||||
# Grab the registry password from ENV
|
||||
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
|
||||
|
||||
# Improve security by using a password manager. Never check config/master.key into git!
|
||||
RAILS_MASTER_KEY=$(cat config/master.key)
|
||||
1
.node-version
Normal file
1
.node-version
Normal file
|
|
@ -0,0 +1 @@
|
|||
23.6.0
|
||||
8
.rubocop.yml
Normal file
8
.rubocop.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Omakase Ruby styling for Rails
|
||||
inherit_gem: { rubocop-rails-omakase: rubocop.yml }
|
||||
|
||||
# Overwrite or add rules to create your own house style
|
||||
#
|
||||
# # Use `[a, [b, c]]` not `[ a, [ b, c ] ]`
|
||||
# Layout/SpaceInsideArrayLiteralBrackets:
|
||||
# Enabled: false
|
||||
1
.ruby-version
Normal file
1
.ruby-version
Normal file
|
|
@ -0,0 +1 @@
|
|||
3.4.4
|
||||
82
Dockerfile
Normal file
82
Dockerfile
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
# check=error=true
|
||||
|
||||
# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
|
||||
# docker build -t theseus .
|
||||
# docker run -d -p 80:80 -e RAILS_MASTER_KEY=<value from config/master.key> --name theseus theseus
|
||||
|
||||
# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html
|
||||
|
||||
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
|
||||
ARG RUBY_VERSION=3.4.4
|
||||
FROM docker.io/library/ruby:$RUBY_VERSION-slim
|
||||
|
||||
# Set production environment
|
||||
ENV RAILS_ENV="production" \
|
||||
BUNDLE_DEPLOYMENT="1" \
|
||||
BUNDLE_PATH="/usr/local/bundle" \
|
||||
BUNDLE_WITHOUT="development"
|
||||
|
||||
# Install base packages and build dependencies
|
||||
RUN apt-get update -qq && apt-get install --no-install-recommends -y \
|
||||
curl \
|
||||
libjemalloc2 \
|
||||
libvips \
|
||||
postgresql-client \
|
||||
wget \
|
||||
build-essential \
|
||||
git \
|
||||
libpq-dev \
|
||||
node-gyp \
|
||||
pkg-config \
|
||||
python-is-python3 \
|
||||
imagemagick \
|
||||
libmagickwand-dev \
|
||||
ghostscript \
|
||||
libyaml-dev \
|
||||
chromium
|
||||
|
||||
RUN sed -i '/disable ghostscript format types/,+6d' /etc/ImageMagick-6/policy.xml
|
||||
|
||||
# Install Node.js and Yarn
|
||||
ARG NODE_VERSION=23.6.0
|
||||
ARG YARN_VERSION=1.22.22
|
||||
ENV PATH=/usr/local/node/bin:$PATH
|
||||
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 && \
|
||||
npm install -g yarn@$YARN_VERSION && \
|
||||
rm -rf /tmp/node-build-master
|
||||
|
||||
# Rails app lives here
|
||||
WORKDIR /rails
|
||||
|
||||
# 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 --frozen-lockfile
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Precompile bootsnap code and assets
|
||||
RUN bundle exec bootsnap precompile app/ lib/ && \
|
||||
SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile && \
|
||||
rm -rf node_modules
|
||||
|
||||
# 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 && \
|
||||
chown -R rails:rails db log storage tmp
|
||||
USER 1000:1000
|
||||
|
||||
# Entrypoint prepares the database.
|
||||
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
|
||||
|
||||
# Start server via Thruster by default, this can be overwritten at runtime
|
||||
EXPOSE 80
|
||||
CMD ["./bin/thrust", "./bin/rails", "server"]
|
||||
171
Gemfile
Normal file
171
Gemfile
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
source "https://rubygems.org"
|
||||
|
||||
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
|
||||
gem "rails", "~> 8.0.1"
|
||||
# The modern asset pipeline for Rails [https://github.com/rails/propshaft]
|
||||
gem "propshaft"
|
||||
# Use postgresql as the database for Active Record
|
||||
gem "pg", "~> 1.1"
|
||||
# Use the Puma web server [https://github.com/puma/puma]
|
||||
gem "puma", ">= 5.0"
|
||||
# Bundle and transpile JavaScript [https://github.com/rails/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"
|
||||
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
|
||||
gem "jbuilder", "~> 2.13"
|
||||
|
||||
# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
|
||||
# gem "bcrypt", "~> 3.1.7"
|
||||
|
||||
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||
gem "tzinfo-data", platforms: %i[ windows jruby ]
|
||||
|
||||
# Use the database-backed adapters for Rails.cache, Active Job, and Action Cable
|
||||
gem "solid_cache"
|
||||
gem "solid_queue"
|
||||
gem "solid_cable"
|
||||
|
||||
# Reduces boot times through caching; required in config/boot.rb
|
||||
gem "bootsnap", require: false
|
||||
|
||||
# Deploy this application anywhere as a Docker container [https://kamal-deploy.org]
|
||||
gem "kamal", require: false
|
||||
|
||||
# Add HTTP asset caching/compression and X-Sendfile acceleration to Puma [https://github.com/basecamp/thruster/]
|
||||
gem "thruster", require: false
|
||||
|
||||
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
|
||||
# gem "image_processing", "~> 1.2"
|
||||
|
||||
group :development, :test do
|
||||
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
|
||||
gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"
|
||||
|
||||
# Static analysis for security vulnerabilities [https://brakemanscanner.org/]
|
||||
gem "brakeman", require: false
|
||||
|
||||
# Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
|
||||
gem "rubocop-rails-omakase", require: false
|
||||
end
|
||||
|
||||
group :development do
|
||||
# Use console on exceptions pages [https://github.com/rails/web-console]
|
||||
gem "web-console"
|
||||
end
|
||||
|
||||
group :test do
|
||||
# Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
|
||||
gem "capybara"
|
||||
gem "selenium-webdriver"
|
||||
end
|
||||
|
||||
gem "foreman", "~> 0.88.1"
|
||||
|
||||
gem "dotenv-rails", "~> 3.1"
|
||||
|
||||
gem "net-http", "~> 0.6.0"
|
||||
|
||||
gem "http", "~> 5.2"
|
||||
|
||||
gem "annotaterb", "~> 4.14"
|
||||
|
||||
gem "aasm", "~> 5.5"
|
||||
|
||||
gem "norairrecord", "~> 0.3.0"
|
||||
|
||||
gem "filterrific", "~> 5.2"
|
||||
|
||||
gem "hashid-rails", "~> 1.4"
|
||||
|
||||
gem "csv", "~> 3.3"
|
||||
|
||||
gem "faraday", "~> 2.12"
|
||||
|
||||
gem "oauth2", "~> 2.0"
|
||||
|
||||
gem "snail", "~> 2.3"
|
||||
|
||||
gem "easypost", "~> 6.4"
|
||||
|
||||
gem "pundit", "~> 2.5"
|
||||
|
||||
|
||||
|
||||
|
||||
gem "select2-rails", "~> 4.0"
|
||||
|
||||
gem "jquery-rails", "~> 4.6"
|
||||
|
||||
|
||||
gem "country-select", "~> 1.2"
|
||||
|
||||
gem "countries", "~> 7.1"
|
||||
|
||||
gem "good_job", "~> 4.9"
|
||||
|
||||
gem "awesome_print", "~> 1.9"
|
||||
|
||||
gem "cocoon", "~> 1.2"
|
||||
|
||||
gem "administrate", "~> 0.19.0"
|
||||
|
||||
gem "slim-rails", "~> 3.7"
|
||||
|
||||
group :development do
|
||||
gem "letter_opener_web", "~> 3.0"
|
||||
end
|
||||
|
||||
gem "prawn", "~> 2.5"
|
||||
|
||||
gem "usps_intelligent_barcode", "~> 1.0"
|
||||
|
||||
gem "rqrcode", "~> 2.2"
|
||||
|
||||
gem "kaminari", "~> 1.2"
|
||||
|
||||
gem "aws-sdk-s3", require: false
|
||||
|
||||
gem "acts-as-taggable-array-on", "~> 0.7.0"
|
||||
|
||||
gem "selectize-rails", "~> 0.12.6"
|
||||
|
||||
gem "ivymeter", "~> 0.1.0"
|
||||
|
||||
gem "slack-notifier", "~> 2.4"
|
||||
|
||||
gem "nokogiri", "~> 1.18"
|
||||
|
||||
gem "vite_rails"
|
||||
|
||||
gem "blazer", "~> 3.3"
|
||||
|
||||
|
||||
gem "redis", "~> 5.4"
|
||||
|
||||
gem "valid_email2", "~> 7.0"
|
||||
|
||||
gem "sssecrets", "~> 1.0"
|
||||
|
||||
gem "lockbox", "~> 2.0"
|
||||
|
||||
gem "blind_index", "~> 2.7"
|
||||
|
||||
gem "ruby-openai", "~> 8.1"
|
||||
|
||||
gem "parallel", "~> 1.26"
|
||||
|
||||
gem "honeybadger", "~> 5.28"
|
||||
|
||||
gem "rmagick", "~> 5.3"
|
||||
|
||||
gem "jb", "~> 0.8.2"
|
||||
|
||||
gem "ferrum_pdf", "~> 0.3.0"
|
||||
|
||||
gem "literal", "~> 1.7"
|
||||
|
||||
gem "phlex-rails", "~> 2.2"
|
||||
|
||||
gem "xsv", "~> 1.3"
|
||||
702
Gemfile.lock
Normal file
702
Gemfile.lock
Normal file
|
|
@ -0,0 +1,702 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
aasm (5.5.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
actioncable (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activejob (= 8.0.2)
|
||||
activerecord (= 8.0.2)
|
||||
activestorage (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
mail (>= 2.8.0)
|
||||
actionmailer (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
actionview (= 8.0.2)
|
||||
activejob (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
mail (>= 2.8.0)
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (8.0.2)
|
||||
actionview (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
nokogiri (>= 1.8.5)
|
||||
rack (>= 2.2.4)
|
||||
rack-session (>= 1.0.1)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
useragent (~> 0.16)
|
||||
actiontext (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activerecord (= 8.0.2)
|
||||
activestorage (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
activejob (8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
activerecord (8.0.2)
|
||||
activemodel (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
timeout (>= 0.4.0)
|
||||
activestorage (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activejob (= 8.0.2)
|
||||
activerecord (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
marcel (~> 1.0)
|
||||
activesupport (8.0.2)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||
connection_pool (>= 2.2.5)
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
logger (>= 1.4.2)
|
||||
minitest (>= 5.1)
|
||||
securerandom (>= 0.3)
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
uri (>= 0.13.1)
|
||||
acts-as-taggable-array-on (0.7.0)
|
||||
activerecord (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
administrate (0.19.0)
|
||||
actionpack (>= 5.0)
|
||||
actionview (>= 5.0)
|
||||
activerecord (>= 5.0)
|
||||
jquery-rails (>= 4.0)
|
||||
kaminari (>= 1.0)
|
||||
sassc-rails (~> 2.1)
|
||||
selectize-rails (~> 0.6)
|
||||
andand (1.3.3)
|
||||
annotaterb (4.14.0)
|
||||
argon2-kdf (0.3.1)
|
||||
fiddle
|
||||
ast (2.4.3)
|
||||
awesome_print (1.9.2)
|
||||
aws-eventstream (1.3.2)
|
||||
aws-partitions (1.1106.0)
|
||||
aws-sdk-core (3.224.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
base64
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
logger
|
||||
aws-sdk-kms (1.101.0)
|
||||
aws-sdk-core (~> 3, >= 3.216.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.186.1)
|
||||
aws-sdk-core (~> 3, >= 3.216.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.11.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
base64 (0.2.0)
|
||||
bcrypt_pbkdf (1.1.1)
|
||||
bcrypt_pbkdf (1.1.1-arm64-darwin)
|
||||
bcrypt_pbkdf (1.1.1-x86_64-darwin)
|
||||
benchmark (0.4.0)
|
||||
bigdecimal (3.1.9)
|
||||
bindex (0.8.1)
|
||||
blazer (3.3.0)
|
||||
activerecord (>= 7.1)
|
||||
chartkick (>= 5)
|
||||
csv
|
||||
railties (>= 7.1)
|
||||
safely_block (>= 0.4)
|
||||
blind_index (2.7.0)
|
||||
activesupport (>= 7.1)
|
||||
argon2-kdf (>= 0.2)
|
||||
bootsnap (1.18.6)
|
||||
msgpack (~> 1.2)
|
||||
brakeman (7.0.2)
|
||||
racc
|
||||
builder (3.3.0)
|
||||
capybara (3.40.0)
|
||||
addressable
|
||||
matrix
|
||||
mini_mime (>= 0.1.3)
|
||||
nokogiri (~> 1.11)
|
||||
rack (>= 1.6.0)
|
||||
rack-test (>= 0.6.3)
|
||||
regexp_parser (>= 1.5, < 3.0)
|
||||
xpath (~> 3.2)
|
||||
chartkick (5.1.5)
|
||||
childprocess (5.1.0)
|
||||
logger (~> 1.5)
|
||||
chunky_png (1.4.0)
|
||||
cocoon (1.2.15)
|
||||
concurrent-ruby (1.3.5)
|
||||
connection_pool (2.5.3)
|
||||
countries (7.1.1)
|
||||
unaccent (~> 0.3)
|
||||
country-select (1.2.1)
|
||||
crass (1.0.6)
|
||||
csv (3.3.4)
|
||||
date (3.4.1)
|
||||
debug (1.10.0)
|
||||
irb (~> 1.10)
|
||||
reline (>= 0.3.8)
|
||||
domain_name (0.6.20240107)
|
||||
dotenv (3.1.8)
|
||||
dotenv-rails (3.1.8)
|
||||
dotenv (= 3.1.8)
|
||||
railties (>= 6.1)
|
||||
drb (2.2.1)
|
||||
dry-cli (1.2.0)
|
||||
easypost (6.4.1)
|
||||
ed25519 (1.4.0)
|
||||
erb (5.0.1)
|
||||
erubi (1.13.1)
|
||||
et-orbi (1.2.11)
|
||||
tzinfo
|
||||
event_stream_parser (1.0.0)
|
||||
faraday (2.13.1)
|
||||
faraday-net_http (>= 2.0, < 3.5)
|
||||
json
|
||||
logger
|
||||
faraday-multipart (1.1.0)
|
||||
multipart-post (~> 2.0)
|
||||
faraday-net_http (3.4.0)
|
||||
net-http (>= 0.5.0)
|
||||
faraday-net_http_persistent (2.3.0)
|
||||
faraday (~> 2.5)
|
||||
net-http-persistent (>= 4.0.4, < 5)
|
||||
ferrum (0.17.1)
|
||||
addressable (~> 2.5)
|
||||
base64 (~> 0.2)
|
||||
concurrent-ruby (~> 1.1)
|
||||
webrick (~> 1.7)
|
||||
websocket-driver (~> 0.7)
|
||||
ferrum_pdf (0.3.0)
|
||||
ferrum (~> 0.15)
|
||||
rails (>= 6.0.0)
|
||||
ffi (1.17.2-aarch64-linux-gnu)
|
||||
ffi (1.17.2-aarch64-linux-musl)
|
||||
ffi (1.17.2-arm-linux-gnu)
|
||||
ffi (1.17.2-arm-linux-musl)
|
||||
ffi (1.17.2-arm64-darwin)
|
||||
ffi (1.17.2-x86_64-darwin)
|
||||
ffi (1.17.2-x86_64-linux-gnu)
|
||||
ffi (1.17.2-x86_64-linux-musl)
|
||||
ffi-compiler (1.3.2)
|
||||
ffi (>= 1.15.5)
|
||||
rake
|
||||
fiddle (1.1.8)
|
||||
filterrific (5.2.7)
|
||||
foreman (0.88.1)
|
||||
fugit (1.11.1)
|
||||
et-orbi (~> 1, >= 1.2.11)
|
||||
raabro (~> 1.4)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
good_job (4.10.1)
|
||||
activejob (>= 6.1.0)
|
||||
activerecord (>= 6.1.0)
|
||||
concurrent-ruby (>= 1.3.1)
|
||||
fugit (>= 1.11.0)
|
||||
railties (>= 6.1.0)
|
||||
thor (>= 1.0.0)
|
||||
hashid-rails (1.4.1)
|
||||
activerecord (>= 4.0)
|
||||
hashids (~> 1.0)
|
||||
hashids (1.0.6)
|
||||
hashie (5.0.0)
|
||||
honeybadger (5.28.0)
|
||||
logger
|
||||
ostruct
|
||||
http (5.2.0)
|
||||
addressable (~> 2.8)
|
||||
base64 (~> 0.1)
|
||||
http-cookie (~> 1.0)
|
||||
http-form_data (~> 2.2)
|
||||
llhttp-ffi (~> 0.5.0)
|
||||
http-cookie (1.0.8)
|
||||
domain_name (~> 0.5)
|
||||
http-form_data (2.3.0)
|
||||
i18n (1.14.7)
|
||||
concurrent-ruby (~> 1.0)
|
||||
io-console (0.8.0)
|
||||
irb (1.15.2)
|
||||
pp (>= 0.6.0)
|
||||
rdoc (>= 4.0.0)
|
||||
reline (>= 0.4.2)
|
||||
ivymeter (0.1.0)
|
||||
jb (0.8.2)
|
||||
jbuilder (2.13.0)
|
||||
actionview (>= 5.0.0)
|
||||
activesupport (>= 5.0.0)
|
||||
jmespath (1.6.2)
|
||||
jquery-rails (4.6.0)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (2.12.0)
|
||||
jwt (2.10.1)
|
||||
base64
|
||||
kamal (2.6.1)
|
||||
activesupport (>= 7.0)
|
||||
base64 (~> 0.2)
|
||||
bcrypt_pbkdf (~> 1.0)
|
||||
concurrent-ruby (~> 1.2)
|
||||
dotenv (~> 3.1)
|
||||
ed25519 (~> 1.4)
|
||||
net-ssh (~> 7.3)
|
||||
sshkit (>= 1.23.0, < 2.0)
|
||||
thor (~> 1.3)
|
||||
zeitwerk (>= 2.6.18, < 3.0)
|
||||
kaminari (1.2.2)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.2.2)
|
||||
kaminari-activerecord (= 1.2.2)
|
||||
kaminari-core (= 1.2.2)
|
||||
kaminari-actionview (1.2.2)
|
||||
actionview
|
||||
kaminari-core (= 1.2.2)
|
||||
kaminari-activerecord (1.2.2)
|
||||
activerecord
|
||||
kaminari-core (= 1.2.2)
|
||||
kaminari-core (1.2.2)
|
||||
language_server-protocol (3.17.0.5)
|
||||
launchy (3.1.1)
|
||||
addressable (~> 2.8)
|
||||
childprocess (~> 5.0)
|
||||
logger (~> 1.6)
|
||||
letter_opener (1.10.0)
|
||||
launchy (>= 2.2, < 4)
|
||||
letter_opener_web (3.0.0)
|
||||
actionmailer (>= 6.1)
|
||||
letter_opener (~> 1.9)
|
||||
railties (>= 6.1)
|
||||
rexml
|
||||
lint_roller (1.1.0)
|
||||
literal (1.7.1)
|
||||
zeitwerk
|
||||
llhttp-ffi (0.5.1)
|
||||
ffi-compiler (~> 1.0)
|
||||
rake (~> 13.0)
|
||||
lockbox (2.0.1)
|
||||
logger (1.7.0)
|
||||
loofah (2.24.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
mail (2.8.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
marcel (1.0.4)
|
||||
matrix (0.4.2)
|
||||
memoizer (1.0.3)
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.25.5)
|
||||
msgpack (1.8.0)
|
||||
multi_xml (0.7.2)
|
||||
bigdecimal (~> 3.1)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.3.0)
|
||||
net-http (0.6.0)
|
||||
uri
|
||||
net-http-persistent (4.0.5)
|
||||
connection_pool (~> 2.2)
|
||||
net-imap (0.5.8)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
net-protocol
|
||||
net-protocol (0.2.2)
|
||||
timeout
|
||||
net-scp (4.1.0)
|
||||
net-ssh (>= 2.6.5, < 8.0.0)
|
||||
net-sftp (4.0.0)
|
||||
net-ssh (>= 5.0.0, < 8.0.0)
|
||||
net-smtp (0.5.1)
|
||||
net-protocol
|
||||
net-ssh (7.3.0)
|
||||
nio4r (2.7.4)
|
||||
nokogiri (1.18.8-aarch64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.8-aarch64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.8-arm-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.8-arm-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.8-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.8-x86_64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.8-x86_64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.8-x86_64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
norairrecord (0.3.0)
|
||||
faraday (>= 1.0, < 3.0)
|
||||
faraday-net_http_persistent
|
||||
net-http-persistent
|
||||
oauth2 (2.0.10)
|
||||
faraday (>= 0.17.3, < 4.0)
|
||||
jwt (>= 1.0, < 4.0)
|
||||
logger (~> 1.2)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 4)
|
||||
snaky_hash (~> 2.0)
|
||||
version_gem (>= 1.1.8, < 3)
|
||||
observer (0.1.2)
|
||||
ostruct (0.6.1)
|
||||
parallel (1.27.0)
|
||||
parser (3.3.8.0)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
pdf-core (0.10.0)
|
||||
pg (1.5.9)
|
||||
phlex (2.2.1)
|
||||
zeitwerk (~> 2.7)
|
||||
phlex-rails (2.2.0)
|
||||
phlex (~> 2.2.1)
|
||||
railties (>= 7.1, < 9)
|
||||
pkg-config (1.6.2)
|
||||
pp (0.6.2)
|
||||
prettyprint
|
||||
prawn (2.5.0)
|
||||
matrix (~> 0.4)
|
||||
pdf-core (~> 0.10.0)
|
||||
ttfunk (~> 1.8)
|
||||
prettyprint (0.2.0)
|
||||
prism (1.4.0)
|
||||
propshaft (1.1.0)
|
||||
actionpack (>= 7.0.0)
|
||||
activesupport (>= 7.0.0)
|
||||
rack
|
||||
railties (>= 7.0.0)
|
||||
psych (5.2.6)
|
||||
date
|
||||
stringio
|
||||
public_suffix (6.0.2)
|
||||
puma (6.6.0)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.5.0)
|
||||
activesupport (>= 3.0.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.8.1)
|
||||
rack (3.1.15)
|
||||
rack-proxy (0.7.7)
|
||||
rack
|
||||
rack-session (2.1.1)
|
||||
base64 (>= 0.1.0)
|
||||
rack (>= 3.0.0)
|
||||
rack-test (2.2.0)
|
||||
rack (>= 1.3)
|
||||
rackup (2.2.1)
|
||||
rack (>= 3)
|
||||
rails (8.0.2)
|
||||
actioncable (= 8.0.2)
|
||||
actionmailbox (= 8.0.2)
|
||||
actionmailer (= 8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
actiontext (= 8.0.2)
|
||||
actionview (= 8.0.2)
|
||||
activejob (= 8.0.2)
|
||||
activemodel (= 8.0.2)
|
||||
activerecord (= 8.0.2)
|
||||
activestorage (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 8.0.2)
|
||||
rails-dom-testing (2.3.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.6.2)
|
||||
loofah (~> 2.21)
|
||||
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
||||
railties (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
irb (~> 1.13)
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0, >= 1.2.2)
|
||||
zeitwerk (~> 2.6)
|
||||
rainbow (3.1.1)
|
||||
rake (13.2.1)
|
||||
rdoc (6.14.0)
|
||||
erb
|
||||
psych (>= 4.0.0)
|
||||
redis (5.4.0)
|
||||
redis-client (>= 0.22.0)
|
||||
redis-client (0.24.0)
|
||||
connection_pool
|
||||
regexp_parser (2.10.0)
|
||||
reline (0.6.1)
|
||||
io-console (~> 0.5)
|
||||
rexml (3.4.1)
|
||||
rmagick (5.5.0)
|
||||
observer (~> 0.1)
|
||||
pkg-config (~> 1.4)
|
||||
rqrcode (2.2.0)
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 1.0)
|
||||
rqrcode_core (1.2.0)
|
||||
rubocop (1.75.6)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.1.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.3.0.2)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 2.9.3, < 3.0)
|
||||
rubocop-ast (>= 1.44.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 4.0)
|
||||
rubocop-ast (1.44.1)
|
||||
parser (>= 3.3.7.2)
|
||||
prism (~> 1.4)
|
||||
rubocop-performance (1.25.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (>= 1.75.0, < 2.0)
|
||||
rubocop-ast (>= 1.38.0, < 2.0)
|
||||
rubocop-rails (2.32.0)
|
||||
activesupport (>= 4.2.0)
|
||||
lint_roller (~> 1.1)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.75.0, < 2.0)
|
||||
rubocop-ast (>= 1.44.0, < 2.0)
|
||||
rubocop-rails-omakase (1.1.0)
|
||||
rubocop (>= 1.72)
|
||||
rubocop-performance (>= 1.24)
|
||||
rubocop-rails (>= 2.30)
|
||||
ruby-openai (8.1.0)
|
||||
event_stream_parser (>= 0.3.0, < 2.0.0)
|
||||
faraday (>= 1)
|
||||
faraday-multipart (>= 1)
|
||||
ruby-progressbar (1.13.0)
|
||||
rubyzip (2.4.1)
|
||||
safely_block (0.4.1)
|
||||
sassc (2.4.0)
|
||||
ffi (~> 1.9)
|
||||
sassc-rails (2.1.2)
|
||||
railties (>= 4.0.0)
|
||||
sassc (>= 2.0)
|
||||
sprockets (> 3.0)
|
||||
sprockets-rails
|
||||
tilt
|
||||
securerandom (0.4.1)
|
||||
select2-rails (4.0.13)
|
||||
selectize-rails (0.12.6)
|
||||
selenium-webdriver (4.32.0)
|
||||
base64 (~> 0.2)
|
||||
logger (~> 1.4)
|
||||
rexml (~> 3.2, >= 3.2.5)
|
||||
rubyzip (>= 1.2.2, < 3.0)
|
||||
websocket (~> 1.0)
|
||||
slack-notifier (2.4.0)
|
||||
slim (5.2.1)
|
||||
temple (~> 0.10.0)
|
||||
tilt (>= 2.1.0)
|
||||
slim-rails (3.7.0)
|
||||
actionpack (>= 3.1)
|
||||
railties (>= 3.1)
|
||||
slim (>= 3.0, < 6.0, != 5.0.0)
|
||||
snail (2.3.0)
|
||||
activesupport
|
||||
snaky_hash (2.0.1)
|
||||
hashie
|
||||
version_gem (~> 1.1, >= 1.1.1)
|
||||
solid_cable (3.0.8)
|
||||
actioncable (>= 7.2)
|
||||
activejob (>= 7.2)
|
||||
activerecord (>= 7.2)
|
||||
railties (>= 7.2)
|
||||
solid_cache (1.0.7)
|
||||
activejob (>= 7.2)
|
||||
activerecord (>= 7.2)
|
||||
railties (>= 7.2)
|
||||
solid_queue (1.1.5)
|
||||
activejob (>= 7.1)
|
||||
activerecord (>= 7.1)
|
||||
concurrent-ruby (>= 1.3.1)
|
||||
fugit (~> 1.11.0)
|
||||
railties (>= 7.1)
|
||||
thor (~> 1.3.1)
|
||||
sprockets (4.2.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
logger
|
||||
rack (>= 2.2.4, < 4)
|
||||
sprockets-rails (3.5.2)
|
||||
actionpack (>= 6.1)
|
||||
activesupport (>= 6.1)
|
||||
sprockets (>= 3.0.0)
|
||||
sshkit (1.24.0)
|
||||
base64
|
||||
logger
|
||||
net-scp (>= 1.1.2)
|
||||
net-sftp (>= 2.1.2)
|
||||
net-ssh (>= 2.8.0)
|
||||
ostruct
|
||||
sssecrets (1.0.1)
|
||||
stimulus-rails (1.3.4)
|
||||
railties (>= 6.0.0)
|
||||
stringio (3.1.7)
|
||||
temple (0.10.3)
|
||||
thor (1.3.2)
|
||||
thruster (0.1.13)
|
||||
thruster (0.1.13-aarch64-linux)
|
||||
thruster (0.1.13-arm64-darwin)
|
||||
thruster (0.1.13-x86_64-darwin)
|
||||
thruster (0.1.13-x86_64-linux)
|
||||
tilt (2.6.0)
|
||||
timeout (0.4.3)
|
||||
ttfunk (1.8.0)
|
||||
bigdecimal (~> 3.1)
|
||||
turbo-rails (2.0.13)
|
||||
actionpack (>= 7.1.0)
|
||||
railties (>= 7.1.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
unaccent (0.4.0)
|
||||
unicode-display_width (3.1.4)
|
||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||
unicode-emoji (4.0.4)
|
||||
uri (1.0.3)
|
||||
useragent (0.16.11)
|
||||
usps_intelligent_barcode (1.0.0)
|
||||
andand (~> 1.3)
|
||||
memoizer (~> 1.0)
|
||||
valid_email2 (7.0.13)
|
||||
activemodel (>= 6.0)
|
||||
mail (~> 2.5)
|
||||
version_gem (1.1.8)
|
||||
vite_rails (3.0.19)
|
||||
railties (>= 5.1, < 9)
|
||||
vite_ruby (~> 3.0, >= 3.2.2)
|
||||
vite_ruby (3.9.2)
|
||||
dry-cli (>= 0.7, < 2)
|
||||
logger (~> 1.6)
|
||||
mutex_m
|
||||
rack-proxy (~> 0.6, >= 0.6.1)
|
||||
zeitwerk (~> 2.2)
|
||||
web-console (4.2.1)
|
||||
actionview (>= 6.0.0)
|
||||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 6.0.0)
|
||||
webrick (1.9.1)
|
||||
websocket (1.2.11)
|
||||
websocket-driver (0.7.7)
|
||||
base64
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
xsv (1.3.2)
|
||||
rubyzip (>= 1.3, < 3)
|
||||
zeitwerk (2.7.3)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux
|
||||
aarch64-linux-gnu
|
||||
aarch64-linux-musl
|
||||
arm-linux-gnu
|
||||
arm-linux-musl
|
||||
arm64-darwin
|
||||
x86_64-darwin
|
||||
x86_64-linux
|
||||
x86_64-linux-gnu
|
||||
x86_64-linux-musl
|
||||
|
||||
DEPENDENCIES
|
||||
aasm (~> 5.5)
|
||||
acts-as-taggable-array-on (~> 0.7.0)
|
||||
administrate (~> 0.19.0)
|
||||
annotaterb (~> 4.14)
|
||||
awesome_print (~> 1.9)
|
||||
aws-sdk-s3
|
||||
blazer (~> 3.3)
|
||||
blind_index (~> 2.7)
|
||||
bootsnap
|
||||
brakeman
|
||||
capybara
|
||||
cocoon (~> 1.2)
|
||||
countries (~> 7.1)
|
||||
country-select (~> 1.2)
|
||||
csv (~> 3.3)
|
||||
debug
|
||||
dotenv-rails (~> 3.1)
|
||||
easypost (~> 6.4)
|
||||
faraday (~> 2.12)
|
||||
ferrum_pdf (~> 0.3.0)
|
||||
filterrific (~> 5.2)
|
||||
foreman (~> 0.88.1)
|
||||
good_job (~> 4.9)
|
||||
hashid-rails (~> 1.4)
|
||||
honeybadger (~> 5.28)
|
||||
http (~> 5.2)
|
||||
ivymeter (~> 0.1.0)
|
||||
jb (~> 0.8.2)
|
||||
jbuilder (~> 2.13)
|
||||
jquery-rails (~> 4.6)
|
||||
kamal
|
||||
kaminari (~> 1.2)
|
||||
letter_opener_web (~> 3.0)
|
||||
literal (~> 1.7)
|
||||
lockbox (~> 2.0)
|
||||
net-http (~> 0.6.0)
|
||||
nokogiri (~> 1.18)
|
||||
norairrecord (~> 0.3.0)
|
||||
oauth2 (~> 2.0)
|
||||
parallel (~> 1.26)
|
||||
pg (~> 1.1)
|
||||
phlex-rails (~> 2.2)
|
||||
prawn (~> 2.5)
|
||||
propshaft
|
||||
puma (>= 5.0)
|
||||
pundit (~> 2.5)
|
||||
rails (~> 8.0.1)
|
||||
redis (~> 5.4)
|
||||
rmagick (~> 5.3)
|
||||
rqrcode (~> 2.2)
|
||||
rubocop-rails-omakase
|
||||
ruby-openai (~> 8.1)
|
||||
select2-rails (~> 4.0)
|
||||
selectize-rails (~> 0.12.6)
|
||||
selenium-webdriver
|
||||
slack-notifier (~> 2.4)
|
||||
slim-rails (~> 3.7)
|
||||
snail (~> 2.3)
|
||||
solid_cable
|
||||
solid_cache
|
||||
solid_queue
|
||||
sssecrets (~> 1.0)
|
||||
stimulus-rails
|
||||
thruster
|
||||
turbo-rails
|
||||
tzinfo-data
|
||||
usps_intelligent_barcode (~> 1.0)
|
||||
valid_email2 (~> 7.0)
|
||||
vite_rails
|
||||
web-console
|
||||
xsv (~> 1.3)
|
||||
|
||||
BUNDLED WITH
|
||||
2.6.9
|
||||
2
Procfile.dev
Normal file
2
Procfile.dev
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
web: env RUBY_DEBUG_OPEN=true bin/rails server
|
||||
vite: bin/vite dev
|
||||
16
README.md
Normal file
16
README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# hack club's shiny new mail system!
|
||||
it's open source now i guess
|
||||
|
||||
getting this running if you're not me or production is probably kind of a bear!
|
||||
|
||||
i will write seeds and a way to log in without setting up a slack....at...some point.....
|
||||
|
||||
(possibly not soon due to time constraints)
|
||||
|
||||
if you wanna hack on this and contribute, dm me (@nora) on slack, i'd love the help!
|
||||
|
||||
a lot of the design decisions and code are not great. they will be incrementally improved someday. (you can look towards issues for some of that)
|
||||
|
||||
thanks for being here!
|
||||
|
||||
~nora <3
|
||||
6
Rakefile
Normal file
6
Rakefile
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# 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_relative "config/application"
|
||||
|
||||
Rails.application.load_tasks
|
||||
0
app/assets/builds/.keep
Normal file
0
app/assets/builds/.keep
Normal file
0
app/assets/config/manifest.js
Normal file
0
app/assets/config/manifest.js
Normal file
15
app/components/base.rb
Normal file
15
app/components/base.rb
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Components::Base < Phlex::HTML
|
||||
include Components
|
||||
|
||||
# Include any helpers you want to be available across all components
|
||||
include Phlex::Rails::Helpers::Routes
|
||||
|
||||
if Rails.env.development?
|
||||
def before_template
|
||||
comment { "Before #{self.class.name}" }
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
46
app/controllers/admin/addresses_controller.rb
Normal file
46
app/controllers/admin/addresses_controller.rb
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
module Admin
|
||||
class AddressesController < Admin::ApplicationController
|
||||
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||
# For example, you may want to send an email after a foo is updated.
|
||||
#
|
||||
# def update
|
||||
# super
|
||||
# send_foo_updated_email(requested_resource)
|
||||
# end
|
||||
|
||||
# Override this method to specify custom lookup behavior.
|
||||
# This will be used to set the resource for the `show`, `edit`, and `update`
|
||||
# actions.
|
||||
#
|
||||
# def find_resource(param)
|
||||
# Foo.find_by!(slug: param)
|
||||
# end
|
||||
|
||||
# The result of this lookup will be available as `requested_resource`
|
||||
|
||||
# Override this if you have certain roles that require a subset
|
||||
# this will be used to set the records shown on the `index` action.
|
||||
#
|
||||
# def scoped_resource
|
||||
# if current_user.super_admin?
|
||||
# resource_class
|
||||
# else
|
||||
# resource_class.with_less_stuff
|
||||
# end
|
||||
# end
|
||||
|
||||
# Override `resource_params` if you want to transform the submitted
|
||||
# data before it's persisted. For example, the following would turn all
|
||||
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||
# and `dashboard`:
|
||||
#
|
||||
# def resource_params
|
||||
# params.require(resource_class.model_name.param_key).
|
||||
# permit(dashboard.permitted_attributes(action_name)).
|
||||
# transform_values { |value| value == "" ? nil : value }
|
||||
# end
|
||||
|
||||
# See https://administrate-demo.herokuapp.com/customizing_controller_actions
|
||||
# for more information
|
||||
end
|
||||
end
|
||||
21
app/controllers/admin/application_controller.rb
Normal file
21
app/controllers/admin/application_controller.rb
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# All Administrate controllers inherit from this
|
||||
# `Administrate::ApplicationController`, making it the ideal place to put
|
||||
# authentication logic or other before_actions.
|
||||
#
|
||||
# If you want to add pagination or other controller-level concerns,
|
||||
# you're free to overwrite the RESTful controller actions.
|
||||
module Admin
|
||||
class ApplicationController < Administrate::ApplicationController
|
||||
before_action :authenticate_admin
|
||||
|
||||
def authenticate_admin
|
||||
redirect_to root_path, alert: "you can't do that!" unless current_user&.admin?
|
||||
end
|
||||
|
||||
helper_method :current_user
|
||||
|
||||
def current_user
|
||||
@current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
|
||||
end
|
||||
end
|
||||
end
|
||||
46
app/controllers/admin/common_tags_controller.rb
Normal file
46
app/controllers/admin/common_tags_controller.rb
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
module Admin
|
||||
class CommonTagsController < Admin::ApplicationController
|
||||
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||
# For example, you may want to send an email after a foo is updated.
|
||||
#
|
||||
# def update
|
||||
# super
|
||||
# send_foo_updated_email(requested_resource)
|
||||
# end
|
||||
|
||||
# Override this method to specify custom lookup behavior.
|
||||
# This will be used to set the resource for the `show`, `edit`, and `update`
|
||||
# actions.
|
||||
#
|
||||
# def find_resource(param)
|
||||
# Foo.find_by!(slug: param)
|
||||
# end
|
||||
|
||||
# The result of this lookup will be available as `requested_resource`
|
||||
|
||||
# Override this if you have certain roles that require a subset
|
||||
# this will be used to set the records shown on the `index` action.
|
||||
#
|
||||
# def scoped_resource
|
||||
# if current_user.super_admin?
|
||||
# resource_class
|
||||
# else
|
||||
# resource_class.with_less_stuff
|
||||
# end
|
||||
# end
|
||||
|
||||
# Override `resource_params` if you want to transform the submitted
|
||||
# data before it's persisted. For example, the following would turn all
|
||||
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||
# and `dashboard`:
|
||||
#
|
||||
# def resource_params
|
||||
# params.require(resource_class.model_name.param_key).
|
||||
# permit(dashboard.permitted_attributes(action_name)).
|
||||
# transform_values { |value| value == "" ? nil : value }
|
||||
# end
|
||||
|
||||
# See https://administrate-demo.herokuapp.com/customizing_controller_actions
|
||||
# for more information
|
||||
end
|
||||
end
|
||||
46
app/controllers/admin/return_addresses_controller.rb
Normal file
46
app/controllers/admin/return_addresses_controller.rb
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
module Admin
|
||||
class ReturnAddressesController < Admin::ApplicationController
|
||||
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||
# For example, you may want to send an email after a foo is updated.
|
||||
#
|
||||
# def update
|
||||
# super
|
||||
# send_foo_updated_email(requested_resource)
|
||||
# end
|
||||
|
||||
# Override this method to specify custom lookup behavior.
|
||||
# This will be used to set the resource for the `show`, `edit`, and `update`
|
||||
# actions.
|
||||
#
|
||||
# def find_resource(param)
|
||||
# Foo.find_by!(slug: param)
|
||||
# end
|
||||
|
||||
# The result of this lookup will be available as `requested_resource`
|
||||
|
||||
# Override this if you have certain roles that require a subset
|
||||
# this will be used to set the records shown on the `index` action.
|
||||
#
|
||||
# def scoped_resource
|
||||
# if current_user.super_admin?
|
||||
# resource_class
|
||||
# else
|
||||
# resource_class.with_less_stuff
|
||||
# end
|
||||
# end
|
||||
|
||||
# Override `resource_params` if you want to transform the submitted
|
||||
# data before it's persisted. For example, the following would turn all
|
||||
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||
# and `dashboard`:
|
||||
#
|
||||
# def resource_params
|
||||
# params.require(resource_class.model_name.param_key).
|
||||
# permit(dashboard.permitted_attributes(action_name)).
|
||||
# transform_values { |value| value == "" ? nil : value }
|
||||
# end
|
||||
|
||||
# See https://administrate-demo.herokuapp.com/customizing_controller_actions
|
||||
# for more information
|
||||
end
|
||||
end
|
||||
46
app/controllers/admin/source_tags_controller.rb
Normal file
46
app/controllers/admin/source_tags_controller.rb
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
module Admin
|
||||
class SourceTagsController < Admin::ApplicationController
|
||||
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||
# For example, you may want to send an email after a foo is updated.
|
||||
#
|
||||
# def update
|
||||
# super
|
||||
# send_foo_updated_email(requested_resource)
|
||||
# end
|
||||
|
||||
# Override this method to specify custom lookup behavior.
|
||||
# This will be used to set the resource for the `show`, `edit`, and `update`
|
||||
# actions.
|
||||
#
|
||||
# def find_resource(param)
|
||||
# Foo.find_by!(slug: param)
|
||||
# end
|
||||
|
||||
# The result of this lookup will be available as `requested_resource`
|
||||
|
||||
# Override this if you have certain roles that require a subset
|
||||
# this will be used to set the records shown on the `index` action.
|
||||
#
|
||||
# def scoped_resource
|
||||
# if current_user.super_admin?
|
||||
# resource_class
|
||||
# else
|
||||
# resource_class.with_less_stuff
|
||||
# end
|
||||
# end
|
||||
|
||||
# Override `resource_params` if you want to transform the submitted
|
||||
# data before it's persisted. For example, the following would turn all
|
||||
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||
# and `dashboard`:
|
||||
#
|
||||
# def resource_params
|
||||
# params.require(resource_class.model_name.param_key).
|
||||
# permit(dashboard.permitted_attributes(action_name)).
|
||||
# transform_values { |value| value == "" ? nil : value }
|
||||
# end
|
||||
|
||||
# See https://administrate-demo.herokuapp.com/customizing_controller_actions
|
||||
# for more information
|
||||
end
|
||||
end
|
||||
46
app/controllers/admin/users_controller.rb
Normal file
46
app/controllers/admin/users_controller.rb
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
module Admin
|
||||
class UsersController < Admin::ApplicationController
|
||||
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||
# For example, you may want to send an email after a foo is updated.
|
||||
#
|
||||
# def update
|
||||
# super
|
||||
# send_foo_updated_email(requested_resource)
|
||||
# end
|
||||
|
||||
# Override this method to specify custom lookup behavior.
|
||||
# This will be used to set the resource for the `show`, `edit`, and `update`
|
||||
# actions.
|
||||
#
|
||||
# def find_resource(param)
|
||||
# Foo.find_by!(slug: param)
|
||||
# end
|
||||
|
||||
# The result of this lookup will be available as `requested_resource`
|
||||
|
||||
# Override this if you have certain roles that require a subset
|
||||
# this will be used to set the records shown on the `index` action.
|
||||
#
|
||||
# def scoped_resource
|
||||
# if current_user.super_admin?
|
||||
# resource_class
|
||||
# else
|
||||
# resource_class.with_less_stuff
|
||||
# end
|
||||
# end
|
||||
|
||||
# Override `resource_params` if you want to transform the submitted
|
||||
# data before it's persisted. For example, the following would turn all
|
||||
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||
# and `dashboard`:
|
||||
#
|
||||
# def resource_params
|
||||
# params.require(resource_class.model_name.param_key).
|
||||
# permit(dashboard.permitted_attributes(action_name)).
|
||||
# transform_values { |value| value == "" ? nil : value }
|
||||
# end
|
||||
|
||||
# See https://administrate-demo.herokuapp.com/customizing_controller_actions
|
||||
# for more information
|
||||
end
|
||||
end
|
||||
46
app/controllers/admin/usps/mailer_ids_controller.rb
Normal file
46
app/controllers/admin/usps/mailer_ids_controller.rb
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
module Admin
|
||||
class USPS::MailerIdsController < Admin::ApplicationController
|
||||
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||
# For example, you may want to send an email after a foo is updated.
|
||||
#
|
||||
# def update
|
||||
# super
|
||||
# send_foo_updated_email(requested_resource)
|
||||
# end
|
||||
|
||||
# Override this method to specify custom lookup behavior.
|
||||
# This will be used to set the resource for the `show`, `edit`, and `update`
|
||||
# actions.
|
||||
#
|
||||
# def find_resource(param)
|
||||
# Foo.find_by!(slug: param)
|
||||
# end
|
||||
|
||||
# The result of this lookup will be available as `requested_resource`
|
||||
|
||||
# Override this if you have certain roles that require a subset
|
||||
# this will be used to set the records shown on the `index` action.
|
||||
#
|
||||
# def scoped_resource
|
||||
# if current_user.super_admin?
|
||||
# resource_class
|
||||
# else
|
||||
# resource_class.with_less_stuff
|
||||
# end
|
||||
# end
|
||||
|
||||
# Override `resource_params` if you want to transform the submitted
|
||||
# data before it's persisted. For example, the following would turn all
|
||||
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||
# and `dashboard`:
|
||||
#
|
||||
# def resource_params
|
||||
# params.require(resource_class.model_name.param_key).
|
||||
# permit(dashboard.permitted_attributes(action_name)).
|
||||
# transform_values { |value| value == "" ? nil : value }
|
||||
# end
|
||||
|
||||
# See https://administrate-demo.herokuapp.com/customizing_controller_actions
|
||||
# for more information
|
||||
end
|
||||
end
|
||||
48
app/controllers/admin/usps/payment_accounts_controller.rb
Normal file
48
app/controllers/admin/usps/payment_accounts_controller.rb
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
module Admin
|
||||
module USPS
|
||||
class PaymentAccountsController < Admin::ApplicationController
|
||||
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||
# For example, you may want to send an email after a foo is updated.
|
||||
#
|
||||
# def update
|
||||
# super
|
||||
# send_foo_updated_email(requested_resource)
|
||||
# end
|
||||
|
||||
# Override this method to specify custom lookup behavior.
|
||||
# This will be used to set the resource for the `show`, `edit`, and `update`
|
||||
# actions.
|
||||
#
|
||||
# def find_resource(param)
|
||||
# Foo.find_by!(slug: param)
|
||||
# end
|
||||
|
||||
# The result of this lookup will be available as `requested_resource`
|
||||
|
||||
# Override this if you have certain roles that require a subset
|
||||
# this will be used to set the records shown on the `index` action.
|
||||
#
|
||||
# def scoped_resource
|
||||
# if current_user.super_admin?
|
||||
# resource_class
|
||||
# else
|
||||
# resource_class.with_less_stuff
|
||||
# end
|
||||
# end
|
||||
|
||||
# Override `resource_params` if you want to transform the submitted
|
||||
# data before it's persisted. For example, the following would turn all
|
||||
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||
# and `dashboard`:
|
||||
#
|
||||
# def resource_params
|
||||
# params.require(resource_class.model_name.param_key).
|
||||
# permit(dashboard.permitted_attributes(action_name)).
|
||||
# transform_values { |value| value == "" ? nil : value }
|
||||
# end
|
||||
|
||||
# See https://administrate-demo.herokuapp.com/customizing_controller_actions
|
||||
# for more information
|
||||
end
|
||||
end
|
||||
end
|
||||
48
app/controllers/admin/warehouse/line_items_controller.rb
Normal file
48
app/controllers/admin/warehouse/line_items_controller.rb
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
module Admin
|
||||
module Warehouse
|
||||
class LineItemsController < Admin::ApplicationController
|
||||
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||
# For example, you may want to send an email after a foo is updated.
|
||||
#
|
||||
# def update
|
||||
# super
|
||||
# send_foo_updated_email(requested_resource)
|
||||
# end
|
||||
|
||||
# Override this method to specify custom lookup behavior.
|
||||
# This will be used to set the resource for the `show`, `edit`, and `update`
|
||||
# actions.
|
||||
#
|
||||
# def find_resource(param)
|
||||
# Foo.find_by!(slug: param)
|
||||
# end
|
||||
|
||||
# The result of this lookup will be available as `requested_resource`
|
||||
|
||||
# Override this if you have certain roles that require a subset
|
||||
# this will be used to set the records shown on the `index` action.
|
||||
#
|
||||
# def scoped_resource
|
||||
# if current_user.super_admin?
|
||||
# resource_class
|
||||
# else
|
||||
# resource_class.with_less_stuff
|
||||
# end
|
||||
# end
|
||||
|
||||
# Override `resource_params` if you want to transform the submitted
|
||||
# data before it's persisted. For example, the following would turn all
|
||||
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||
# and `dashboard`:
|
||||
#
|
||||
# def resource_params
|
||||
# params.require(resource_class.model_name.param_key).
|
||||
# permit(dashboard.permitted_attributes(action_name)).
|
||||
# transform_values { |value| value == "" ? nil : value }
|
||||
# end
|
||||
|
||||
# See https://administrate-demo.herokuapp.com/customizing_controller_actions
|
||||
# for more information
|
||||
end
|
||||
end
|
||||
end
|
||||
49
app/controllers/admin/warehouse/orders_controller.rb
Normal file
49
app/controllers/admin/warehouse/orders_controller.rb
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
module Admin
|
||||
module Warehouse
|
||||
class OrdersController < Admin::ApplicationController
|
||||
include Administrate::ArrayParameterHandler
|
||||
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||
# For example, you may want to send an email after a foo is updated.
|
||||
#
|
||||
# def update
|
||||
# super
|
||||
# send_foo_updated_email(requested_resource)
|
||||
# end
|
||||
|
||||
# Override this method to specify custom lookup behavior.
|
||||
# This will be used to set the resource for the `show`, `edit`, and `update`
|
||||
# actions.
|
||||
#
|
||||
def find_resource(param)
|
||||
::Warehouse::Order.find_by!(hc_id: param)
|
||||
end
|
||||
|
||||
# The result of this lookup will be available as `requested_resource`
|
||||
|
||||
# Override this if you have certain roles that require a subset
|
||||
# this will be used to set the records shown on the `index` action.
|
||||
#
|
||||
# def scoped_resource
|
||||
# if current_user.super_admin?
|
||||
# resource_class
|
||||
# else
|
||||
# resource_class.with_less_stuff
|
||||
# end
|
||||
# end
|
||||
|
||||
# Override `resource_params` if you want to transform the submitted
|
||||
# data before it's persisted. For example, the following would turn all
|
||||
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||
# and `dashboard`:
|
||||
#
|
||||
# def resource_params
|
||||
# params.require(resource_class.model_name.param_key).
|
||||
# permit(dashboard.permitted_attributes(action_name)).
|
||||
# transform_values { |value| value == "" ? nil : value }
|
||||
# end
|
||||
|
||||
# See https://administrate-demo.herokuapp.com/customizing_controller_actions
|
||||
# for more information
|
||||
end
|
||||
end
|
||||
end
|
||||
48
app/controllers/admin/warehouse/purpose_codes_controller.rb
Normal file
48
app/controllers/admin/warehouse/purpose_codes_controller.rb
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
module Admin
|
||||
module Warehouse
|
||||
class PurposeCodesController < Admin::ApplicationController
|
||||
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||
# For example, you may want to send an email after a foo is updated.
|
||||
#
|
||||
# def update
|
||||
# super
|
||||
# send_foo_updated_email(requested_resource)
|
||||
# end
|
||||
|
||||
# Override this method to specify custom lookup behavior.
|
||||
# This will be used to set the resource for the `show`, `edit`, and `update`
|
||||
# actions.
|
||||
#
|
||||
# def find_resource(param)
|
||||
# Foo.find_by!(slug: param)
|
||||
# end
|
||||
|
||||
# The result of this lookup will be available as `requested_resource`
|
||||
|
||||
# Override this if you have certain roles that require a subset
|
||||
# this will be used to set the records shown on the `index` action.
|
||||
#
|
||||
# def scoped_resource
|
||||
# if current_user.super_admin?
|
||||
# resource_class
|
||||
# else
|
||||
# resource_class.with_less_stuff
|
||||
# end
|
||||
# end
|
||||
|
||||
# Override `resource_params` if you want to transform the submitted
|
||||
# data before it's persisted. For example, the following would turn all
|
||||
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||
# and `dashboard`:
|
||||
#
|
||||
# def resource_params
|
||||
# params.require(resource_class.model_name.param_key).
|
||||
# permit(dashboard.permitted_attributes(action_name)).
|
||||
# transform_values { |value| value == "" ? nil : value }
|
||||
# end
|
||||
|
||||
# See https://administrate-demo.herokuapp.com/customizing_controller_actions
|
||||
# for more information
|
||||
end
|
||||
end
|
||||
end
|
||||
48
app/controllers/admin/warehouse/skus_controller.rb
Normal file
48
app/controllers/admin/warehouse/skus_controller.rb
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
module Admin
|
||||
module Warehouse
|
||||
class SKUsController < Admin::ApplicationController
|
||||
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||
# For example, you may want to send an email after a foo is updated.
|
||||
#
|
||||
# def update
|
||||
# super
|
||||
# send_foo_updated_email(requested_resource)
|
||||
# end
|
||||
|
||||
# Override this method to specify custom lookup behavior.
|
||||
# This will be used to set the resource for the `show`, `edit`, and `update`
|
||||
# actions.
|
||||
#
|
||||
# def find_resource(param)
|
||||
# Foo.find_by!(slug: param)
|
||||
# end
|
||||
|
||||
# The result of this lookup will be available as `requested_resource`
|
||||
|
||||
# Override this if you have certain roles that require a subset
|
||||
# this will be used to set the records shown on the `index` action.
|
||||
#
|
||||
# def scoped_resource
|
||||
# if current_user.super_admin?
|
||||
# resource_class
|
||||
# else
|
||||
# resource_class.with_less_stuff
|
||||
# end
|
||||
# end
|
||||
|
||||
# Override `resource_params` if you want to transform the submitted
|
||||
# data before it's persisted. For example, the following would turn all
|
||||
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||
# and `dashboard`:
|
||||
#
|
||||
# def resource_params
|
||||
# params.require(resource_class.model_name.param_key).
|
||||
# permit(dashboard.permitted_attributes(action_name)).
|
||||
# transform_values { |value| value == "" ? nil : value }
|
||||
# end
|
||||
|
||||
# See https://administrate-demo.herokuapp.com/customizing_controller_actions
|
||||
# for more information
|
||||
end
|
||||
end
|
||||
end
|
||||
48
app/controllers/admin/warehouse/templates_controller.rb
Normal file
48
app/controllers/admin/warehouse/templates_controller.rb
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
module Admin
|
||||
module Warehouse
|
||||
class TemplatesController < Admin::ApplicationController
|
||||
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||
# For example, you may want to send an email after a foo is updated.
|
||||
#
|
||||
# def update
|
||||
# super
|
||||
# send_foo_updated_email(requested_resource)
|
||||
# end
|
||||
|
||||
# Override this method to specify custom lookup behavior.
|
||||
# This will be used to set the resource for the `show`, `edit`, and `update`
|
||||
# actions.
|
||||
#
|
||||
def find_resource(param)
|
||||
Warehouse::Template.find(param)
|
||||
end
|
||||
|
||||
# The result of this lookup will be available as `requested_resource`
|
||||
|
||||
# Override this if you have certain roles that require a subset
|
||||
# this will be used to set the records shown on the `index` action.
|
||||
#
|
||||
# def scoped_resource
|
||||
# if current_user.super_admin?
|
||||
# resource_class
|
||||
# else
|
||||
# resource_class.with_less_stuff
|
||||
# end
|
||||
# end
|
||||
|
||||
# Override `resource_params` if you want to transform the submitted
|
||||
# data before it's persisted. For example, the following would turn all
|
||||
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||
# and `dashboard`:
|
||||
#
|
||||
# def resource_params
|
||||
# params.require(resource_class.model_name.param_key).
|
||||
# permit(dashboard.permitted_attributes(action_name)).
|
||||
# transform_values { |value| value == "" ? nil : value }
|
||||
# end
|
||||
|
||||
# See https://administrate-demo.herokuapp.com/customizing_controller_actions
|
||||
# for more information
|
||||
end
|
||||
end
|
||||
end
|
||||
59
app/controllers/api/v1/application_controller.rb
Normal file
59
app/controllers/api/v1/application_controller.rb
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
module API
|
||||
module V1
|
||||
class ApplicationController < ActionController::API
|
||||
prepend_view_path Rails.root.join("app/views/api/v1")
|
||||
attr_reader :current_user
|
||||
|
||||
before_action :authenticate!
|
||||
before_action :set_expand
|
||||
before_action :set_pii
|
||||
|
||||
include Pundit::Authorization
|
||||
include ActionController::HttpAuthentication::Token::ControllerMethods
|
||||
|
||||
rescue_from Pundit::NotAuthorizedError do |e|
|
||||
render json: { error: "not_authorized" }, status: :forbidden
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do |e|
|
||||
render json: { error: "resource_not_found", message: ("Couldn't locate that #{e.model.constantize.model_name.human}." if e.model) }.compact_blank, status: :not_found
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid do |e|
|
||||
render json: { error: "validation_error", messages: e.record.errors.full_messages }, status: :bad_request
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotUnique do |e|
|
||||
render json: { error: "idempotency_error", messages: ["a record by that idempotency key already exists!"] }, status: :bad_request
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_expand
|
||||
@expand = params[:expand].to_s.split(",").map { |e| e.strip.to_sym }
|
||||
end
|
||||
|
||||
def set_pii
|
||||
@pii = current_token&.pii?
|
||||
end
|
||||
|
||||
def authenticate!
|
||||
@current_token = authenticate_with_http_token { |t, _options| APIKey.find_by(token: t) }
|
||||
unless @current_token&.active?
|
||||
return render json: { error: "invalid_auth" }, status: :unauthorized
|
||||
end
|
||||
@current_user = if current_token&.may_impersonate? && params[:impersonate].present?
|
||||
begin
|
||||
User.find_by!(slack_id: params[:impersonate])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render json: { error: "impersonate_error", message: "couldn't find that user" }, status: :bad_request
|
||||
end
|
||||
else
|
||||
current_token&.user
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :current_token
|
||||
end
|
||||
end
|
||||
end
|
||||
128
app/controllers/api/v1/letter_queues_controller.rb
Normal file
128
app/controllers/api/v1/letter_queues_controller.rb
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
module API
|
||||
module V1
|
||||
class LetterQueuesController < ApplicationController
|
||||
before_action :set_letter_queue, only: [:show, :create_letter]
|
||||
before_action :set_instant_letter_queue, only: [:create_instant_letter, :queued]
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do |e|
|
||||
render json: { error: "Queue not found" }, status: :not_found
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid do |e|
|
||||
Honeybadger.notify(e)
|
||||
render json: {
|
||||
error: "Validation failed",
|
||||
details: e.record.errors.full_messages,
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @letter_queue
|
||||
end
|
||||
|
||||
# this should REALLY be websockets or some shit
|
||||
# but it's not, so we're just going to poll
|
||||
# i'm not braining well enough to do it right anytime soon
|
||||
def queued
|
||||
# authorize @letter_queue, policy_class: Letter::QueuePolicy
|
||||
raise Pundit::NotAuthorizedError unless current_token&.pii?
|
||||
|
||||
return render json: { error: "no" } unless @letter_queue.is_a?(Letter::InstantQueue)
|
||||
@expand = [:label]
|
||||
|
||||
@letters = @letter_queue.letters.pending
|
||||
end
|
||||
|
||||
def create_letter
|
||||
authorize @letter_queue
|
||||
|
||||
# Normalize country name using FrickinCountryNames
|
||||
country = FrickinCountryNames.find_country(letter_params[:address][:country])
|
||||
if country.nil?
|
||||
render json: { error: "couldn't figure out country name #{letter_params[:address][:country]}" }, status: :unprocessable_entity
|
||||
return
|
||||
end
|
||||
|
||||
# Create address with normalized country
|
||||
address_params = letter_params[:address].merge(country: country.alpha2)
|
||||
# Normalize state name to abbreviation
|
||||
address_params[:state] = FrickinCountryNames.normalize_state(country, address_params[:state])
|
||||
addy = Address.new(address_params)
|
||||
|
||||
@letter = @letter_queue.create_letter!(
|
||||
addy,
|
||||
letter_params.except(:address).merge(user: current_user),
|
||||
)
|
||||
render :create_letter, status: :created
|
||||
|
||||
# rescue ActiveRecord::RecordInvalid => e
|
||||
# render json: { error: e.record.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def create_instant_letter
|
||||
authorize @letter_queue, policy_class: Letter::QueuePolicy
|
||||
|
||||
# Normalize country name using FrickinCountryNames
|
||||
country = FrickinCountryNames.find_country(letter_params[:address][:country])
|
||||
if country.nil?
|
||||
render json: { error: "couldn't figure out country name #{letter_params[:address][:country]}" }, status: :unprocessable_entity
|
||||
return
|
||||
end
|
||||
|
||||
# Create address with normalized country
|
||||
address_params = letter_params[:address].merge(country: country.alpha2)
|
||||
# Normalize state name to abbreviation
|
||||
address_params[:state] = FrickinCountryNames.normalize_state(country, address_params[:state])
|
||||
addy = Address.new(address_params)
|
||||
|
||||
@letter = @letter_queue.process_letter_instantly!(
|
||||
addy,
|
||||
letter_params.except(:address).merge(user: current_user),
|
||||
)
|
||||
@expand << :label
|
||||
render :create_letter, status: :created
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
return render json: { error: "Queue not found" }, status: :not_found
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
return render json: { error: e.record.errors.full_messages.join(", ") }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_letter_queue
|
||||
@letter_queue = Letter::Queue.find_by!(slug: params[:id])
|
||||
# grossest hack on the planet, nora why are you like this
|
||||
raise ActiveRecord::RecordNotFound if @letter_queue.is_a?(Letter::InstantQueue)
|
||||
end
|
||||
|
||||
def set_instant_letter_queue
|
||||
@letter_queue = Letter::InstantQueue.find_by!(slug: params[:id])
|
||||
end
|
||||
|
||||
def letter_params
|
||||
params.permit(
|
||||
:rubber_stamps,
|
||||
:recipient_email,
|
||||
:idempotency_key,
|
||||
:return_address_name,
|
||||
metadata: {},
|
||||
address: [
|
||||
:first_name,
|
||||
:last_name,
|
||||
:line_1,
|
||||
:line_2,
|
||||
:city,
|
||||
:state,
|
||||
:postal_code,
|
||||
:country,
|
||||
],
|
||||
)
|
||||
end
|
||||
|
||||
def normalize_country(country)
|
||||
return "US" if country.blank? || country.downcase == "usa" || country.downcase == "united states"
|
||||
country
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
29
app/controllers/api/v1/letters_controller.rb
Normal file
29
app/controllers/api/v1/letters_controller.rb
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
module API
|
||||
module V1
|
||||
class LettersController < ApplicationController
|
||||
before_action :set_letter, only: [:show, :mark_printed]
|
||||
|
||||
def show
|
||||
authorize @letter
|
||||
end
|
||||
|
||||
def by_tag
|
||||
@letters = Letter.where("? = ANY(tags)", params[:tag])
|
||||
authorize @letters
|
||||
render :letters_collection
|
||||
end
|
||||
|
||||
def mark_printed
|
||||
authorize @letter
|
||||
@letter.mark_printed!
|
||||
render json: { message: "Letter marked as printed" }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_letter
|
||||
@letter = Letter.find_by_public_id!(params[:id])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/controllers/api/v1/qz_trays_controller.rb
Normal file
13
app/controllers/api/v1/qz_trays_controller.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
module API
|
||||
module V1
|
||||
class QZTraysController < ApplicationController
|
||||
def cert
|
||||
send_data QZTrayService.certificate
|
||||
end
|
||||
|
||||
def sign
|
||||
send_data QZTrayService.sign(params.require(:request))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
50
app/controllers/api/v1/tags_controller.rb
Normal file
50
app/controllers/api/v1/tags_controller.rb
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
module API
|
||||
module V1
|
||||
class TagsController < ApplicationController
|
||||
# skip_before_action :authenticate!
|
||||
|
||||
def index
|
||||
@tags = ::ApplicationController.helpers.available_tags
|
||||
end
|
||||
|
||||
def show
|
||||
@tag = params[:id]
|
||||
|
||||
letter_query = Letter.with_any_tags([@tag]).where(aasm_state: [:mailed, :received])
|
||||
wh_order_query = Warehouse::Order.with_any_tags([@tag]).where.not(aasm_state: [:draft, :errored])
|
||||
|
||||
if letter_query.none? && wh_order_query.none?
|
||||
render json: { error: "no letters or warehouse orders found for tag #{@tag}...?" }, status: :not_found
|
||||
return
|
||||
end
|
||||
|
||||
cache_key = "api/v1/tags/#{@tag}"
|
||||
cache_options = params[:no_cache] ? { force: true } : { expires_in: 5.minutes }
|
||||
|
||||
cached_data = Rails.cache.fetch(cache_key, cache_options) do
|
||||
{
|
||||
letter_count: letter_query.count,
|
||||
letter_postage_cost: letter_query.sum(:postage),
|
||||
warehouse_order_count: wh_order_query.count,
|
||||
warehouse_order_postage_cost: wh_order_query.sum(:postage_cost),
|
||||
warehouse_order_labor_cost: wh_order_query.sum(:labor_cost),
|
||||
warehouse_order_contents_cost: wh_order_query.sum(:contents_cost),
|
||||
warehouse_order_total_cost: wh_order_query.sum(:postage_cost) + wh_order_query.sum(:labor_cost) + wh_order_query.sum(:contents_cost),
|
||||
}
|
||||
end
|
||||
|
||||
@letter_count = cached_data[:letter_count]
|
||||
@letter_postage_cost = cached_data[:letter_postage_cost]
|
||||
@warehouse_order_count = cached_data[:warehouse_order_count]
|
||||
@warehouse_order_postage_cost = cached_data[:warehouse_order_postage_cost]
|
||||
@warehouse_order_labor_cost = cached_data[:warehouse_order_labor_cost]
|
||||
@warehouse_order_contents_cost = cached_data[:warehouse_order_contents_cost]
|
||||
@warehouse_order_total_cost = cached_data[:warehouse_order_total_cost]
|
||||
end
|
||||
|
||||
def letters
|
||||
@letters = Letter.with_any_tags(params[:id])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
10
app/controllers/api/v1/users_controller.rb
Normal file
10
app/controllers/api/v1/users_controller.rb
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
module API
|
||||
module V1
|
||||
class UsersController < ApplicationController
|
||||
def show
|
||||
@user = authorize current_user
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
50
app/controllers/api_keys_controller.rb
Normal file
50
app/controllers/api_keys_controller.rb
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
class APIKeysController < ApplicationController
|
||||
before_action :set_api_key, except: [:index, :new, :create]
|
||||
|
||||
def index
|
||||
authorize APIKey
|
||||
@api_keys = policy_scope(APIKey)
|
||||
end
|
||||
|
||||
def new
|
||||
authorize APIKey
|
||||
@api_key = APIKey.new(user: current_user)
|
||||
end
|
||||
|
||||
def create
|
||||
permitted_params = [:name, :pii]
|
||||
permitted_params << :may_impersonate if current_user.admin?
|
||||
|
||||
@api_key = APIKey.new(params.require(:api_key).permit(*permitted_params).merge(user: current_user))
|
||||
|
||||
authorize @api_key
|
||||
|
||||
if @api_key.save
|
||||
redirect_to api_key_path(@api_key)
|
||||
else
|
||||
flash[:error] = @api_key.errors.full_messages.to_sentence
|
||||
redirect_to new_api_key_path(@api_key)
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @api_key
|
||||
end
|
||||
|
||||
def revoke_confirm
|
||||
authorize @api_key
|
||||
end
|
||||
|
||||
def revoke
|
||||
authorize @api_key
|
||||
@api_key.revoke!
|
||||
flash[:success] = "terminated with extreme prejudice."
|
||||
redirect_to api_key_path(@api_key)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_api_key
|
||||
@api_key = policy_scope(APIKey).find(params[:id])
|
||||
end
|
||||
end
|
||||
39
app/controllers/application_controller.rb
Normal file
39
app/controllers/application_controller.rb
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
class ApplicationController < ActionController::Base
|
||||
include Pundit::Authorization
|
||||
after_action :verify_authorized
|
||||
|
||||
helper_method :current_user, :user_signed_in?
|
||||
|
||||
before_action :authenticate_user!, :set_honeybadger_context
|
||||
|
||||
def current_user
|
||||
@current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
|
||||
end
|
||||
|
||||
def user_signed_in?
|
||||
!!current_user
|
||||
end
|
||||
|
||||
def authenticate_user!
|
||||
unless user_signed_in?
|
||||
redirect_to login_path, alert: ("you need to be logged in!" unless request.env["PATH_INFO"] == "/back_office")
|
||||
end
|
||||
end
|
||||
|
||||
def set_honeybadger_context
|
||||
Honeybadger.context({
|
||||
user_id: current_user&.id,
|
||||
user_email: current_user&.email,
|
||||
})
|
||||
end
|
||||
|
||||
rescue_from Pundit::NotAuthorizedError do |e|
|
||||
flash[:error] = "you don't seem to be authorized – ask nora?"
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do |e|
|
||||
flash[:error] = "sorry, couldn't find that object... (404)"
|
||||
redirect_to root_path
|
||||
end
|
||||
end
|
||||
111
app/controllers/base_batches_controller.rb
Normal file
111
app/controllers/base_batches_controller.rb
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
class BaseBatchesController < ApplicationController
|
||||
before_action :set_batch, except: %i[ index new create ]
|
||||
before_action :setup_csv_fields, only: %i[ map_fields set_mapping ]
|
||||
|
||||
REQUIRED_FIELDS = %w[first_name line_1 city state postal_code country].freeze
|
||||
PREVIEW_ROWS = 3
|
||||
|
||||
# GET /batches or /batches.json
|
||||
def index
|
||||
authorize Batch
|
||||
@batches = policy_scope(Batch).order(created_at: :desc)
|
||||
end
|
||||
|
||||
# GET /batches/1 or /batches/1.json
|
||||
def show
|
||||
authorize @batch
|
||||
end
|
||||
|
||||
# GET /batches/1/edit
|
||||
def edit
|
||||
authorize @batch
|
||||
end
|
||||
|
||||
# PATCH/PUT /batches/1 or /batches/1.json
|
||||
def update
|
||||
authorize @batch
|
||||
respond_to do |format|
|
||||
if @batch.update(batch_params)
|
||||
format.html { redirect_to @batch, notice: "Batch was successfully updated." }
|
||||
format.json { render :show, status: :ok, location: @batch }
|
||||
else
|
||||
format.html { render :edit, status: :unprocessable_entity }
|
||||
format.json { render json: @batch.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /batches/1 or /batches/1.json
|
||||
def destroy
|
||||
authorize @batch
|
||||
@batch.destroy!
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to batches_path, status: :see_other, notice: "Batch was successfully destroyed." }
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
def map_fields
|
||||
authorize @batch, :map_fields?
|
||||
end
|
||||
|
||||
def set_mapping
|
||||
authorize @batch, :set_mapping?
|
||||
mapping = mapping_params.to_h
|
||||
|
||||
# Invert the mapping to get from CSV columns to address fields
|
||||
inverted_mapping = mapping.invert
|
||||
|
||||
# Validate required fields
|
||||
missing_fields = REQUIRED_FIELDS.reject { |field| inverted_mapping[field].present? }
|
||||
|
||||
if missing_fields.any?
|
||||
flash.now[:error] = "Please map the following required fields: #{missing_fields.join(", ")}"
|
||||
render :map_fields, status: :unprocessable_entity
|
||||
return
|
||||
end
|
||||
|
||||
if @batch.update!(field_mapping: inverted_mapping)
|
||||
begin
|
||||
@batch.run_map!
|
||||
rescue StandardError => e
|
||||
raise
|
||||
Rails.logger.warn(e)
|
||||
uuid = Honeybadger.notify(e)
|
||||
redirect_to send("#{@batch.class.name.split("::").first.downcase}_batch_path", @batch), flash: { alert: "error mapping fields! #{e.message} (please report EID: #{uuid})" }
|
||||
return
|
||||
end
|
||||
redirect_to send("process_confirm_#{@batch.class.name.split("::").first.downcase}_batch_path", @batch), notice: "Field mapping saved. Please review and process your batch."
|
||||
else
|
||||
flash.now[:error] = "failed to save field mapping. #{@batch.errors.full_messages.join(", ")}"
|
||||
render :map_fields, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_batch
|
||||
@batch = Batch.find(params[:id])
|
||||
end
|
||||
|
||||
def setup_csv_fields
|
||||
csv_rows = CSV.parse(@batch.csv_data)
|
||||
@csv_headers = csv_rows.first
|
||||
@csv_preview = csv_rows[1..PREVIEW_ROWS] || []
|
||||
|
||||
# Get fields based on batch type
|
||||
@address_fields = if @batch.is_a?(Letter::Batch)
|
||||
# For letter batches, include address fields and rubber_stamps
|
||||
(Address.column_names - ["id", "created_at", "updated_at", "batch_id"]) +
|
||||
["rubber_stamps"]
|
||||
else
|
||||
# For other batches, just include address fields
|
||||
(Address.column_names - ["id", "created_at", "updated_at"])
|
||||
end
|
||||
end
|
||||
|
||||
def mapping_params
|
||||
params.require(:mapping).permit!
|
||||
end
|
||||
end
|
||||
0
app/controllers/concerns/.keep
Normal file
0
app/controllers/concerns/.keep
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
module Administrate
|
||||
module ArrayParameterHandler
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :handle_array_parameters
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_array_parameters
|
||||
return unless params[:warehouse_order].present?
|
||||
|
||||
params[:warehouse_order].each do |key, value|
|
||||
if value.is_a?(String) && value.include?(",")
|
||||
params[:warehouse_order][key] = value.split(",").map(&:strip)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
30
app/controllers/concerns/public/frameable.rb
Normal file
30
app/controllers/concerns/public/frameable.rb
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
module Public::Frameable
|
||||
extend ActiveSupport::Concern
|
||||
included do
|
||||
layout "public/frameable"
|
||||
|
||||
def set_framed
|
||||
@framed = request.headers["Sec-Fetch-Dest"] == "iframe"
|
||||
end
|
||||
|
||||
before_action :set_framed
|
||||
|
||||
def frame_aware_redirect_to(path, **options)
|
||||
if @framed
|
||||
redirect_to "#{path}#{path.include?('?') ? '&' : '?'}framed=true", **options
|
||||
else
|
||||
redirect_to path, **options
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_framed_param(path)
|
||||
uri = URI.parse(path)
|
||||
params = URI.decode_www_form(uri.query || '')
|
||||
params << ['framed', @framed]
|
||||
uri.query = URI.encode_www_form(params)
|
||||
uri.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
37
app/controllers/customs_receipts_controller.rb
Normal file
37
app/controllers/customs_receipts_controller.rb
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
class CustomsReceiptsController < ApplicationController
|
||||
def index
|
||||
authorize :customs_receipt
|
||||
end
|
||||
|
||||
def generate
|
||||
authorize :customs_receipt
|
||||
|
||||
return redirect_to customs_receipts_path, alert: "well, ya gotta search for *something*" if params[:search].blank?
|
||||
|
||||
receiptable = get_receiptable(params[:search])
|
||||
|
||||
if receiptable.nil?
|
||||
flash[:error] = "couldn't find anything about #{params[:search]}... better luck next time?"
|
||||
return redirect_to customs_receipts_path
|
||||
end
|
||||
|
||||
send_data CustomsReceipt::Generate.run(receiptable), filename: "customs_receipt.pdf", type: "application/pdf", disposition: "inline"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_receiptable(search)
|
||||
order = Warehouse::Order.where("hc_id = :search or tracking_number = :search", search:).first
|
||||
return CustomsReceipt::TheseusSpecific.receiptable_from_warehouse_order(order) if order
|
||||
|
||||
sanitized_airtable_search = search.gsub("'", "\\'")
|
||||
|
||||
msr = LSV::MarketingShipmentRequest.first_where(
|
||||
"OR({Airtable ID (Automation)} = '#{sanitized_airtable_search}', {Warehouse–Tracking Number} = '#{sanitized_airtable_search}')"
|
||||
)
|
||||
|
||||
return CustomsReceipt::TheseusSpecific.receiptable_from_msr(msr) if msr
|
||||
|
||||
nil
|
||||
end
|
||||
end
|
||||
6
app/controllers/inspect/indicia_controller.rb
Normal file
6
app/controllers/inspect/indicia_controller.rb
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
module Inspect
|
||||
class IndiciaController < InspectorController
|
||||
MODEL = USPS::Indicium
|
||||
LINKED_FIELDS = %i(letter)
|
||||
end
|
||||
end
|
||||
28
app/controllers/inspect/inspector_controller.rb
Normal file
28
app/controllers/inspect/inspector_controller.rb
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
module Inspect
|
||||
class InspectorController < ApplicationController
|
||||
skip_after_action :verify_authorized
|
||||
before_action :set_record
|
||||
before_action do
|
||||
unless current_user.admin?
|
||||
redirect_to root_path, alert: "you are not authorized to access this page."
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@linked_fields = self.class::LINKED_FIELDS.each_with_object({}) do |field, hash|
|
||||
hash[field] = nil
|
||||
res = @record.send(field)
|
||||
next if res.blank?
|
||||
hash[field] = url_for(res) if res.present?
|
||||
rescue ActionController::RoutingError
|
||||
end
|
||||
render "inspect/show"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_record
|
||||
@record = self.class::MODEL.find_by_public_id(params[:id]) || self.class::MODEL.find(params[:id])
|
||||
end
|
||||
end
|
||||
end
|
||||
8
app/controllers/inspect/iv_mtr_events_controller.rb
Normal file
8
app/controllers/inspect/iv_mtr_events_controller.rb
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
module Inspect
|
||||
class IVMTREventsController < InspectorController
|
||||
MODEL = USPS::IVMTR::Event
|
||||
LINKED_FIELDS = %i(letter)
|
||||
|
||||
private
|
||||
end
|
||||
end
|
||||
240
app/controllers/letter/batches_controller.rb
Normal file
240
app/controllers/letter/batches_controller.rb
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
class Letter::BatchesController < BaseBatchesController
|
||||
# GET /letter/batches
|
||||
def index
|
||||
authorize Letter::Batch, policy_class: Letter::BatchPolicy
|
||||
@batches = policy_scope(Letter::Batch, policy_scope_class: Letter::BatchPolicy::Scope).order(created_at: :desc)
|
||||
end
|
||||
|
||||
# GET /letter/batches/new
|
||||
def new
|
||||
authorize Letter::Batch, policy_class: Letter::BatchPolicy
|
||||
@batch = Letter::Batch.new
|
||||
end
|
||||
|
||||
# GET /letter/batches/:id
|
||||
def show
|
||||
authorize @batch, policy_class: Letter::BatchPolicy
|
||||
end
|
||||
|
||||
# GET /letter/batches/:id/edit
|
||||
def edit
|
||||
authorize @batch, policy_class: Letter::BatchPolicy
|
||||
end
|
||||
|
||||
# POST /letter/batches
|
||||
def create
|
||||
authorize Letter::Batch, policy_class: Letter::BatchPolicy
|
||||
@batch = Letter::Batch.new(batch_params.merge(user: current_user))
|
||||
|
||||
if @batch.save
|
||||
redirect_to map_fields_letter_batch_path(@batch), notice: "Batch was successfully created."
|
||||
else
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
# PATCH /letter/batches/:id
|
||||
def update
|
||||
authorize @batch, policy_class: Letter::BatchPolicy
|
||||
if @batch.update(batch_params)
|
||||
validate_postage_types
|
||||
if @batch.errors.any?
|
||||
render :edit, status: :unprocessable_entity
|
||||
return
|
||||
end
|
||||
|
||||
# Update associated letters if the batch hasn't been processed
|
||||
if @batch.may_mark_processed?
|
||||
@batch.letters.update_all(
|
||||
height: @batch.letter_height,
|
||||
width: @batch.letter_width,
|
||||
weight: @batch.letter_weight,
|
||||
mailing_date: @batch.letter_mailing_date,
|
||||
usps_mailer_id_id: @batch.letter_mailer_id_id,
|
||||
return_address_id: @batch.letter_return_address_id,
|
||||
return_address_name: @batch.letter_return_address_name,
|
||||
)
|
||||
end
|
||||
|
||||
# Always update tags and user facing title on letters
|
||||
@batch.letters.update_all(
|
||||
tags: @batch.tags,
|
||||
user_facing_title: @batch.user_facing_title,
|
||||
)
|
||||
|
||||
redirect_to letter_batch_path(@batch), notice: "Batch was successfully updated."
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /letter/batches/:id
|
||||
def destroy
|
||||
authorize @batch, policy_class: Letter::BatchPolicy
|
||||
@batch.destroy
|
||||
redirect_to letter_batches_path, notice: "Batch was successfully destroyed."
|
||||
end
|
||||
|
||||
def process_form
|
||||
authorize @batch, :process_form?, policy_class: Letter::BatchPolicy
|
||||
render :process_letter
|
||||
end
|
||||
|
||||
def process_batch
|
||||
authorize @batch, :process_batch?, policy_class: Letter::BatchPolicy
|
||||
@batch = Batch.find(params[:id])
|
||||
|
||||
if request.post?
|
||||
if letter_batch_params[:letter_mailing_date].blank?
|
||||
redirect_to process_letter_batch_path(@batch), alert: "Mailing date is required"
|
||||
return
|
||||
end
|
||||
|
||||
@batch.letter_mailing_date = letter_batch_params[:letter_mailing_date]
|
||||
@batch.save! # Save the mailing date before processing
|
||||
|
||||
# Only require payment account if indicia is selected
|
||||
if letter_batch_params[:us_postage_type] == "indicia" || letter_batch_params[:intl_postage_type] == "indicia"
|
||||
payment_account = USPS::PaymentAccount.find_by(id: letter_batch_params[:usps_payment_account_id])
|
||||
|
||||
if payment_account.nil?
|
||||
redirect_to process_letter_batch_path(@batch), alert: "Please select a valid payment account when using indicia"
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
@batch.process!(
|
||||
payment_account: payment_account,
|
||||
us_postage_type: letter_batch_params[:us_postage_type],
|
||||
intl_postage_type: letter_batch_params[:intl_postage_type],
|
||||
template_cycle: letter_batch_params[:template_cycle].to_s.split(",").compact_blank,
|
||||
user_facing_title: letter_batch_params[:user_facing_title],
|
||||
include_qr_code: letter_batch_params[:include_qr_code],
|
||||
)
|
||||
@batch.mark_processed! if @batch.may_mark_processed?
|
||||
|
||||
redirect_to letter_batch_path(@batch, print_now: letter_batch_params[:print_immediately]), notice: "Batch processed successfully"
|
||||
rescue => e
|
||||
uuid = Honeybadger.notify(e)
|
||||
redirect_to process_letter_batch_path(@batch), alert: "Failed to process batch: #{e.message} (please report EID: #{uuid})"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def mark_printed
|
||||
authorize @batch, :mark_printed?, policy_class: Letter::BatchPolicy
|
||||
if @batch.processed?
|
||||
@batch.letters.each do |letter|
|
||||
letter.mark_printed! if letter.may_mark_printed?
|
||||
end
|
||||
flash[:success] = "all letters have been marked as printed!"
|
||||
redirect_to letter_batch_path(@batch)
|
||||
else
|
||||
flash[:alert] = "Cannot mark letters as printed. Batch must be processed."
|
||||
redirect_to letter_batch_path(@batch)
|
||||
end
|
||||
end
|
||||
|
||||
def mark_mailed
|
||||
authorize @batch, :mark_mailed?, policy_class: Letter::BatchPolicy
|
||||
if @batch.processed?
|
||||
@batch.letters.each do |letter|
|
||||
letter.mark_mailed! if letter.may_mark_mailed?
|
||||
end
|
||||
User::UpdateTasksJob.perform_now(current_user)
|
||||
redirect_to letter_batch_path(@batch), notice: "All letters have been marked as mailed."
|
||||
else
|
||||
redirect_to letter_batch_path(@batch), alert: "Cannot mark letters as mailed. Batch must be processed."
|
||||
end
|
||||
end
|
||||
|
||||
def update_costs
|
||||
authorize @batch, :update_costs?, policy_class: Letter::BatchPolicy
|
||||
# Calculate counts without saving
|
||||
us_letters = @batch.letters.joins(:address).where(addresses: { country: "US" })
|
||||
intl_letters = @batch.letters.joins(:address).where.not(addresses: { country: "US" })
|
||||
|
||||
cost_differences = @batch.postage_cost_difference(
|
||||
us_postage_type: params[:us_postage_type],
|
||||
intl_postage_type: params[:intl_postage_type],
|
||||
)
|
||||
|
||||
render json: {
|
||||
total_cost: @batch.postage_cost,
|
||||
cost_difference: {
|
||||
us: cost_differences[:us],
|
||||
intl: cost_differences[:intl],
|
||||
},
|
||||
us_count: us_letters.count,
|
||||
intl_count: intl_letters.count,
|
||||
}
|
||||
end
|
||||
|
||||
def regenerate_form
|
||||
authorize @batch, :process_batch?, policy_class: Letter::BatchPolicy
|
||||
render :regenerate_labels
|
||||
end
|
||||
|
||||
def regenerate_labels
|
||||
authorize @batch, :process_batch?, policy_class: Letter::BatchPolicy
|
||||
@batch.regenerate_labels!(
|
||||
template_cycle: letter_batch_params[:template_cycle].to_s.split(",").compact_blank,
|
||||
include_qr_code: letter_batch_params[:include_qr_code],
|
||||
)
|
||||
redirect_to letter_batch_path(@batch), notice: "Labels regenerated successfully"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def batch_params
|
||||
params.require(:letter_batch).permit(
|
||||
:csv,
|
||||
:letter_template_id,
|
||||
:user_facing_title,
|
||||
:letter_height,
|
||||
:letter_width,
|
||||
:letter_weight,
|
||||
:letter_mailing_date,
|
||||
:letter_mailer_id_id,
|
||||
:letter_return_address_id,
|
||||
:letter_return_address_name,
|
||||
:letter_processing_category,
|
||||
tags: [],
|
||||
)
|
||||
end
|
||||
|
||||
def letter_batch_params
|
||||
params.require(:batch).permit(
|
||||
:csv,
|
||||
:letter_height,
|
||||
:letter_width,
|
||||
:user_facing_title,
|
||||
:letter_weight,
|
||||
:letter_mailing_date,
|
||||
:letter_processing_category,
|
||||
:letter_mailer_id_id,
|
||||
:letter_return_address_id,
|
||||
:letter_return_address_name,
|
||||
:us_postage_type,
|
||||
:intl_postage_type,
|
||||
:usps_payment_account_id,
|
||||
:include_qr_code,
|
||||
:print_immediately,
|
||||
:template_cycle,
|
||||
tags: [],
|
||||
)
|
||||
end
|
||||
|
||||
def validate_postage_types
|
||||
return unless @batch.letter_return_address&.us?
|
||||
|
||||
if @batch.us_postage_type.present? && !%w[stamps indicia].include?(@batch.us_postage_type)
|
||||
@batch.errors.add(:us_postage_type, "must be either 'stamps' or 'indicia'")
|
||||
end
|
||||
|
||||
if @batch.intl_postage_type.present? && !%w[stamps indicia].include?(@batch.intl_postage_type)
|
||||
@batch.errors.add(:intl_postage_type, "must be either 'stamps' or 'indicia'")
|
||||
end
|
||||
end
|
||||
end
|
||||
38
app/controllers/letter/instant_queues_controller.rb
Normal file
38
app/controllers/letter/instant_queues_controller.rb
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
class Letter::InstantQueuesController < Letter::QueuesController
|
||||
before_action :set_letter_queue, only: %i[ show edit update destroy ]
|
||||
|
||||
def new
|
||||
@letter_queue = Letter::InstantQueue.new
|
||||
end
|
||||
|
||||
def show
|
||||
@letters = @letter_queue.letters
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_letter_queue
|
||||
@letter_queue = Letter::InstantQueue.find_by!(slug: params[:id])
|
||||
end
|
||||
|
||||
def letter_queue_params
|
||||
params.require(:letter_instant_queue).permit(
|
||||
:name,
|
||||
:type,
|
||||
:letter_height,
|
||||
:letter_width,
|
||||
:letter_weight,
|
||||
:letter_processing_category,
|
||||
:letter_mailer_id_id,
|
||||
:letter_return_address_id,
|
||||
:letter_return_address_name,
|
||||
:user_facing_title,
|
||||
:template,
|
||||
:postage_type,
|
||||
:usps_payment_account_id,
|
||||
:include_qr_code,
|
||||
:letter_mailing_date,
|
||||
tags: [],
|
||||
)
|
||||
end
|
||||
end
|
||||
152
app/controllers/letter/queues_controller.rb
Normal file
152
app/controllers/letter/queues_controller.rb
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
class Letter::QueuesController < ApplicationController
|
||||
before_action :set_letter_queue, only: %i[ show edit update destroy batch ]
|
||||
skip_after_action :verify_authorized
|
||||
# GET /letter/queues or /letter/queues.json
|
||||
def index
|
||||
authorize Letter::Queue, policy_class: Letter::QueuePolicy
|
||||
@letter_queues = policy_scope(Letter::Queue, policy_scope_class: Letter::QueuePolicy::Scope)
|
||||
end
|
||||
|
||||
# GET /letter/queues/1 or /letter/queues/1.json
|
||||
def show
|
||||
@letters = @letter_queue.letters.queued
|
||||
@batches = @letter_queue.letter_batches
|
||||
end
|
||||
|
||||
# GET /letter/queues/new
|
||||
def new
|
||||
@letter_queue = Letter::Queue.new
|
||||
end
|
||||
|
||||
# GET /letter/queues/1/edit
|
||||
def edit
|
||||
end
|
||||
|
||||
# POST /letter/queues or /letter/queues.json
|
||||
def create
|
||||
@letter_queue = letter_queue_class.new(letter_queue_params.merge(user: current_user))
|
||||
|
||||
respond_to do |format|
|
||||
if @letter_queue.save
|
||||
format.html { redirect_to @letter_queue, notice: "Queue was successfully created." }
|
||||
format.json { render :show, status: :created, location: @letter_queue }
|
||||
else
|
||||
format.html { render :new, status: :unprocessable_entity }
|
||||
format.json { render json: @letter_queue.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PATCH/PUT /letter/queues/1 or /letter/queues/1.json
|
||||
def update
|
||||
respond_to do |format|
|
||||
if @letter_queue.update(letter_queue_params)
|
||||
format.html { redirect_to @letter_queue, notice: "Queue was successfully updated." }
|
||||
format.json { render :show, status: :ok, location: @letter_queue }
|
||||
else
|
||||
format.html { render :edit, status: :unprocessable_entity }
|
||||
format.json { render json: @letter_queue.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /letter/queues/1 or /letter/queues/1.json
|
||||
def destroy
|
||||
@letter_queue.destroy!
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to letter_queues_path, status: :see_other, notice: "Queue was successfully destroyed." }
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
def batch
|
||||
authorize @letter_queue
|
||||
unless @letter_queue.letters.any?
|
||||
flash[:error] = "no letters?"
|
||||
redirect_to @letter_queue
|
||||
end
|
||||
batch = @letter_queue.make_batch(user: current_user)
|
||||
User::UpdateTasksJob.perform_now(current_user)
|
||||
flash[:success] = "now do something with it!"
|
||||
redirect_to process_letter_batch_path(batch, uft: @letter_queue.user_facing_title, template: @letter_queue.template)
|
||||
end
|
||||
|
||||
def mark_printed_instants_mailed
|
||||
authorize Letter::Queue
|
||||
|
||||
# Find all letters with "printed" status in any instant letter queue
|
||||
printed_letters = Letter.joins(:queue)
|
||||
.where(letter_queues: { type: "Letter::InstantQueue" })
|
||||
.where(aasm_state: "printed")
|
||||
|
||||
if printed_letters.empty?
|
||||
flash[:notice] = "No printed letters found in instant queues."
|
||||
redirect_to letter_queues_path
|
||||
return
|
||||
end
|
||||
|
||||
# Mark all printed letters as mailed
|
||||
marked_count = 0
|
||||
failed_letters = []
|
||||
|
||||
printed_letters.each do |letter|
|
||||
begin
|
||||
letter.mark_mailed!
|
||||
marked_count += 1
|
||||
rescue => e
|
||||
failed_letters << "#{letter.public_id} (#{e.message})"
|
||||
end
|
||||
end
|
||||
|
||||
# Update user tasks after marking letters as mailed
|
||||
User::UpdateTasksJob.perform_now(current_user) if marked_count > 0
|
||||
|
||||
if failed_letters.any?
|
||||
flash[:alert] = "Marked #{marked_count} letters as mailed, but failed to mark #{failed_letters.count} letters: #{failed_letters.join(", ")}"
|
||||
else
|
||||
flash[:success] = "Successfully marked #{marked_count} letters as mailed from instant queues."
|
||||
end
|
||||
|
||||
redirect_to letter_queues_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Use callbacks to share common setup or constraints between actions.
|
||||
def set_letter_queue
|
||||
@letter_queue = Letter::Queue.find_by!(slug: params[:id])
|
||||
end
|
||||
|
||||
def letter_queue_class
|
||||
type = params[:letter_queue]&.dig(:type) || params[:letter_instant_queue]&.dig(:type)
|
||||
case type
|
||||
when "Letter::InstantQueue"
|
||||
Letter::InstantQueue
|
||||
else
|
||||
Letter::Queue
|
||||
end
|
||||
end
|
||||
|
||||
# Only allow a list of trusted parameters through.
|
||||
def letter_queue_params
|
||||
params.require(:letter_queue).permit(
|
||||
:name,
|
||||
:type,
|
||||
:letter_height,
|
||||
:letter_width,
|
||||
:letter_weight,
|
||||
:letter_processing_category,
|
||||
:letter_mailer_id_id,
|
||||
:letter_return_address_id,
|
||||
:letter_return_address_name,
|
||||
:user_facing_title,
|
||||
:template,
|
||||
:postage_type,
|
||||
:usps_payment_account_id,
|
||||
:include_qr_code,
|
||||
:letter_mailing_date,
|
||||
tags: [],
|
||||
)
|
||||
end
|
||||
end
|
||||
243
app/controllers/letters_controller.rb
Normal file
243
app/controllers/letters_controller.rb
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
class LettersController < ApplicationController
|
||||
before_action :set_letter, except: %i[ index new create ]
|
||||
|
||||
# GET /letters
|
||||
def index
|
||||
authorize Letter
|
||||
# Get all letters with their associations using policy scope
|
||||
@all_letters = policy_scope(Letter).includes(:batch, :address, :usps_mailer_id, :label_attachment, :label_blob)
|
||||
.where.not(aasm_state: "queued")
|
||||
.order(created_at: :desc)
|
||||
|
||||
# Get unbatched letters with pagination
|
||||
@unbatched_letters = @all_letters.not_in_batch.page(params[:page]).per(20)
|
||||
|
||||
# Get batched letters grouped by batch
|
||||
@batched_letters = @all_letters.in_batch.group_by(&:batch)
|
||||
end
|
||||
|
||||
# GET /letters/1
|
||||
def show
|
||||
authorize @letter
|
||||
@available_templates = SnailMail::Service.available_templates
|
||||
end
|
||||
|
||||
# GET /letters/new
|
||||
def new
|
||||
authorize Letter
|
||||
@letter = Letter.new
|
||||
@letter.return_address = current_user.home_return_address || ReturnAddress.first
|
||||
@letter.build_address
|
||||
end
|
||||
|
||||
# GET /letters/1/edit
|
||||
def edit
|
||||
authorize @letter
|
||||
# If letter doesn't have a return address already, don't build one
|
||||
# Let the user select one from the dropdown
|
||||
end
|
||||
|
||||
# POST /letters
|
||||
def create
|
||||
@letter = Letter.new(letter_params.merge(user: current_user))
|
||||
authorize @letter
|
||||
|
||||
# Set postage type to international_origin if return address is not US
|
||||
if @letter.return_address && @letter.return_address.country != "US"
|
||||
@letter.postage_type = "international_origin"
|
||||
end
|
||||
|
||||
if @letter.save
|
||||
redirect_to @letter, notice: "Letter was successfully created."
|
||||
else
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
# PATCH/PUT /letters/1
|
||||
def update
|
||||
authorize @letter
|
||||
|
||||
if @letter.batch_id.present? && params[:letter][:postage_type].present?
|
||||
redirect_to @letter, alert: "Cannot change postage type for a letter that is part of a batch."
|
||||
return
|
||||
end
|
||||
|
||||
# Set postage type to international_origin if return address is not US
|
||||
if params[:letter][:return_address_id].present?
|
||||
return_address = ReturnAddress.find(params[:letter][:return_address_id])
|
||||
if return_address.country != "US"
|
||||
params[:letter][:postage_type] = "international_origin"
|
||||
end
|
||||
end
|
||||
|
||||
if @letter.update(letter_params)
|
||||
redirect_to @letter, notice: "Letter was successfully updated."
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /letters/1
|
||||
def destroy
|
||||
authorize @letter
|
||||
@letter.destroy!
|
||||
redirect_to letters_path, status: :see_other, notice: "Letter was successfully destroyed."
|
||||
end
|
||||
|
||||
# POST /letters/1/generate_label
|
||||
def generate_label
|
||||
authorize @letter, :generate_label?
|
||||
template = params[:template]
|
||||
include_qr_code = params[:qr].present?
|
||||
|
||||
# Generate label with specified template
|
||||
begin
|
||||
# Let the model method handle saving itself
|
||||
if @letter.generate_label(template:, include_qr_code:)
|
||||
if @letter.label.attached?
|
||||
# Redirect back to the letter page with a success message
|
||||
redirect_to @letter, notice: "Label was successfully generated."
|
||||
else
|
||||
redirect_to @letter, alert: "Failed to generate label."
|
||||
end
|
||||
else
|
||||
redirect_to @letter, alert: "Failed to generate label: #{@letter.errors.full_messages.join(", ")}"
|
||||
end
|
||||
rescue => e
|
||||
raise
|
||||
redirect_to @letter, alert: "Error generating label: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
def preview_template
|
||||
authorize @letter, :preview_template?
|
||||
template = params["template"]
|
||||
include_qr_code = params["qr"].present?
|
||||
send_data SnailMail::Service.generate_label(@letter, { template:, include_qr_code: }).render, type: "application/pdf", disposition: "inline"
|
||||
end
|
||||
|
||||
# POST /letters/1/mark_printed
|
||||
def mark_printed
|
||||
authorize @letter, :mark_printed?
|
||||
if @letter.mark_printed!
|
||||
redirect_to @letter, notice: "Letter has been marked as printed."
|
||||
else
|
||||
redirect_to @letter, alert: "Could not mark letter as printed: #{@letter.errors.full_messages.join(", ")}"
|
||||
end
|
||||
end
|
||||
|
||||
# POST /letters/1/mark_mailed
|
||||
def mark_mailed
|
||||
authorize @letter, :mark_mailed?
|
||||
if @letter.mark_mailed!
|
||||
User::UpdateTasksJob.perform_now(current_user)
|
||||
redirect_to @letter, notice: "Letter has been marked as mailed."
|
||||
else
|
||||
redirect_to @letter, alert: "Could not mark letter as mailed: #{@letter.errors.full_messages.join(", ")}"
|
||||
end
|
||||
end
|
||||
|
||||
# POST /letters/1/mark_received
|
||||
def mark_received
|
||||
authorize @letter, :mark_received?
|
||||
if @letter.mark_received!
|
||||
redirect_to @letter, notice: "Letter has been marked as received."
|
||||
else
|
||||
redirect_to @letter, alert: "Could not mark letter as received: #{@letter.errors.full_messages.join(", ")}"
|
||||
end
|
||||
end
|
||||
|
||||
# POST /letters/1/clear_label
|
||||
def clear_label
|
||||
authorize @letter, :clear_label?
|
||||
if @letter.pending? && @letter.label.attached?
|
||||
@letter.label.purge
|
||||
redirect_to @letter, notice: "Label has been cleared."
|
||||
else
|
||||
redirect_to @letter, alert: "Cannot clear label: Letter is not in pending state or has no label attached."
|
||||
end
|
||||
end
|
||||
|
||||
# POST /letters/1/buy_indicia
|
||||
def buy_indicia
|
||||
authorize @letter, :buy_indicia?
|
||||
if @letter.batch_id.present?
|
||||
redirect_to @letter, alert: "Cannot buy indicia for a letter that is part of a batch."
|
||||
return
|
||||
end
|
||||
|
||||
if @letter.postage_type != "indicia"
|
||||
redirect_to @letter, alert: "Letter must be set to indicia postage type first."
|
||||
return
|
||||
end
|
||||
|
||||
if @letter.usps_indicium.present?
|
||||
redirect_to @letter, alert: "Indicia already purchased for this letter."
|
||||
return
|
||||
end
|
||||
|
||||
payment_account = USPS::PaymentAccount.find_by(id: params[:usps_payment_account_id])
|
||||
if payment_account.nil?
|
||||
redirect_to @letter, alert: "Please select a valid payment account."
|
||||
return
|
||||
end
|
||||
|
||||
indicium = USPS::Indicium.new(letter: @letter, payment_account: payment_account)
|
||||
begin
|
||||
indicium.buy!
|
||||
redirect_to @letter, notice: "Indicia purchased successfully."
|
||||
rescue => e
|
||||
redirect_to @letter, alert: "Failed to purchase indicia: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Use callbacks to share common setup or constraints between actions.
|
||||
def set_letter
|
||||
@letter = Letter.find_by_public_id!(params[:id])
|
||||
end
|
||||
|
||||
# Only allow a list of trusted parameters through.
|
||||
def letter_params
|
||||
params.require(:letter).permit(
|
||||
:body,
|
||||
:height,
|
||||
:width,
|
||||
:weight,
|
||||
:non_machinable,
|
||||
:processing_category,
|
||||
:postage_type,
|
||||
:mailing_date,
|
||||
:rubber_stamps,
|
||||
:user_facing_title,
|
||||
:usps_mailer_id_id,
|
||||
:return_address_id,
|
||||
:return_address_name,
|
||||
:recipient_email,
|
||||
address_attributes: [
|
||||
:id,
|
||||
:first_name,
|
||||
:last_name,
|
||||
:line_1,
|
||||
:line_2,
|
||||
:city,
|
||||
:state,
|
||||
:postal_code,
|
||||
:country,
|
||||
],
|
||||
return_address_attributes: [
|
||||
:id,
|
||||
:name,
|
||||
:line_1,
|
||||
:line_2,
|
||||
:city,
|
||||
:state,
|
||||
:postal_code,
|
||||
:country,
|
||||
],
|
||||
tags: [],
|
||||
)
|
||||
end
|
||||
end
|
||||
39
app/controllers/public/api/v1/application_controller.rb
Normal file
39
app/controllers/public/api/v1/application_controller.rb
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
module Public
|
||||
module API
|
||||
module V1
|
||||
class ApplicationController < ActionController::API
|
||||
prepend_view_path "app/views/public/api/v1"
|
||||
|
||||
attr_reader :current_public_user
|
||||
|
||||
before_action :authenticate!
|
||||
before_action :set_expand
|
||||
|
||||
include ActionController::HttpAuthentication::Token::ControllerMethods
|
||||
|
||||
rescue_from Pundit::NotAuthorizedError do |e|
|
||||
render json: { error: "not_authorized" }, status: :forbidden
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do |e|
|
||||
render json: { error: "resource_not_found", message: ("Couldn't locate that #{e.model.constantize.model_name.human}." if e.model) }.compact_blank, status: :not_found
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_expand
|
||||
@expand = params[:expand].to_s.split(",").map { |e| e.strip.to_sym }
|
||||
end
|
||||
|
||||
def authenticate!
|
||||
@current_token = authenticate_with_http_token { |t, _options| Public::APIKey.find_by(token: t) }
|
||||
unless @current_token&.active?
|
||||
return render json: { error: "invalid_auth" }, status: :unauthorized
|
||||
end
|
||||
|
||||
@current_public_user = @current_token.public_user
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
15
app/controllers/public/api/v1/letters_controller.rb
Normal file
15
app/controllers/public/api/v1/letters_controller.rb
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
module Public
|
||||
module API
|
||||
module V1
|
||||
class LettersController < ApplicationController
|
||||
def index
|
||||
@letters = Letter.where(recipient_email: current_public_user.email)
|
||||
end
|
||||
|
||||
def show
|
||||
@letter = Letter.where(recipient_email: current_public_user.email).find_by_public_id!(params[:id])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
19
app/controllers/public/api/v1/lsv_controller.rb
Normal file
19
app/controllers/public/api/v1/lsv_controller.rb
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
module Public
|
||||
module API
|
||||
module V1
|
||||
class LSVController < ApplicationController
|
||||
def index
|
||||
@lsv = LSV::TYPES.map { |type| type.find_by_email(current_public_user.email) }.flatten
|
||||
render :index
|
||||
end
|
||||
|
||||
def show
|
||||
@lsv = LSV::SLUGS[params[:slug].to_sym]&.find(params[:id])
|
||||
raise ActiveRecord::RecordNotFound unless @lsv && @lsv.email == current_public_user&.email
|
||||
rescue Norairrecord::RecordNotFoundError
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
18
app/controllers/public/api/v1/mail_controller.rb
Normal file
18
app/controllers/public/api/v1/mail_controller.rb
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
module Public
|
||||
module API
|
||||
module V1
|
||||
class MailController < ApplicationController
|
||||
def index
|
||||
@mail =
|
||||
Warehouse::Order.where(recipient_email: current_public_user.email) +
|
||||
Letter.where(recipient_email: current_public_user.email)
|
||||
unless params[:no_load_lsv]
|
||||
@mail += LSV::TYPES.map { |type| type.find_by_email(current_public_user.email) }.flatten
|
||||
end
|
||||
@mail.sort_by!(&:created_at).reverse!
|
||||
render :index
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
22
app/controllers/public/api/v1/packages_controller.rb
Normal file
22
app/controllers/public/api/v1/packages_controller.rb
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
module Public
|
||||
module API
|
||||
module V1
|
||||
class PackagesController < ApplicationController
|
||||
before_action :set_package, only: [:show]
|
||||
|
||||
def index
|
||||
@packages = Warehouse::Order.where(recipient_email: current_public_user.email)
|
||||
end
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_package
|
||||
@package = Warehouse::Order.find_by!(hc_id: params[:id])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
12
app/controllers/public/api/v1/users_controller.rb
Normal file
12
app/controllers/public/api/v1/users_controller.rb
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
module Public
|
||||
module API
|
||||
module V1
|
||||
class UsersController < ApplicationController
|
||||
def me
|
||||
@user = current_public_user
|
||||
render :show
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
43
app/controllers/public/api_keys_controller.rb
Normal file
43
app/controllers/public/api_keys_controller.rb
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
module Public
|
||||
class APIKeysController < ApplicationController
|
||||
before_action :set_api_key, except: [:index, :new, :create]
|
||||
before_action :authenticate_public_user!
|
||||
|
||||
def index
|
||||
@api_keys = APIKey.where(public_user: current_public_user)
|
||||
end
|
||||
|
||||
def new
|
||||
@api_key = APIKey.new(public_user: current_public_user)
|
||||
end
|
||||
|
||||
def create
|
||||
@api_key = APIKey.new(params.require(:public_api_key).permit(:name).merge(public_user: current_public_user))
|
||||
|
||||
if @api_key.save
|
||||
redirect_to public_api_key_path(@api_key)
|
||||
else
|
||||
flash[:error] = @api_key.errors.full_messages.to_sentence
|
||||
redirect_to new_public_api_key_path
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
def revoke_confirm
|
||||
end
|
||||
|
||||
def revoke
|
||||
@api_key.revoke!
|
||||
flash[:success] = "terminated with extreme prejudice."
|
||||
redirect_to public_api_key_path(@api_key)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_api_key
|
||||
@api_key = APIKey.where(public_user: current_public_user).find(params[:id])
|
||||
end
|
||||
end
|
||||
end
|
||||
52
app/controllers/public/application_controller.rb
Normal file
52
app/controllers/public/application_controller.rb
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
module Public
|
||||
class ApplicationController < ActionController::Base
|
||||
include Pundit::Authorization
|
||||
|
||||
layout "public"
|
||||
|
||||
before_action do
|
||||
Honeybadger.context({
|
||||
user_id: current_public_user&.id,
|
||||
user_email: current_public_user&.email,
|
||||
real_user_id: current_user&.id,
|
||||
real_user_email: current_user&.email,
|
||||
impersonator_user_id: session[:public_impersonator_user_id],
|
||||
})
|
||||
end
|
||||
|
||||
helper_method :current_user, :current_public_user, :public_user_signed_in?, :authenticate_public_user!, :impersonating?
|
||||
|
||||
# DO NOT USE (in most cases :-P)
|
||||
def current_user
|
||||
@current_user ||= ::User.find_by(id: session[:user_id]) if session[:user_id]
|
||||
end
|
||||
|
||||
def current_public_user
|
||||
@current_public_user ||= Public::User.find_by(id: session[:public_user_id]) if session[:public_user_id]
|
||||
end
|
||||
|
||||
def public_user_signed_in?
|
||||
!!current_public_user
|
||||
end
|
||||
|
||||
def authenticate_public_user!
|
||||
unless public_user_signed_in?
|
||||
redirect_to public_login_path, alert: ("you need to be logged in!" unless request.env["PATH_INFO"] == "/")
|
||||
end
|
||||
end
|
||||
|
||||
def impersonating?
|
||||
!!session[:public_impersonator_user_id]
|
||||
end
|
||||
|
||||
rescue_from Pundit::NotAuthorizedError do |e|
|
||||
flash[:error] = "hey, you can't do that!"
|
||||
redirect_to public_root_path
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do |e|
|
||||
flash[:error] = "sorry, couldn't find that page!"
|
||||
redirect_to public_root_path
|
||||
end
|
||||
end
|
||||
end
|
||||
33
app/controllers/public/impersonations_controller.rb
Normal file
33
app/controllers/public/impersonations_controller.rb
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
module Public
|
||||
class ImpersonationsController < ApplicationController
|
||||
def new
|
||||
authorize Impersonation
|
||||
@impersonation = Impersonation.new
|
||||
end
|
||||
|
||||
def create
|
||||
@impersonation = Impersonation.new(impersonation_params.merge(user: current_user))
|
||||
|
||||
authorize @impersonation
|
||||
|
||||
if @impersonation.save
|
||||
public_user = Public::User.find_or_create_by!(email: impersonation_params[:target_email])
|
||||
session[:public_user_id] = public_user.id
|
||||
session[:public_impersonator_user_id] = current_user.id
|
||||
redirect_to public_root_path
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def stop_impersonating
|
||||
session[:public_user_id] = nil
|
||||
session[:public_impersonator_user_id] = nil
|
||||
redirect_to public_root_path
|
||||
end
|
||||
|
||||
def impersonation_params
|
||||
params.require(:public_impersonation).permit(:target_email, :justification)
|
||||
end
|
||||
end
|
||||
end
|
||||
49
app/controllers/public/leaderboards_controller.rb
Normal file
49
app/controllers/public/leaderboards_controller.rb
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
module Public
|
||||
class LeaderboardsController < ApplicationController
|
||||
layout 'public/frameable'
|
||||
|
||||
before_action :set_framed
|
||||
|
||||
def this_week
|
||||
@tab = :this_week
|
||||
@lb = fetch_leaderboard(:week, Time.current.beginning_of_week)
|
||||
render :show
|
||||
end
|
||||
|
||||
def this_month
|
||||
@tab = :this_month
|
||||
@lb = fetch_leaderboard(:month, Time.current.beginning_of_month)
|
||||
render :show
|
||||
end
|
||||
|
||||
def all_time
|
||||
@tab = :all_time
|
||||
@lb = fetch_leaderboard(:all_time)
|
||||
render :show
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_framed
|
||||
@framed = true
|
||||
end
|
||||
|
||||
def fetch_leaderboard(period, start_time = nil)
|
||||
cache_key = "letter_leaderboard/#{period}"
|
||||
cache_key += "/#{start_time.to_i}" if start_time
|
||||
|
||||
Rails.cache.fetch(cache_key, expires_in: 10.minutes) do
|
||||
query = ::User.joins(:letters)
|
||||
.where(letters: { aasm_state: ['mailed', 'received'] })
|
||||
.group('users.id')
|
||||
.select('users.*, COUNT(letters.id) as letter_count')
|
||||
.having('COUNT(letters.id) > 0')
|
||||
.order('letter_count DESC')
|
||||
.limit(100)
|
||||
|
||||
query = query.where('letters.mailed_at >= ?', start_time) if start_time
|
||||
query
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
41
app/controllers/public/letters_controller.rb
Normal file
41
app/controllers/public/letters_controller.rb
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
module Public
|
||||
class LettersController < ApplicationController
|
||||
include Frameable
|
||||
|
||||
before_action :set_letter
|
||||
|
||||
def show
|
||||
@framed = params[:framed].present? ? params[:framed] == 'true' : request.headers["Sec-Fetch-Dest"] == "iframe"
|
||||
render "public/letters/show"
|
||||
end
|
||||
|
||||
def mark_received
|
||||
@framed = params[:framed]
|
||||
|
||||
if @letter.may_mark_received?
|
||||
@letter.mark_received!
|
||||
@received = true
|
||||
frame_aware_redirect_to public_letter_path(@letter)
|
||||
else
|
||||
flash[:alert] = "huh?"
|
||||
return frame_aware_redirect_to public_letter_path(@letter)
|
||||
end
|
||||
end
|
||||
|
||||
def mark_mailed
|
||||
if @letter.may_mark_mailed?
|
||||
@letter.mark_mailed!
|
||||
frame_aware_redirect_to public_letter_path(@letter)
|
||||
else
|
||||
flash[:alert] = "huh?"
|
||||
return frame_aware_redirect_to public_letter_path(@letter)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def set_letter
|
||||
@letter = Letter.find_by_public_id!(params[:id])
|
||||
@events = @letter.events
|
||||
end
|
||||
end
|
||||
end
|
||||
31
app/controllers/public/lsv_controller.rb
Normal file
31
app/controllers/public/lsv_controller.rb
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
module Public
|
||||
class LSVController < ApplicationController
|
||||
include Frameable
|
||||
|
||||
def show
|
||||
@lsv = LSV::SLUGS[params[:slug].to_sym]&.find(params[:id])
|
||||
raise ActiveRecord::RecordNotFound unless @lsv && @lsv.email == current_public_user&.email
|
||||
rescue Norairrecord::RecordNotFoundError
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
def customs_receipt
|
||||
@msr = LSV::MarketingShipmentRequest.find(params[:id])
|
||||
raise ActiveRecord::RecordNotFound unless @msr && @msr.email == current_public_user&.email && @msr.country != "US"
|
||||
rescue Norairrecord::RecordNotFoundError
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
def generate_customs_receipt
|
||||
@msr = LSV::MarketingShipmentRequest.find(params[:id])
|
||||
raise ActiveRecord::RecordNotFound unless @msr && @msr.email == current_public_user&.email && @msr.country != "US"
|
||||
|
||||
CustomsReceipt::MSRReceiptJob.perform_later(@msr.id)
|
||||
|
||||
flash[:success] = "check your email in a little bit!"
|
||||
return redirect_to show_lsv_path(slug: "msr", id: @msr.id)
|
||||
rescue Norairrecord::RecordNotFoundError
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
end
|
||||
end
|
||||
15
app/controllers/public/mail_controller.rb
Normal file
15
app/controllers/public/mail_controller.rb
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
module Public
|
||||
class MailController < ApplicationController
|
||||
before_action :authenticate_public_user!
|
||||
|
||||
def index
|
||||
@mail =
|
||||
Warehouse::Order.where(recipient_email: current_public_user.email) +
|
||||
Letter.where(recipient_email: current_public_user.email)
|
||||
unless params[:no_load_lsv]
|
||||
@mail += LSV::TYPES.map { |type| type.find_by_email(current_public_user.email) }.flatten
|
||||
end
|
||||
@mail.sort_by!(&:created_at).reverse!
|
||||
end
|
||||
end
|
||||
end
|
||||
16
app/controllers/public/maps_controller.rb
Normal file
16
app/controllers/public/maps_controller.rb
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
require "set"
|
||||
|
||||
module Public
|
||||
class MapsController < ApplicationController
|
||||
include Frameable
|
||||
layout "public/frameable"
|
||||
|
||||
def show
|
||||
@return_path = public_root_path
|
||||
@letters_data = Rails.cache.fetch("map_data") do
|
||||
# If cache is empty, run the job synchronously as a fallback
|
||||
Public::UpdateMapDataJob.perform_now
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
36
app/controllers/public/packages_controller.rb
Normal file
36
app/controllers/public/packages_controller.rb
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
module Public
|
||||
class PackagesController < ApplicationController
|
||||
include Frameable
|
||||
before_action :set_package
|
||||
|
||||
def show
|
||||
if @package.is_a? Warehouse::Order
|
||||
render "public/warehouse/orders/show"
|
||||
end
|
||||
end
|
||||
|
||||
def customs_receipt
|
||||
raise ActiveRecord::RecordNotFound unless @package.is_a?(Warehouse::Order) &&
|
||||
@package.recipient_email == current_public_user&.email &&
|
||||
!@package.address.us?
|
||||
end
|
||||
|
||||
def generate_customs_receipt
|
||||
raise ActiveRecord::RecordNotFound unless @package.is_a?(Warehouse::Order) &&
|
||||
@package.recipient_email == current_public_user&.email &&
|
||||
!@package.address.us?
|
||||
|
||||
# Queue the job to generate and send the customs receipt
|
||||
CustomsReceipt::WarehouseOrderReceiptJob.perform_later(@package.id)
|
||||
|
||||
flash[:success] = "check your email in a little bit!"
|
||||
redirect_to public_package_path(@package)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_package
|
||||
@package = Warehouse::Order.find_by!(hc_id: params[:id])
|
||||
end
|
||||
end
|
||||
end
|
||||
21
app/controllers/public/public_identifiable_controller.rb
Normal file
21
app/controllers/public/public_identifiable_controller.rb
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
module Public
|
||||
class PublicIdentifiableController < ApplicationController
|
||||
def show
|
||||
prefix = params[:public_id].split("!").first&.downcase
|
||||
|
||||
case prefix
|
||||
when "ltr"
|
||||
@record = Letter.find_by_public_id!(params[:public_id])
|
||||
redirect_to public_letter_path(@record)
|
||||
when "pkg"
|
||||
@record = Warehouse::Order.find_by_public_id!(params[:public_id])
|
||||
redirect_to public_package_path(@record)
|
||||
else
|
||||
raise ActiveRecord::RecordNotFound, "no record found with public_id: #{params[:public_id]}"
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound => e
|
||||
flash[:alert] = "what are you even looking for..?"
|
||||
redirect_to public_root_path
|
||||
end
|
||||
end
|
||||
end
|
||||
50
app/controllers/public/sessions_controller.rb
Normal file
50
app/controllers/public/sessions_controller.rb
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
module Public
|
||||
class SessionsController < ApplicationController
|
||||
def send_email
|
||||
begin
|
||||
@email = params.require(:email)
|
||||
rescue ActionController::ParameterMissing => e
|
||||
@error = "you do need to enter an email address...."
|
||||
return render "public/static_pages/login"
|
||||
end
|
||||
|
||||
if @email.ends_with?("hack.af")
|
||||
@error = "come on, is that your <b>real</b> email? say hi to orpheus for me".html_safe
|
||||
return render "public/static_pages/login"
|
||||
end
|
||||
|
||||
address = ValidEmail2::Address.new(@email)
|
||||
|
||||
unless address.valid?
|
||||
@error = "that isn't shaped like an email..."
|
||||
return render "public/static_pages/login"
|
||||
end
|
||||
|
||||
if Rails.env.production?
|
||||
SendLoginEmailJob.perform_later(@email)
|
||||
else
|
||||
SendLoginEmailJob.perform_now(@email)
|
||||
end
|
||||
end
|
||||
|
||||
def login_code
|
||||
valid_code = LoginCode.where(token: params[:token], used_at: nil)
|
||||
.where("expires_at > ?", Time.current)
|
||||
.first
|
||||
|
||||
if valid_code
|
||||
valid_code.mark_used!
|
||||
session[:public_user_id] = valid_code.user_id
|
||||
redirect_to public_root_path, notice: "you're in!"
|
||||
else
|
||||
redirect_to public_root_path, alert: "invalid or expired sign-in link...?"
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
session[:public_user_id] = nil
|
||||
session[:public_impersonator_user_id] = nil
|
||||
redirect_to public_root_path, notice: "signed out!"
|
||||
end
|
||||
end
|
||||
end
|
||||
10
app/controllers/public/static_pages_controller.rb
Normal file
10
app/controllers/public/static_pages_controller.rb
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
module Public
|
||||
class StaticPagesController < ApplicationController
|
||||
def root
|
||||
# flash[:alert] = "bruh"
|
||||
end
|
||||
|
||||
def login
|
||||
end
|
||||
end
|
||||
end
|
||||
61
app/controllers/public_ids_controller.rb
Normal file
61
app/controllers/public_ids_controller.rb
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
class PublicIdsController < ApplicationController
|
||||
# GET /id/:public_id
|
||||
#
|
||||
skip_after_action :verify_authorized
|
||||
#
|
||||
def index
|
||||
end
|
||||
|
||||
def lookup
|
||||
# The public_id contains the prefix that determines the model class
|
||||
prefix = params[:id].split("!").first&.downcase
|
||||
id_part = params[:id].split("!").last
|
||||
|
||||
# Find the corresponding model based on the prefix
|
||||
case prefix
|
||||
when "mtr"
|
||||
@record = USPS::IVMTR::Event.find_by_public_id!(params[:id])
|
||||
@letter = @record.letter
|
||||
if current_user.admin?
|
||||
redirect_to inspect_iv_mtr_event_path(@record)
|
||||
else
|
||||
if @letter.present?
|
||||
redirect_to public_letter_path(@letter)
|
||||
else
|
||||
redirect_to public_ids_path, alert: "MTR event found, but no associated letter...?"
|
||||
end
|
||||
end
|
||||
when "hackapost", "dev"
|
||||
@indicium = USPS::Indicium.find(id_part[1...])
|
||||
@letter = @indicium.letter
|
||||
if current_user.admin?
|
||||
redirect_to inspect_indicium_path(@indicium)
|
||||
else
|
||||
if @letter.present?
|
||||
redirect_to public_letter_path(@letter)
|
||||
else
|
||||
redirect_to public_ids_path, alert: "indicium found, but no associated letter...?"
|
||||
end
|
||||
end
|
||||
else
|
||||
# bad hack:
|
||||
clazzes = ActiveRecord::Base.descendants.select { |c| c.included_modules.include?(PublicIdentifiable) }
|
||||
clazz = clazzes.find { |c| c.public_id_prefix == prefix }
|
||||
unless clazz.present?
|
||||
return redirect_to public_ids_path, alert: "don't think we have that prefix: #{prefix}"
|
||||
end
|
||||
@record = clazz.find_by_public_id(params[:id])
|
||||
unless @record.present?
|
||||
return redirect_to public_ids_path, alert: "no #{clazz.name} found with public id #{params[:id]}"
|
||||
end
|
||||
|
||||
redirect_to url_for(@record)
|
||||
|
||||
# If no matching prefix is found, return 404
|
||||
# raise ActiveRecord::RecordNotFound, "No record found with public_id: #{params[:id]}"
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound => e
|
||||
flash[:alert] = "Record not found"
|
||||
redirect_to public_ids_path
|
||||
end
|
||||
end
|
||||
19
app/controllers/qz_trays_controller.rb
Normal file
19
app/controllers/qz_trays_controller.rb
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
class QZTraysController < ApplicationController
|
||||
skip_after_action :verify_authorized
|
||||
skip_before_action :verify_authenticity_token, only: [:sign]
|
||||
skip_before_action :authenticate_user!, only: [:test_print]
|
||||
def cert
|
||||
send_data QZTrayService.certificate
|
||||
end
|
||||
|
||||
def settings
|
||||
end
|
||||
|
||||
def sign
|
||||
send_data QZTrayService.sign(params.require(:request))
|
||||
end
|
||||
|
||||
def test_print
|
||||
send_file(Rails.root.join('app', 'lib', 'test_print.pdf'), type: 'application/pdf', disposition: 'inline')
|
||||
end
|
||||
end
|
||||
82
app/controllers/return_addresses_controller.rb
Normal file
82
app/controllers/return_addresses_controller.rb
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
class ReturnAddressesController < ApplicationController
|
||||
before_action :set_return_address, only: [:edit, :update, :destroy]
|
||||
|
||||
def index
|
||||
authorize ReturnAddress
|
||||
@return_addresses = ReturnAddress.where(shared: true).or(ReturnAddress.where(user: current_user))
|
||||
end
|
||||
|
||||
def new
|
||||
authorize ReturnAddress
|
||||
@return_address = ReturnAddress.new
|
||||
@return_address.user = current_user if user_signed_in?
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize @return_address
|
||||
end
|
||||
|
||||
def create
|
||||
@return_address = ReturnAddress.new(return_address_params)
|
||||
@return_address.user = current_user if user_signed_in?
|
||||
authorize @return_address
|
||||
|
||||
if @return_address.save
|
||||
# If this was created from the letter form, redirect back to the letter
|
||||
if params[:from_letter].present?
|
||||
redirect_to new_letter_path, notice: "Return address was successfully created. Please select it from the dropdown."
|
||||
else
|
||||
flash[:success] = "Return address was successfully created."
|
||||
redirect_to return_addresses_path
|
||||
end
|
||||
else
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @return_address
|
||||
|
||||
if @return_address.update(return_address_params)
|
||||
# If this was updated from the letter form, redirect back to the letter
|
||||
if params[:from_letter].present?
|
||||
redirect_to new_letter_path, notice: "Return address was successfully updated. Please select it from the dropdown."
|
||||
else
|
||||
redirect_to @return_address, notice: "Return address was successfully updated."
|
||||
end
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @return_address
|
||||
|
||||
if @return_address.letters.any?
|
||||
redirect_to return_addresses_url, alert: "return address has letters associated with it, so it can't be deleted :-("
|
||||
else
|
||||
@return_address.destroy
|
||||
redirect_to return_addresses_url, notice: "Return address was successfully destroyed."
|
||||
end
|
||||
end
|
||||
|
||||
def set_as_home
|
||||
@return_address = ReturnAddress.find(params[:id])
|
||||
authorize @return_address
|
||||
|
||||
current_user.update!(home_return_address: @return_address)
|
||||
flash[:success] = "#{@return_address.display_name} is now your default return address."
|
||||
|
||||
redirect_to return_addresses_url
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_return_address
|
||||
@return_address = ReturnAddress.find(params[:id])
|
||||
end
|
||||
|
||||
def return_address_params
|
||||
params.require(:return_address).permit(:name, :line_1, :line_2, :city, :state, :postal_code, :country, :shared, :user_id, :from_letter)
|
||||
end
|
||||
end
|
||||
67
app/controllers/sessions_controller.rb
Normal file
67
app/controllers/sessions_controller.rb
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
class SessionsController < ApplicationController
|
||||
skip_before_action :authenticate_user!, only: [:new, :create]
|
||||
|
||||
skip_after_action :verify_authorized
|
||||
|
||||
def new
|
||||
redirect_uri = url_for(action: :create, only_path: false)
|
||||
Rails.logger.info "Starting Slack OAuth flow with redirect URI: #{redirect_uri}"
|
||||
redirect_to User.authorize_url(redirect_uri),
|
||||
host: "https://slack.com",
|
||||
allow_other_host: true
|
||||
end
|
||||
|
||||
def create
|
||||
redirect_uri = url_for(action: :create, only_path: false)
|
||||
|
||||
if params[:error].present?
|
||||
Rails.logger.error "Slack OAuth error: #{params[:error]}"
|
||||
uuid = Honeybadger.notify("Slack OAuth error: #{params[:error]}")
|
||||
redirect_to login_path, alert: "failed to authenticate with Slack! (error: #{uuid})"
|
||||
return
|
||||
end
|
||||
|
||||
begin
|
||||
@user = User.from_slack_token(params[:code], redirect_uri)
|
||||
rescue => e
|
||||
Rails.logger.error "Error creating user from Slack data: #{e.message}"
|
||||
uuid = Honeybadger.notify(e)
|
||||
redirect_to login_path, alert: "error authenticating! (error: #{uuid})"
|
||||
return
|
||||
end
|
||||
|
||||
if @user&.persisted?
|
||||
session[:user_id] = @user.id
|
||||
flash[:success] = "welcome aboard!"
|
||||
redirect_to root_path
|
||||
else
|
||||
Rails.logger.error "Failed to create/update user from Slack data"
|
||||
redirect_to login_path, alert: "are you sure you should be here?"
|
||||
end
|
||||
end
|
||||
|
||||
def impersonate
|
||||
unless current_user.admin?
|
||||
redirect_to root_path, alert: "you are not authorized to impersonate users. this incident has been reported :-P"
|
||||
Honeybadger.notify("Impersonation attempt by #{current_user.username} to #{params[:id]}")
|
||||
return
|
||||
end
|
||||
|
||||
session[:impersonator_user_id] ||= current_user.id
|
||||
user = User.find(params[:id])
|
||||
session[:user_id] = user.id
|
||||
flash[:success] = "hey #{user.username}! how's it going? nice 'stache and glasses!"
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
def stop_impersonating
|
||||
session[:user_id] = session[:impersonator_user_id]
|
||||
session[:impersonator_user_id] = nil
|
||||
redirect_to root_path, notice: "welcome back, 007!"
|
||||
end
|
||||
|
||||
def destroy
|
||||
session[:user_id] = nil
|
||||
redirect_to root_path, notice: "bye, see you next time!"
|
||||
end
|
||||
end
|
||||
70
app/controllers/source_tags_controller.rb
Normal file
70
app/controllers/source_tags_controller.rb
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
class SourceTagsController < ApplicationController
|
||||
before_action :set_source_tag, only: %i[ show edit update destroy ]
|
||||
|
||||
# GET /source_tags or /source_tags.json
|
||||
def index
|
||||
@source_tags = SourceTag.all
|
||||
end
|
||||
|
||||
# GET /source_tags/1 or /source_tags/1.json
|
||||
def show
|
||||
end
|
||||
|
||||
# GET /source_tags/new
|
||||
def new
|
||||
@source_tag = SourceTag.new
|
||||
end
|
||||
|
||||
# GET /source_tags/1/edit
|
||||
def edit
|
||||
end
|
||||
|
||||
# POST /source_tags or /source_tags.json
|
||||
def create
|
||||
@source_tag = SourceTag.new(source_tag_params)
|
||||
|
||||
respond_to do |format|
|
||||
if @source_tag.save
|
||||
format.html { redirect_to @source_tag, notice: "Source tag was successfully created." }
|
||||
format.json { render :show, status: :created, location: @source_tag }
|
||||
else
|
||||
format.html { render :new, status: :unprocessable_entity }
|
||||
format.json { render json: @source_tag.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PATCH/PUT /source_tags/1 or /source_tags/1.json
|
||||
def update
|
||||
respond_to do |format|
|
||||
if @source_tag.update(source_tag_params)
|
||||
format.html { redirect_to @source_tag, notice: "Source tag was successfully updated." }
|
||||
format.json { render :show, status: :ok, location: @source_tag }
|
||||
else
|
||||
format.html { render :edit, status: :unprocessable_entity }
|
||||
format.json { render json: @source_tag.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /source_tags/1 or /source_tags/1.json
|
||||
def destroy
|
||||
@source_tag.destroy!
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to source_tags_path, status: :see_other, notice: "Source tag was successfully destroyed." }
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Use callbacks to share common setup or constraints between actions.
|
||||
def set_source_tag
|
||||
@source_tag = SourceTag.find(params.expect(:id))
|
||||
end
|
||||
|
||||
# Only allow a list of trusted parameters through.
|
||||
def source_tag_params
|
||||
params.expect(source_tag: [ :slug, :name, :owner ])
|
||||
end
|
||||
end
|
||||
11
app/controllers/static_pages_controller.rb
Normal file
11
app/controllers/static_pages_controller.rb
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
class StaticPagesController < ApplicationController
|
||||
skip_before_action :authenticate_user!, only: [:login]
|
||||
skip_after_action :verify_authorized
|
||||
|
||||
def index
|
||||
end
|
||||
|
||||
def login
|
||||
render :login, layout: false
|
||||
end
|
||||
end
|
||||
71
app/controllers/tags_controller.rb
Normal file
71
app/controllers/tags_controller.rb
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
class TagsController < ApplicationController
|
||||
skip_after_action :verify_authorized
|
||||
# GET /tags or /tags.json
|
||||
def index
|
||||
@common_tags = CommonTag.all
|
||||
|
||||
@tags = Rails.cache.fetch "tags_list" do
|
||||
warehouse_order_tags = Warehouse::Order.all_tags
|
||||
letter_tags = Letter.all_tags
|
||||
|
||||
(warehouse_order_tags + letter_tags).uniq.compact_blank - @common_tags.map(&:tag)
|
||||
end
|
||||
end
|
||||
|
||||
# GET /tags/1 or /tags/1.json
|
||||
def show
|
||||
tag = params[:id]
|
||||
time_period = params[:time_period] || "all_time"
|
||||
year = params[:year]&.to_i || Time.current.year
|
||||
month = params[:month]&.to_i
|
||||
|
||||
# Base queries
|
||||
letter_query = Letter.with_any_tags([tag]).where.not(aasm_state: "queued")
|
||||
wh_order_query = Warehouse::Order.with_any_tags([tag])
|
||||
|
||||
# Apply time period filter
|
||||
case time_period
|
||||
when "ytd"
|
||||
start_date = Date.new(year, 1, 1)
|
||||
letter_query = letter_query.where("created_at >= ?", start_date)
|
||||
wh_order_query = wh_order_query.where("created_at >= ?", start_date)
|
||||
when "month"
|
||||
if month.present?
|
||||
start_date = Date.new(year, month, 1)
|
||||
end_date = start_date.end_of_month
|
||||
letter_query = letter_query.where(created_at: start_date..end_date)
|
||||
wh_order_query = wh_order_query.where(created_at: start_date..end_date)
|
||||
end
|
||||
when "last_week"
|
||||
letter_query = letter_query.where("created_at >= ?", 1.week.ago)
|
||||
wh_order_query = wh_order_query.where("created_at >= ?", 1.week.ago)
|
||||
when "last_month"
|
||||
letter_query = letter_query.where("created_at >= ?", 1.month.ago)
|
||||
wh_order_query = wh_order_query.where("created_at >= ?", 1.month.ago)
|
||||
when "last_year"
|
||||
letter_query = letter_query.where("created_at >= ?", 1.year.ago)
|
||||
wh_order_query = wh_order_query.where("created_at >= ?", 1.year.ago)
|
||||
end
|
||||
|
||||
@letter_count = letter_query.count
|
||||
@letter_postage_cost = letter_query.sum(:postage)
|
||||
@warehouse_order_count = wh_order_query.count
|
||||
@warehouse_order_postage_cost = wh_order_query.sum(:postage_cost)
|
||||
@warehouse_order_labor_cost = wh_order_query.sum(:labor_cost)
|
||||
@warehouse_order_contents_cost = wh_order_query.sum(:contents_cost)
|
||||
@warehouse_order_total_cost = @warehouse_order_postage_cost + @warehouse_order_labor_cost + @warehouse_order_contents_cost
|
||||
|
||||
@tag = tag
|
||||
@time_period = time_period
|
||||
@year = year
|
||||
@month = month
|
||||
@years = (2020..Time.current.year).to_a.reverse
|
||||
@months = (1..12).map { |m| [Date::MONTHNAMES[m], m] }
|
||||
end
|
||||
|
||||
def refresh
|
||||
Rails.cache.delete("tags_list")
|
||||
flash[:success] = "refreshed!"
|
||||
redirect_to tags_path
|
||||
end
|
||||
end
|
||||
22
app/controllers/tasks_controller.rb
Normal file
22
app/controllers/tasks_controller.rb
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
class TasksController < ApplicationController
|
||||
skip_after_action :verify_authorized
|
||||
before_action :find_tasks
|
||||
|
||||
def badge
|
||||
render :badge, layout: false
|
||||
end
|
||||
|
||||
def show
|
||||
render :show
|
||||
end
|
||||
|
||||
def refresh
|
||||
User::UpdateTasksJob.perform_now(current_user)
|
||||
redirect_to tasks_path
|
||||
end
|
||||
|
||||
def find_tasks
|
||||
@tasks = Rails.cache.read("user_tasks/#{current_user.id}")
|
||||
@tasks ||= User::UpdateTasksJob.perform_now(current_user)
|
||||
end
|
||||
end
|
||||
70
app/controllers/users_controller.rb
Normal file
70
app/controllers/users_controller.rb
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
class UsersController < ApplicationController
|
||||
before_action :set_user, only: %i[ show edit update destroy ]
|
||||
|
||||
# GET /users or /users.json
|
||||
def index
|
||||
@users = User.all
|
||||
end
|
||||
|
||||
# GET /users/1 or /users/1.json
|
||||
def show
|
||||
end
|
||||
|
||||
# GET /users/new
|
||||
def new
|
||||
@user = User.new
|
||||
end
|
||||
|
||||
# GET /users/1/edit
|
||||
def edit
|
||||
end
|
||||
|
||||
# POST /users or /users.json
|
||||
def create
|
||||
@user = User.new(user_params)
|
||||
|
||||
respond_to do |format|
|
||||
if @user.save
|
||||
format.html { redirect_to @user, notice: "User was successfully created." }
|
||||
format.json { render :show, status: :created, location: @user }
|
||||
else
|
||||
format.html { render :new, status: :unprocessable_entity }
|
||||
format.json { render json: @user.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PATCH/PUT /users/1 or /users/1.json
|
||||
def update
|
||||
respond_to do |format|
|
||||
if @user.update(user_params)
|
||||
format.html { redirect_to @user, notice: "User was successfully updated." }
|
||||
format.json { render :show, status: :ok, location: @user }
|
||||
else
|
||||
format.html { render :edit, status: :unprocessable_entity }
|
||||
format.json { render json: @user.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /users/1 or /users/1.json
|
||||
def destroy
|
||||
@user.destroy!
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to users_path, status: :see_other, notice: "User was successfully destroyed." }
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Use callbacks to share common setup or constraints between actions.
|
||||
def set_user
|
||||
@user = User.find(params.expect(:id))
|
||||
end
|
||||
|
||||
# Only allow a list of trusted parameters through.
|
||||
def user_params
|
||||
params.expect(user: [ :slack_id, :email, :is_admin ])
|
||||
end
|
||||
end
|
||||
31
app/controllers/usps/iv_mtr/webhook_controller.rb
Normal file
31
app/controllers/usps/iv_mtr/webhook_controller.rb
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
module USPS
|
||||
module IVMTR
|
||||
class WebhookController < ActionController::Base
|
||||
|
||||
skip_before_action :verify_authenticity_token
|
||||
|
||||
before_action do
|
||||
http_basic_authenticate_or_request_with(
|
||||
name: "my_best_friend_the_informed_visibility_robot",
|
||||
password: Rails.application.credentials.dig(:usps, :iv_mtr, :webhook_password),
|
||||
realm: "IV-MTR",
|
||||
message: "nice try, jackwagon"
|
||||
)
|
||||
end
|
||||
|
||||
def ingest
|
||||
data = JSON.parse(request.raw_post)
|
||||
batch = USPS::IVMTR::RawJSONBatch.create(
|
||||
payload: data["events"],
|
||||
message_group_id: data["msgGrpId"],
|
||||
processed: false
|
||||
)
|
||||
USPS::IVMTR::ImportEventsJob.perform_later(batch)
|
||||
render json: {message: "hey, thanks!"}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
137
app/controllers/warehouse/batches_controller.rb
Normal file
137
app/controllers/warehouse/batches_controller.rb
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
class Warehouse::BatchesController < BaseBatchesController
|
||||
before_action :set_allowed_templates, only: %i[ new create ]
|
||||
|
||||
# GET /warehouse/batches or /warehouse/batches.json
|
||||
def index
|
||||
authorize Warehouse::Batch
|
||||
@batches = policy_scope(Warehouse::Batch).order(created_at: :desc)
|
||||
end
|
||||
|
||||
# GET /warehouse/batches/1 or /warehouse/batches/1.json
|
||||
def show
|
||||
authorize @batch
|
||||
end
|
||||
|
||||
# GET /warehouse/batches/new
|
||||
def new
|
||||
authorize Warehouse::Batch
|
||||
@batch = Warehouse::Batch.new
|
||||
end
|
||||
|
||||
# GET /warehouse/batches/1/edit
|
||||
def edit
|
||||
authorize @batch
|
||||
end
|
||||
|
||||
# POST /warehouse/batches or /warehouse/batches.json
|
||||
def create
|
||||
authorize Warehouse::Batch
|
||||
@batch = Warehouse::Batch.new(batch_params.merge(user: current_user))
|
||||
|
||||
if @batch.save
|
||||
@batch.aasm_state = :awaiting_field_mapping
|
||||
@batch.save!
|
||||
redirect_to map_fields_warehouse_batch_path(@batch), notice: "Please map your CSV fields to address fields."
|
||||
else
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
# PATCH/PUT /warehouse/batches/1 or /warehouse/batches/1.json
|
||||
def update
|
||||
authorize @batch
|
||||
if @batch.update(batch_params)
|
||||
# If template changed and batch hasn't been processed, recreate orders
|
||||
if @batch.may_mark_processed? && @batch.saved_change_to_warehouse_template_id?
|
||||
# Delete existing orders
|
||||
@batch.orders.destroy_all
|
||||
|
||||
# Recreate orders from addresses with new template
|
||||
@batch.addresses.each do |address|
|
||||
Warehouse::Order.from_template(
|
||||
@batch.warehouse_template,
|
||||
batch: @batch,
|
||||
recipient_email: address.email,
|
||||
address: address,
|
||||
user: @batch.user,
|
||||
idempotency_key: "batch_#{@batch.id}_address_#{address.id}",
|
||||
user_facing_title: @batch.warehouse_user_facing_title,
|
||||
tags: @batch.tags,
|
||||
).save!
|
||||
end
|
||||
end
|
||||
|
||||
# Always update tags and user facing title on orders
|
||||
@batch.orders.update_all(
|
||||
tags: @batch.tags,
|
||||
user_facing_title: @batch.warehouse_user_facing_title,
|
||||
)
|
||||
|
||||
redirect_to warehouse_batch_path(@batch), notice: "Batch was successfully updated."
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @batch
|
||||
@batch.destroy
|
||||
redirect_to warehouse_batches_path, notice: "Batch was successfully destroyed."
|
||||
end
|
||||
|
||||
def process_form
|
||||
authorize @batch, :process_form?
|
||||
render :process_warehouse
|
||||
end
|
||||
|
||||
def process_batch
|
||||
authorize @batch, :process_batch?
|
||||
if @batch.process!
|
||||
redirect_to warehouse_batch_path(@batch), notice: "Batch was successfully processed."
|
||||
else
|
||||
render :process_form, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def set_mapping
|
||||
authorize @batch, :set_mapping?
|
||||
mapping = mapping_params.to_h
|
||||
|
||||
# Invert the mapping to get from CSV columns to address fields
|
||||
inverted_mapping = mapping.invert
|
||||
|
||||
# Validate required fields
|
||||
missing_fields = REQUIRED_FIELDS.reject { |field| inverted_mapping[field].present? }
|
||||
|
||||
if missing_fields.any?
|
||||
flash.now[:error] = "Please map the following required fields: #{missing_fields.join(", ")}"
|
||||
render :map_fields, status: :unprocessable_entity
|
||||
return
|
||||
end
|
||||
|
||||
if @batch.update!(field_mapping: inverted_mapping)
|
||||
begin
|
||||
@batch.run_map!
|
||||
rescue StandardError => e
|
||||
Rails.logger.warn(e)
|
||||
uuid = Honeybadger.notify(e)
|
||||
redirect_to warehouse_batch_path(@batch), flash: { alert: "Error mapping fields! #{e.message} (please report EID: #{uuid})" }
|
||||
return
|
||||
end
|
||||
redirect_to process_confirm_warehouse_batch_path(@batch), notice: "Field mapping saved. Please review and process your batch."
|
||||
else
|
||||
flash.now[:error] = "Failed to save field mapping. #{@batch.errors.full_messages.join(", ")}"
|
||||
render :map_fields, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def batch_params
|
||||
params.require(:batch).permit(:warehouse_template_id, :warehouse_user_facing_title, :csv, tags: [])
|
||||
end
|
||||
|
||||
def set_allowed_templates
|
||||
@allowed_templates = Warehouse::Template.where(public: true).or(Warehouse::Template.where(user: current_user))
|
||||
end
|
||||
end
|
||||
137
app/controllers/warehouse/orders_controller.rb
Normal file
137
app/controllers/warehouse/orders_controller.rb
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
class Warehouse::OrdersController < ApplicationController
|
||||
before_action :set_warehouse_order, except: [:new, :create, :index]
|
||||
# GET /warehouse/orders or /warehouse/orders.json
|
||||
def index
|
||||
authorize Warehouse::Order
|
||||
|
||||
# Get all orders with their associations using policy scope
|
||||
@all_orders = policy_scope(Warehouse::Order).includes(:batch, :address, :source_tag, :user)
|
||||
|
||||
# Filter by batched/unbatched based on view parameter
|
||||
if params[:view] == "batched"
|
||||
@warehouse_orders = @all_orders.in_batch
|
||||
else
|
||||
@warehouse_orders = @all_orders.not_in_batch.page(params[:page]).per(20)
|
||||
end
|
||||
end
|
||||
|
||||
# GET /warehouse/orders/1 or /warehouse/orders/1.json
|
||||
def show
|
||||
authorize @warehouse_order
|
||||
end
|
||||
|
||||
# GET /warehouse/orders/new
|
||||
def new
|
||||
authorize Warehouse::Order
|
||||
@warehouse_order = Warehouse::Order.new
|
||||
@warehouse_order.build_address
|
||||
end
|
||||
|
||||
# GET /warehouse/orders/1/edit
|
||||
def edit
|
||||
authorize @warehouse_order
|
||||
end
|
||||
|
||||
def send_to_warehouse
|
||||
authorize @warehouse_order
|
||||
|
||||
begin
|
||||
@warehouse_order.dispatch!
|
||||
rescue Zenventory::ZenventoryError => e
|
||||
uuid = Honeybadger.notify(e)
|
||||
redirect_to @warehouse_order, alert: "zenventory said \"#{e.message}\" (please report EID: #{uuid})"
|
||||
rescue AASM::InvalidTransition => e
|
||||
uuid = Honeybadger.notify(e)
|
||||
redirect_to @warehouse_order, alert: "couldn't dispatch order! wrong state? (please report EID: #{uuid})"
|
||||
end
|
||||
redirect_to @warehouse_order, flash: { success: "successfully sent to warehouse!" }
|
||||
end
|
||||
|
||||
# POST /warehouse/orders or /warehouse/orders.json
|
||||
def create
|
||||
@warehouse_order = Warehouse::Order.new(
|
||||
warehouse_order_params.merge(
|
||||
user: current_user,
|
||||
source_tag: SourceTag.web_tag,
|
||||
)
|
||||
)
|
||||
|
||||
authorize @warehouse_order
|
||||
|
||||
respond_to do |format|
|
||||
if @warehouse_order.save
|
||||
format.html { redirect_to @warehouse_order, notice: "Order was successfully created." }
|
||||
format.json { render :show, status: :created, location: @warehouse_order }
|
||||
else
|
||||
format.html { render :new, status: :unprocessable_entity }
|
||||
format.json { render json: @warehouse_order.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PATCH/PUT /warehouse/orders/1 or /warehouse/orders/1.json
|
||||
def update
|
||||
authorize @warehouse_order
|
||||
respond_to do |format|
|
||||
if @warehouse_order.update(warehouse_order_params)
|
||||
format.html { redirect_to @warehouse_order, notice: "Order was successfully updated." }
|
||||
format.json { render :show, status: :ok, location: @warehouse_order }
|
||||
else
|
||||
format.html { render :edit, status: :unprocessable_entity }
|
||||
format.json { render json: @warehouse_order.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cancel
|
||||
authorize @warehouse_order
|
||||
unless @warehouse_order.may_mark_canceled?
|
||||
redirect_back_or_to @warehouse_order, alert: "order is not in a cancelable state!"
|
||||
end
|
||||
end
|
||||
|
||||
def confirm_cancel
|
||||
authorize @warehouse_order, :cancel?
|
||||
|
||||
reason = params.require(:cancellation_reason)
|
||||
begin
|
||||
@warehouse_order.cancel!(reason)
|
||||
rescue Zenventory::ZenventoryError => e
|
||||
redirect_to @warehouse_order, alert: "couldn't cancel order! zenventory said: #{e.message}"
|
||||
rescue AASM::InvalidTransition => e
|
||||
redirect_to @warehouse_order, alert: "couldn't cancel order! wrong state?"
|
||||
end
|
||||
end
|
||||
|
||||
# # DELETE /warehouse/orders/1 or /warehouse/orders/1.json
|
||||
def destroy
|
||||
authorize @warehouse_order
|
||||
@warehouse_order.destroy!
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to warehouse_orders_path, status: :see_other, notice: "it's gone." }
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Use callbacks to share common setup or constraints between actions.
|
||||
def set_warehouse_order
|
||||
@warehouse_order = Warehouse::Order.find_by!(hc_id: params.expect(:id))
|
||||
end
|
||||
|
||||
# Only allow a list of trusted parameters through.
|
||||
def warehouse_order_params
|
||||
params.require(:warehouse_order).permit(
|
||||
:user_facing_title,
|
||||
:user_facing_description,
|
||||
:internal_notes,
|
||||
:recipient_email,
|
||||
:notify_on_dispatch,
|
||||
tags: [],
|
||||
line_items_attributes: [:id, :sku_id, :quantity, :_destroy],
|
||||
address_attributes: %i[first_name last_name line_1 line_2 city state postal_code country phone_number email],
|
||||
).compact_blank
|
||||
end
|
||||
end
|
||||
68
app/controllers/warehouse/skus_controller.rb
Normal file
68
app/controllers/warehouse/skus_controller.rb
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
class Warehouse::SKUsController < ApplicationController
|
||||
before_action :set_warehouse_sku, only: %i[ show edit update ]
|
||||
|
||||
# GET /warehouse/skus or /warehouse/skus.json
|
||||
def index
|
||||
authorize Warehouse::SKU
|
||||
@warehouse_skus = params[:include_non_inventory] ? Warehouse::SKU.all : Warehouse::SKU.in_inventory
|
||||
end
|
||||
|
||||
# GET /warehouse/skus/1 or /warehouse/skus/1.json
|
||||
def show
|
||||
authorize @warehouse_sku
|
||||
end
|
||||
|
||||
# GET /warehouse/skus/new
|
||||
def new
|
||||
authorize Warehouse::SKU
|
||||
@warehouse_sku = Warehouse::SKU.new
|
||||
end
|
||||
|
||||
# GET /warehouse/skus/1/edit
|
||||
def edit
|
||||
authorize @warehouse_sku
|
||||
end
|
||||
|
||||
# POST /warehouse/skus or /warehouse/skus.json
|
||||
def create
|
||||
@warehouse_sku = Warehouse::SKU.new(warehouse_sku_params)
|
||||
|
||||
authorize @warehouse_sku
|
||||
|
||||
respond_to do |format|
|
||||
if @warehouse_sku.save
|
||||
format.html { redirect_to @warehouse_sku, notice: "WarehouseSKU was successfully created." }
|
||||
format.json { render :show, status: :created, location: @warehouse_sku }
|
||||
else
|
||||
format.html { render :new, status: :unprocessable_entity }
|
||||
format.json { render json: @warehouse_sku.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PATCH/PUT /warehouse/skus/1 or /warehouse/skus/1.json
|
||||
def update
|
||||
authorize @warehouse_sku
|
||||
respond_to do |format|
|
||||
if @warehouse_sku.update(warehouse_sku_params)
|
||||
format.html { redirect_to @warehouse_sku, notice: "WarehouseSKU was successfully updated." }
|
||||
format.json { render :show, status: :ok, location: @warehouse_sku }
|
||||
else
|
||||
format.html { render :edit, status: :unprocessable_entity }
|
||||
format.json { render json: @warehouse_sku.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
# Use callbacks to share common setup or constraints between actions.
|
||||
def set_warehouse_sku
|
||||
@warehouse_sku = Warehouse::SKU.find(params.expect(:id))
|
||||
end
|
||||
|
||||
# Only allow a list of trusted parameters through.
|
||||
def warehouse_sku_params
|
||||
params.expect(warehouse_sku: [ :sku, :description, :unit_cost, :customs_description, :in_stock, :ai_enabled, :enabled ])
|
||||
end
|
||||
end
|
||||
80
app/controllers/warehouse/templates_controller.rb
Normal file
80
app/controllers/warehouse/templates_controller.rb
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
class Warehouse::TemplatesController < ApplicationController
|
||||
before_action :set_warehouse_template, only: %i[ show edit update destroy ]
|
||||
|
||||
# GET /warehouse/templates or /warehouse/templates.json
|
||||
def index
|
||||
authorize Warehouse::Template
|
||||
@warehouse_templates = Warehouse::Template.all
|
||||
end
|
||||
|
||||
# GET /warehouse/templates/1 or /warehouse/templates/1.json
|
||||
def show
|
||||
authorize @warehouse_template
|
||||
end
|
||||
|
||||
# GET /warehouse/templates/new
|
||||
def new
|
||||
authorize Warehouse::Template
|
||||
@warehouse_template = Warehouse::Template.new
|
||||
end
|
||||
|
||||
# GET /warehouse/templates/1/edit
|
||||
def edit
|
||||
authorize @warehouse_template
|
||||
end
|
||||
|
||||
# POST /warehouse/templates or /warehouse/templates.json
|
||||
def create
|
||||
@warehouse_template = Warehouse::Template.new(warehouse_template_params.merge(user: current_user, source_tag: SourceTag.web_tag))
|
||||
authorize @warehouse_template
|
||||
|
||||
respond_to do |format|
|
||||
if @warehouse_template.save
|
||||
format.html { redirect_to @warehouse_template, notice: "Template was successfully created." }
|
||||
format.json { render :show, status: :created, location: @warehouse_template }
|
||||
else
|
||||
format.html { render :new, status: :unprocessable_entity }
|
||||
format.json { render json: @warehouse_template.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PATCH/PUT /warehouse/templates/1 or /warehouse/templates/1.json
|
||||
def update
|
||||
authorize @warehouse_template
|
||||
respond_to do |format|
|
||||
if @warehouse_template.update(warehouse_template_params)
|
||||
format.html { redirect_to @warehouse_template, notice: "Template was successfully updated." }
|
||||
format.json { render :show, status: :ok, location: @warehouse_template }
|
||||
else
|
||||
format.html { render :edit, status: :unprocessable_entity }
|
||||
format.json { render json: @warehouse_template.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /warehouse/templates/1 or /warehouse/templates/1.json
|
||||
def destroy
|
||||
authorize @warehouse_template
|
||||
@warehouse_template.destroy!
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to warehouse_templates_path, status: :see_other, notice: "Template was successfully destroyed." }
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Use callbacks to share common setup or constraints between actions.
|
||||
def set_warehouse_template
|
||||
@warehouse_template = Warehouse::Template.find(params.require(:id))
|
||||
end
|
||||
|
||||
# Only allow a list of trusted parameters through.
|
||||
def warehouse_template_params
|
||||
params.require(:warehouse_template).permit(
|
||||
:name, :public,
|
||||
line_items_attributes: [ :id, :sku_id, :quantity, :_destroy ]
|
||||
)
|
||||
end
|
||||
end
|
||||
87
app/dashboards/address_dashboard.rb
Normal file
87
app/dashboards/address_dashboard.rb
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
require "administrate/base_dashboard"
|
||||
|
||||
class AddressDashboard < Administrate::BaseDashboard
|
||||
# ATTRIBUTE_TYPES
|
||||
# a hash that describes the type of each of the model's fields.
|
||||
#
|
||||
# Each different type represents an Administrate::Field object,
|
||||
# which determines how the attribute is displayed
|
||||
# on pages throughout the dashboard.
|
||||
ATTRIBUTE_TYPES = {
|
||||
id: Field::Number,
|
||||
city: Field::String,
|
||||
country: Field::Select.with_options(searchable: false, collection: ->(field) { field.resource.class.send(field.attribute.to_s.pluralize).keys }),
|
||||
first_name: Field::String,
|
||||
last_name: Field::String,
|
||||
line_1: Field::String,
|
||||
line_2: Field::String,
|
||||
phone_number: Field::String,
|
||||
postal_code: Field::String,
|
||||
state: Field::String,
|
||||
created_at: Field::DateTime,
|
||||
updated_at: Field::DateTime
|
||||
}.freeze
|
||||
|
||||
# COLLECTION_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's index page.
|
||||
#
|
||||
# By default, it's limited to four items to reduce clutter on index pages.
|
||||
# Feel free to add, remove, or rearrange items.
|
||||
COLLECTION_ATTRIBUTES = %i[
|
||||
id
|
||||
city
|
||||
country
|
||||
first_name
|
||||
].freeze
|
||||
|
||||
# SHOW_PAGE_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's show page.
|
||||
SHOW_PAGE_ATTRIBUTES = %i[
|
||||
id
|
||||
city
|
||||
country
|
||||
first_name
|
||||
last_name
|
||||
line_1
|
||||
line_2
|
||||
phone_number
|
||||
postal_code
|
||||
state
|
||||
created_at
|
||||
updated_at
|
||||
].freeze
|
||||
|
||||
# FORM_ATTRIBUTES
|
||||
# an array of attributes that will be displayed
|
||||
# on the model's form (`new` and `edit`) pages.
|
||||
FORM_ATTRIBUTES = %i[
|
||||
city
|
||||
country
|
||||
first_name
|
||||
last_name
|
||||
line_1
|
||||
line_2
|
||||
phone_number
|
||||
postal_code
|
||||
state
|
||||
].freeze
|
||||
|
||||
# COLLECTION_FILTERS
|
||||
# a hash that defines filters that can be used while searching via the search
|
||||
# field of the dashboard.
|
||||
#
|
||||
# For example to add an option to search for open resources by typing "open:"
|
||||
# in the search field:
|
||||
#
|
||||
# COLLECTION_FILTERS = {
|
||||
# open: ->(resources) { resources.where(open: true) }
|
||||
# }.freeze
|
||||
COLLECTION_FILTERS = {}.freeze
|
||||
|
||||
# Overwrite this method to customize how addresses are displayed
|
||||
# across all pages of the admin dashboard.
|
||||
#
|
||||
# def display_resource(address)
|
||||
# "Address ##{address.id}"
|
||||
# end
|
||||
end
|
||||
66
app/dashboards/common_tag_dashboard.rb
Normal file
66
app/dashboards/common_tag_dashboard.rb
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
require "administrate/base_dashboard"
|
||||
|
||||
class CommonTagDashboard < Administrate::BaseDashboard
|
||||
# ATTRIBUTE_TYPES
|
||||
# a hash that describes the type of each of the model's fields.
|
||||
#
|
||||
# Each different type represents an Administrate::Field object,
|
||||
# which determines how the attribute is displayed
|
||||
# on pages throughout the dashboard.
|
||||
ATTRIBUTE_TYPES = {
|
||||
id: Field::Number,
|
||||
implies_ysws: Field::Boolean,
|
||||
tag: Field::String,
|
||||
created_at: Field::DateTime,
|
||||
updated_at: Field::DateTime,
|
||||
}.freeze
|
||||
|
||||
# COLLECTION_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's index page.
|
||||
#
|
||||
# By default, it's limited to four items to reduce clutter on index pages.
|
||||
# Feel free to add, remove, or rearrange items.
|
||||
COLLECTION_ATTRIBUTES = %i[
|
||||
id
|
||||
implies_ysws
|
||||
tag
|
||||
created_at
|
||||
].freeze
|
||||
|
||||
# SHOW_PAGE_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's show page.
|
||||
SHOW_PAGE_ATTRIBUTES = %i[
|
||||
id
|
||||
implies_ysws
|
||||
tag
|
||||
created_at
|
||||
updated_at
|
||||
].freeze
|
||||
|
||||
# FORM_ATTRIBUTES
|
||||
# an array of attributes that will be displayed
|
||||
# on the model's form (`new` and `edit`) pages.
|
||||
FORM_ATTRIBUTES = %i[
|
||||
implies_ysws
|
||||
tag
|
||||
].freeze
|
||||
|
||||
# COLLECTION_FILTERS
|
||||
# a hash that defines filters that can be used while searching via the search
|
||||
# field of the dashboard.
|
||||
#
|
||||
# For example to add an option to search for open resources by typing "open:"
|
||||
# in the search field:
|
||||
#
|
||||
# COLLECTION_FILTERS = {
|
||||
# open: ->(resources) { resources.where(open: true) }
|
||||
# }.freeze
|
||||
COLLECTION_FILTERS = {}.freeze
|
||||
|
||||
# Overwrite this method to customize how common tags are displayed
|
||||
# across all pages of the admin dashboard.
|
||||
#
|
||||
# def display_resource(common_tag)
|
||||
# "CommonTag ##{common_tag.id}"
|
||||
# end
|
||||
end
|
||||
90
app/dashboards/return_address_dashboard.rb
Normal file
90
app/dashboards/return_address_dashboard.rb
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
require "administrate/base_dashboard"
|
||||
|
||||
class ReturnAddressDashboard < Administrate::BaseDashboard
|
||||
# ATTRIBUTE_TYPES
|
||||
# a hash that describes the type of each of the model's fields.
|
||||
#
|
||||
# Each different type represents an Administrate::Field object,
|
||||
# which determines how the attribute is displayed
|
||||
# on pages throughout the dashboard.
|
||||
ATTRIBUTE_TYPES = {
|
||||
id: Field::Number,
|
||||
name: Field::String,
|
||||
line_1: Field::String,
|
||||
line_2: Field::String,
|
||||
city: Field::String,
|
||||
country: Field::Select.with_options(searchable: false, collection: ->(field) { field.resource.class.send(field.attribute.to_s.pluralize).keys }),
|
||||
postal_code: Field::String,
|
||||
shared: Field::Boolean,
|
||||
state: Field::String,
|
||||
user: Field::BelongsTo.with_options(required: false),
|
||||
created_at: Field::DateTime,
|
||||
updated_at: Field::DateTime
|
||||
}.freeze
|
||||
|
||||
# COLLECTION_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's index page.
|
||||
#
|
||||
# By default, it's limited to four items to reduce clutter on index pages.
|
||||
# Feel free to add, remove, or rearrange items.
|
||||
COLLECTION_ATTRIBUTES = %i[
|
||||
name
|
||||
user
|
||||
shared
|
||||
city
|
||||
].freeze
|
||||
|
||||
# SHOW_PAGE_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's show page.
|
||||
SHOW_PAGE_ATTRIBUTES = %i[
|
||||
id
|
||||
name
|
||||
line_1
|
||||
line_2
|
||||
city
|
||||
state
|
||||
postal_code
|
||||
country
|
||||
shared
|
||||
user
|
||||
created_at
|
||||
updated_at
|
||||
].freeze
|
||||
|
||||
# FORM_ATTRIBUTES
|
||||
# an array of attributes that will be displayed
|
||||
# on the model's form (`new` and `edit`) pages.
|
||||
FORM_ATTRIBUTES = %i[
|
||||
name
|
||||
line_1
|
||||
line_2
|
||||
city
|
||||
state
|
||||
postal_code
|
||||
country
|
||||
shared
|
||||
user
|
||||
].freeze
|
||||
|
||||
# COLLECTION_FILTERS
|
||||
# a hash that defines filters that can be used while searching via the search
|
||||
# field of the dashboard.
|
||||
#
|
||||
# For example to add an option to search for open resources by typing "open:"
|
||||
# in the search field:
|
||||
#
|
||||
# COLLECTION_FILTERS = {
|
||||
# open: ->(resources) { resources.where(open: true) }
|
||||
# }.freeze
|
||||
COLLECTION_FILTERS = {
|
||||
shared: ->(resources) { resources.where(shared: true) },
|
||||
unshared: ->(resources) { resources.where(shared: false) }
|
||||
}.freeze
|
||||
|
||||
# Overwrite this method to customize how return addresses are displayed
|
||||
# across all pages of the admin dashboard.
|
||||
#
|
||||
def display_resource(return_address)
|
||||
return_address.name
|
||||
end
|
||||
end
|
||||
72
app/dashboards/source_tag_dashboard.rb
Normal file
72
app/dashboards/source_tag_dashboard.rb
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
require "administrate/base_dashboard"
|
||||
|
||||
class SourceTagDashboard < Administrate::BaseDashboard
|
||||
# ATTRIBUTE_TYPES
|
||||
# a hash that describes the type of each of the model's fields.
|
||||
#
|
||||
# Each different type represents an Administrate::Field object,
|
||||
# which determines how the attribute is displayed
|
||||
# on pages throughout the dashboard.
|
||||
ATTRIBUTE_TYPES = {
|
||||
id: Field::Number,
|
||||
name: Field::String,
|
||||
owner: Field::String,
|
||||
slug: Field::String,
|
||||
warehouse_orders: Field::HasMany,
|
||||
created_at: Field::DateTime,
|
||||
updated_at: Field::DateTime
|
||||
}.freeze
|
||||
|
||||
# COLLECTION_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's index page.
|
||||
#
|
||||
# By default, it's limited to four items to reduce clutter on index pages.
|
||||
# Feel free to add, remove, or rearrange items.
|
||||
COLLECTION_ATTRIBUTES = %i[
|
||||
id
|
||||
name
|
||||
owner
|
||||
slug
|
||||
].freeze
|
||||
|
||||
# SHOW_PAGE_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's show page.
|
||||
SHOW_PAGE_ATTRIBUTES = %i[
|
||||
id
|
||||
name
|
||||
owner
|
||||
slug
|
||||
warehouse_orders
|
||||
created_at
|
||||
updated_at
|
||||
].freeze
|
||||
|
||||
# FORM_ATTRIBUTES
|
||||
# an array of attributes that will be displayed
|
||||
# on the model's form (`new` and `edit`) pages.
|
||||
FORM_ATTRIBUTES = %i[
|
||||
name
|
||||
owner
|
||||
slug
|
||||
warehouse_orders
|
||||
].freeze
|
||||
|
||||
# COLLECTION_FILTERS
|
||||
# a hash that defines filters that can be used while searching via the search
|
||||
# field of the dashboard.
|
||||
#
|
||||
# For example to add an option to search for open resources by typing "open:"
|
||||
# in the search field:
|
||||
#
|
||||
# COLLECTION_FILTERS = {
|
||||
# open: ->(resources) { resources.where(open: true) }
|
||||
# }.freeze
|
||||
COLLECTION_FILTERS = {}.freeze
|
||||
|
||||
# Overwrite this method to customize how source tags are displayed
|
||||
# across all pages of the admin dashboard.
|
||||
#
|
||||
def display_resource(source_tag)
|
||||
source_tag.slug
|
||||
end
|
||||
end
|
||||
87
app/dashboards/user_dashboard.rb
Normal file
87
app/dashboards/user_dashboard.rb
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
require "administrate/base_dashboard"
|
||||
|
||||
class UserDashboard < Administrate::BaseDashboard
|
||||
# ATTRIBUTE_TYPES
|
||||
# a hash that describes the type of each of the model's fields.
|
||||
#
|
||||
# Each different type represents an Administrate::Field object,
|
||||
# which determines how the attribute is displayed
|
||||
# on pages throughout the dashboard.
|
||||
ATTRIBUTE_TYPES = {
|
||||
id: Field::Number,
|
||||
can_warehouse: Field::Boolean,
|
||||
email: Field::String,
|
||||
icon_url: Field::String,
|
||||
is_admin: Field::Boolean,
|
||||
slack_id: Field::String,
|
||||
username: Field::String,
|
||||
warehouse_templates: Field::HasMany,
|
||||
home_mid: Field::BelongsTo,
|
||||
home_return_address: Field::BelongsTo,
|
||||
created_at: Field::DateTime,
|
||||
updated_at: Field::DateTime,
|
||||
}.freeze
|
||||
|
||||
# COLLECTION_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's index page.
|
||||
#
|
||||
# By default, it's limited to four items to reduce clutter on index pages.
|
||||
# Feel free to add, remove, or rearrange items.
|
||||
COLLECTION_ATTRIBUTES = %i[
|
||||
username
|
||||
is_admin
|
||||
can_warehouse
|
||||
email
|
||||
slack_id
|
||||
].freeze
|
||||
|
||||
# SHOW_PAGE_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's show page.
|
||||
SHOW_PAGE_ATTRIBUTES = %i[
|
||||
id
|
||||
can_warehouse
|
||||
email
|
||||
icon_url
|
||||
is_admin
|
||||
slack_id
|
||||
username
|
||||
warehouse_templates
|
||||
home_mid
|
||||
home_return_address
|
||||
created_at
|
||||
updated_at
|
||||
].freeze
|
||||
|
||||
# FORM_ATTRIBUTES
|
||||
# an array of attributes that will be displayed
|
||||
# on the model's form (`new` and `edit`) pages.
|
||||
FORM_ATTRIBUTES = %i[
|
||||
can_warehouse
|
||||
email
|
||||
icon_url
|
||||
is_admin
|
||||
slack_id
|
||||
username
|
||||
home_mid
|
||||
home_return_address
|
||||
].freeze
|
||||
|
||||
# COLLECTION_FILTERS
|
||||
# a hash that defines filters that can be used while searching via the search
|
||||
# field of the dashboard.
|
||||
#
|
||||
# For example to add an option to search for open resources by typing "open:"
|
||||
# in the search field:
|
||||
#
|
||||
# COLLECTION_FILTERS = {
|
||||
# open: ->(resources) { resources.where(open: true) }
|
||||
# }.freeze
|
||||
COLLECTION_FILTERS = {}.freeze
|
||||
|
||||
# Overwrite this method to customize how users are displayed
|
||||
# across all pages of the admin dashboard.
|
||||
#
|
||||
def display_resource(user)
|
||||
user.username
|
||||
end
|
||||
end
|
||||
71
app/dashboards/usps/mailer_id_dashboard.rb
Normal file
71
app/dashboards/usps/mailer_id_dashboard.rb
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
require "administrate/base_dashboard"
|
||||
|
||||
module USPS
|
||||
class MailerIdDashboard < Administrate::BaseDashboard
|
||||
# ATTRIBUTE_TYPES
|
||||
# a hash that describes the type of each of the model's fields.
|
||||
#
|
||||
# Each different type represents an Administrate::Field object,
|
||||
# which determines how the attribute is displayed
|
||||
# on pages throughout the dashboard.
|
||||
ATTRIBUTE_TYPES = {
|
||||
id: Field::Number,
|
||||
crid: Field::String,
|
||||
mid: Field::String,
|
||||
name: Field::String,
|
||||
created_at: Field::DateTime,
|
||||
updated_at: Field::DateTime
|
||||
}.freeze
|
||||
|
||||
# COLLECTION_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's index page.
|
||||
#
|
||||
# By default, it's limited to four items to reduce clutter on index pages.
|
||||
# Feel free to add, remove, or rearrange items.
|
||||
COLLECTION_ATTRIBUTES = %i[
|
||||
id
|
||||
crid
|
||||
mid
|
||||
name
|
||||
].freeze
|
||||
|
||||
# SHOW_PAGE_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's show page.
|
||||
SHOW_PAGE_ATTRIBUTES = %i[
|
||||
id
|
||||
crid
|
||||
mid
|
||||
name
|
||||
created_at
|
||||
updated_at
|
||||
].freeze
|
||||
|
||||
# FORM_ATTRIBUTES
|
||||
# an array of attributes that will be displayed
|
||||
# on the model's form (`new` and `edit`) pages.
|
||||
FORM_ATTRIBUTES = %i[
|
||||
crid
|
||||
mid
|
||||
name
|
||||
].freeze
|
||||
|
||||
# COLLECTION_FILTERS
|
||||
# a hash that defines filters that can be used while searching via the search
|
||||
# field of the dashboard.
|
||||
#
|
||||
# For example to add an option to search for open resources by typing "open:"
|
||||
# in the search field:
|
||||
#
|
||||
# COLLECTION_FILTERS = {
|
||||
# open: ->(resources) { resources.where(open: true) }
|
||||
# }.freeze
|
||||
COLLECTION_FILTERS = {}.freeze
|
||||
|
||||
# Overwrite this method to customize how mailer ids are displayed
|
||||
# across all pages of the admin dashboard.
|
||||
#
|
||||
# def display_resource(mailer_id)
|
||||
# "USPS::MailerId ##{mailer_id.id}"
|
||||
# end
|
||||
end
|
||||
end
|
||||
86
app/dashboards/usps/payment_account_dashboard.rb
Normal file
86
app/dashboards/usps/payment_account_dashboard.rb
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
require "administrate/base_dashboard"
|
||||
|
||||
module USPS
|
||||
class PaymentAccountDashboard < Administrate::BaseDashboard
|
||||
# ATTRIBUTE_TYPES
|
||||
# a hash that describes the type of each of the model's fields.
|
||||
#
|
||||
# Each different type represents an Administrate::Field object,
|
||||
# which determines how the attribute is displayed
|
||||
# on pages throughout the dashboard.
|
||||
ATTRIBUTE_TYPES = {
|
||||
id: Field::Number,
|
||||
account_number: Field::String,
|
||||
account_type: Field::Select.with_options(searchable: false, collection: ->(field) { field.resource.class.send(field.attribute.to_s.pluralize).keys }),
|
||||
manifest_mid: Field::String,
|
||||
name: Field::String,
|
||||
permit_number: Field::String,
|
||||
permit_zip: Field::String,
|
||||
usps_mailer_id: Field::BelongsTo,
|
||||
created_at: Field::DateTime,
|
||||
updated_at: Field::DateTime,
|
||||
ach: Field::Boolean,
|
||||
}.freeze
|
||||
|
||||
# COLLECTION_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's index page.
|
||||
#
|
||||
# By default, it's limited to four items to reduce clutter on index pages.
|
||||
# Feel free to add, remove, or rearrange items.
|
||||
COLLECTION_ATTRIBUTES = %i[
|
||||
id
|
||||
account_number
|
||||
account_type
|
||||
manifest_mid
|
||||
].freeze
|
||||
|
||||
# SHOW_PAGE_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's show page.
|
||||
SHOW_PAGE_ATTRIBUTES = %i[
|
||||
id
|
||||
account_number
|
||||
account_type
|
||||
manifest_mid
|
||||
name
|
||||
permit_number
|
||||
permit_zip
|
||||
usps_mailer_id
|
||||
created_at
|
||||
updated_at
|
||||
ach
|
||||
].freeze
|
||||
|
||||
# FORM_ATTRIBUTES
|
||||
# an array of attributes that will be displayed
|
||||
# on the model's form (`new` and `edit`) pages.
|
||||
FORM_ATTRIBUTES = %i[
|
||||
account_number
|
||||
account_type
|
||||
manifest_mid
|
||||
name
|
||||
permit_number
|
||||
permit_zip
|
||||
usps_mailer_id
|
||||
ach
|
||||
].freeze
|
||||
|
||||
# COLLECTION_FILTERS
|
||||
# a hash that defines filters that can be used while searching via the search
|
||||
# field of the dashboard.
|
||||
#
|
||||
# For example to add an option to search for open resources by typing "open:"
|
||||
# in the search field:
|
||||
#
|
||||
# COLLECTION_FILTERS = {
|
||||
# open: ->(resources) { resources.where(open: true) }
|
||||
# }.freeze
|
||||
COLLECTION_FILTERS = {}.freeze
|
||||
|
||||
# Overwrite this method to customize how payment accounts are displayed
|
||||
# across all pages of the admin dashboard.
|
||||
#
|
||||
# def display_resource(payment_account)
|
||||
# "USPS::PaymentAccount ##{payment_account.id}"
|
||||
# end
|
||||
end
|
||||
end
|
||||
74
app/dashboards/warehouse/line_item_dashboard.rb
Normal file
74
app/dashboards/warehouse/line_item_dashboard.rb
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
require "administrate/base_dashboard"
|
||||
|
||||
module Warehouse
|
||||
class LineItemDashboard < Administrate::BaseDashboard
|
||||
# ATTRIBUTE_TYPES
|
||||
# a hash that describes the type of each of the model's fields.
|
||||
#
|
||||
# Each different type represents an Administrate::Field object,
|
||||
# which determines how the attribute is displayed
|
||||
# on pages throughout the dashboard.
|
||||
ATTRIBUTE_TYPES = {
|
||||
id: Field::Number,
|
||||
order: Field::BelongsTo,
|
||||
quantity: Field::Number,
|
||||
sku: Field::BelongsTo,
|
||||
template: Field::BelongsTo,
|
||||
created_at: Field::DateTime,
|
||||
updated_at: Field::DateTime
|
||||
}.freeze
|
||||
|
||||
# COLLECTION_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's index page.
|
||||
#
|
||||
# By default, it's limited to four items to reduce clutter on index pages.
|
||||
# Feel free to add, remove, or rearrange items.
|
||||
COLLECTION_ATTRIBUTES = %i[
|
||||
id
|
||||
order
|
||||
quantity
|
||||
sku
|
||||
].freeze
|
||||
|
||||
# SHOW_PAGE_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's show page.
|
||||
SHOW_PAGE_ATTRIBUTES = %i[
|
||||
id
|
||||
order
|
||||
quantity
|
||||
sku
|
||||
template
|
||||
created_at
|
||||
updated_at
|
||||
].freeze
|
||||
|
||||
# FORM_ATTRIBUTES
|
||||
# an array of attributes that will be displayed
|
||||
# on the model's form (`new` and `edit`) pages.
|
||||
FORM_ATTRIBUTES = %i[
|
||||
order
|
||||
quantity
|
||||
sku
|
||||
template
|
||||
].freeze
|
||||
|
||||
# COLLECTION_FILTERS
|
||||
# a hash that defines filters that can be used while searching via the search
|
||||
# field of the dashboard.
|
||||
#
|
||||
# For example to add an option to search for open resources by typing "open:"
|
||||
# in the search field:
|
||||
#
|
||||
# COLLECTION_FILTERS = {
|
||||
# open: ->(resources) { resources.where(open: true) }
|
||||
# }.freeze
|
||||
COLLECTION_FILTERS = {}.freeze
|
||||
|
||||
# Overwrite this method to customize how line items are displayed
|
||||
# across all pages of the admin dashboard.
|
||||
#
|
||||
# def display_resource(line_item)
|
||||
# "Warehouse::LineItem ##{line_item.id}"
|
||||
# end
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue