From 7d1d6b3a72f068aea0c2dcc2d3e8e610cf98705c Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Tue, 26 Nov 2024 05:33:39 -0500 Subject: [PATCH 01/73] rails 7 initial commit --- app/admin/dashboard.rb | 33 ++++++++++ app/assets/config/manifest.js | 4 ++ app/assets/images/.keep | 0 app/assets/javascripts/active_admin.js | 1 + app/assets/stylesheets/active_admin.scss | 17 +++++ app/assets/stylesheets/application.css | 15 +++++ app/channels/application_cable/channel.rb | 4 ++ app/channels/application_cable/connection.rb | 4 ++ app/controllers/application_controller.rb | 4 ++ app/controllers/concerns/.keep | 0 app/helpers/application_helper.rb | 2 + app/javascript/application.js | 3 + app/javascript/controllers/application.js | 9 +++ .../controllers/hello_controller.js | 7 ++ app/javascript/controllers/index.js | 4 ++ app/jobs/application_job.rb | 7 ++ app/mailers/application_mailer.rb | 4 ++ app/models/api_key.rb | 2 + app/models/application_record.rb | 3 + app/models/authentication.rb | 2 + app/models/concerns/.keep | 0 app/models/gift_card.rb | 2 + app/models/person.rb | 2 + app/views/layouts/application.html.erb | 23 +++++++ app/views/layouts/mailer.html.erb | 13 ++++ app/views/layouts/mailer.text.erb | 1 + app/views/pwa/manifest.json.erb | 22 +++++++ app/views/pwa/service-worker.js | 26 ++++++++ .../20241126092131_create_gift_cards.rb | 13 ++++ db/migrate/20241126092151_create_people.rb | 10 +++ .../20241126092204_create_authentications.rb | 13 ++++ db/migrate/20241126092217_create_api_keys.rb | 10 +++ ...092230_add_certificate_id_to_gift_cards.rb | 5 ++ ...1126094659_create_active_admin_comments.rb | 16 +++++ db/schema.rb | 65 +++++++++++++++++++ db/seeds.rb | 9 +++ 36 files changed, 355 insertions(+) create mode 100644 app/admin/dashboard.rb create mode 100644 app/assets/config/manifest.js create mode 100644 app/assets/images/.keep create mode 100644 app/assets/javascripts/active_admin.js create mode 100644 app/assets/stylesheets/active_admin.scss create mode 100644 app/assets/stylesheets/application.css create mode 100644 app/channels/application_cable/channel.rb create mode 100644 app/channels/application_cable/connection.rb create mode 100644 app/controllers/application_controller.rb create mode 100644 app/controllers/concerns/.keep create mode 100644 app/helpers/application_helper.rb create mode 100644 app/javascript/application.js create mode 100644 app/javascript/controllers/application.js create mode 100644 app/javascript/controllers/hello_controller.js create mode 100644 app/javascript/controllers/index.js create mode 100644 app/jobs/application_job.rb create mode 100644 app/mailers/application_mailer.rb create mode 100644 app/models/api_key.rb create mode 100644 app/models/application_record.rb create mode 100644 app/models/authentication.rb create mode 100644 app/models/concerns/.keep create mode 100644 app/models/gift_card.rb create mode 100644 app/models/person.rb create mode 100644 app/views/layouts/application.html.erb create mode 100644 app/views/layouts/mailer.html.erb create mode 100644 app/views/layouts/mailer.text.erb create mode 100644 app/views/pwa/manifest.json.erb create mode 100644 app/views/pwa/service-worker.js create mode 100644 db/migrate/20241126092131_create_gift_cards.rb create mode 100644 db/migrate/20241126092151_create_people.rb create mode 100644 db/migrate/20241126092204_create_authentications.rb create mode 100644 db/migrate/20241126092217_create_api_keys.rb create mode 100644 db/migrate/20241126092230_add_certificate_id_to_gift_cards.rb create mode 100644 db/migrate/20241126094659_create_active_admin_comments.rb create mode 100644 db/schema.rb create mode 100644 db/seeds.rb diff --git a/app/admin/dashboard.rb b/app/admin/dashboard.rb new file mode 100644 index 0000000..3824c62 --- /dev/null +++ b/app/admin/dashboard.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true +ActiveAdmin.register_page "Dashboard" do + menu priority: 1, label: proc { I18n.t("active_admin.dashboard") } + + content title: proc { I18n.t("active_admin.dashboard") } do + div class: "blank_slate_container", id: "dashboard_default_message" do + span class: "blank_slate" do + span I18n.t("active_admin.dashboard_welcome.welcome") + small I18n.t("active_admin.dashboard_welcome.call_to_action") + end + end + + # Here is an example of a simple dashboard with columns and panels. + # + # columns do + # column do + # panel "Recent Posts" do + # ul do + # Post.recent(5).map do |post| + # li link_to(post.title, admin_post_path(post)) + # end + # end + # end + # end + + # column do + # panel "Info" do + # para "Welcome to ActiveAdmin." + # end + # end + # end + end # content +end diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js new file mode 100644 index 0000000..ddd546a --- /dev/null +++ b/app/assets/config/manifest.js @@ -0,0 +1,4 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/app/assets/images/.keep b/app/assets/images/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/javascripts/active_admin.js b/app/assets/javascripts/active_admin.js new file mode 100644 index 0000000..d2b66c5 --- /dev/null +++ b/app/assets/javascripts/active_admin.js @@ -0,0 +1 @@ +//= require active_admin/base diff --git a/app/assets/stylesheets/active_admin.scss b/app/assets/stylesheets/active_admin.scss new file mode 100644 index 0000000..41c27b3 --- /dev/null +++ b/app/assets/stylesheets/active_admin.scss @@ -0,0 +1,17 @@ +// Sass variable overrides must be declared before loading up Active Admin's styles. +// +// To view the variables that Active Admin provides, take a look at +// `app/assets/stylesheets/active_admin/mixins/_variables.scss` in the +// Active Admin source. +// +// For example, to change the sidebar width: +// $sidebar-width: 242px; + +// Active Admin's got SASS! +@import "active_admin/mixins"; +@import "active_admin/base"; + +// Overriding any non-variable Sass must be done after the fact. +// For example, to change the default status-tag color: +// +// .status_tag { background: #6090DB; } diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css new file mode 100644 index 0000000..288b9ab --- /dev/null +++ b/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb new file mode 100644 index 0000000..d672697 --- /dev/null +++ b/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb new file mode 100644 index 0000000..0ff5442 --- /dev/null +++ b/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb new file mode 100644 index 0000000..0d95db2 --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,4 @@ +class ApplicationController < ActionController::Base + # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. + allow_browser versions: :modern +end diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000..de6be79 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/app/javascript/application.js b/app/javascript/application.js new file mode 100644 index 0000000..0d7b494 --- /dev/null +++ b/app/javascript/application.js @@ -0,0 +1,3 @@ +// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" diff --git a/app/javascript/controllers/application.js b/app/javascript/controllers/application.js new file mode 100644 index 0000000..1213e85 --- /dev/null +++ b/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/app/javascript/controllers/hello_controller.js b/app/javascript/controllers/hello_controller.js new file mode 100644 index 0000000..5975c07 --- /dev/null +++ b/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js new file mode 100644 index 0000000..1156bf8 --- /dev/null +++ b/app/javascript/controllers/index.js @@ -0,0 +1,4 @@ +// Import and register all your controllers from the importmap via controllers/**/*_controller +import { application } from "controllers/application" +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 0000000..d394c3d --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb new file mode 100644 index 0000000..3c34c81 --- /dev/null +++ b/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" +end diff --git a/app/models/api_key.rb b/app/models/api_key.rb new file mode 100644 index 0000000..f44edba --- /dev/null +++ b/app/models/api_key.rb @@ -0,0 +1,2 @@ +class ApiKey < ApplicationRecord +end diff --git a/app/models/application_record.rb b/app/models/application_record.rb new file mode 100644 index 0000000..b63caeb --- /dev/null +++ b/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/app/models/authentication.rb b/app/models/authentication.rb new file mode 100644 index 0000000..a3c8f98 --- /dev/null +++ b/app/models/authentication.rb @@ -0,0 +1,2 @@ +class Authentication < ApplicationRecord +end diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/models/gift_card.rb b/app/models/gift_card.rb new file mode 100644 index 0000000..1b11df6 --- /dev/null +++ b/app/models/gift_card.rb @@ -0,0 +1,2 @@ +class GiftCard < ApplicationRecord +end diff --git a/app/models/person.rb b/app/models/person.rb new file mode 100644 index 0000000..a8b1b85 --- /dev/null +++ b/app/models/person.rb @@ -0,0 +1,2 @@ +class Person < ApplicationRecord +end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000..13f3e77 --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,23 @@ + + + + <%= content_for(:title) || "Familylife Gift Cards" %> + + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= yield :head %> + + + + + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + + <%= yield %> + + diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb new file mode 100644 index 0000000..3aac900 --- /dev/null +++ b/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb new file mode 100644 index 0000000..37f0bdd --- /dev/null +++ b/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/app/views/pwa/manifest.json.erb b/app/views/pwa/manifest.json.erb new file mode 100644 index 0000000..a685c35 --- /dev/null +++ b/app/views/pwa/manifest.json.erb @@ -0,0 +1,22 @@ +{ + "name": "FamilylifeGiftCards", + "icons": [ + { + "src": "/icon.png", + "type": "image/png", + "sizes": "512x512" + }, + { + "src": "/icon.png", + "type": "image/png", + "sizes": "512x512", + "purpose": "maskable" + } + ], + "start_url": "/", + "display": "standalone", + "scope": "/", + "description": "FamilylifeGiftCards.", + "theme_color": "red", + "background_color": "red" +} diff --git a/app/views/pwa/service-worker.js b/app/views/pwa/service-worker.js new file mode 100644 index 0000000..b3a13fb --- /dev/null +++ b/app/views/pwa/service-worker.js @@ -0,0 +1,26 @@ +// Add a service worker for processing Web Push notifications: +// +// self.addEventListener("push", async (event) => { +// const { title, options } = await event.data.json() +// event.waitUntil(self.registration.showNotification(title, options)) +// }) +// +// self.addEventListener("notificationclick", function(event) { +// event.notification.close() +// event.waitUntil( +// clients.matchAll({ type: "window" }).then((clientList) => { +// for (let i = 0; i < clientList.length; i++) { +// let client = clientList[i] +// let clientPath = (new URL(client.url)).pathname +// +// if (clientPath == event.notification.data.path && "focus" in client) { +// return client.focus() +// } +// } +// +// if (clients.openWindow) { +// return clients.openWindow(event.notification.data.path) +// } +// }) +// ) +// }) diff --git a/db/migrate/20241126092131_create_gift_cards.rb b/db/migrate/20241126092131_create_gift_cards.rb new file mode 100644 index 0000000..273ade8 --- /dev/null +++ b/db/migrate/20241126092131_create_gift_cards.rb @@ -0,0 +1,13 @@ +class CreateGiftCards < ActiveRecord::Migration[7.2] + def change + create_table :gift_cards do |t| + t.datetime :expiration_date + t.integer :registrations_available + t.string :associated_product + t.decimal :certificate_value + t.string :gl_code + + t.timestamps + end + end +end diff --git a/db/migrate/20241126092151_create_people.rb b/db/migrate/20241126092151_create_people.rb new file mode 100644 index 0000000..6fa6505 --- /dev/null +++ b/db/migrate/20241126092151_create_people.rb @@ -0,0 +1,10 @@ +class CreatePeople < ActiveRecord::Migration[7.2] + def change + create_table :people do |t| + t.string :first_name + t.string :last_name + + t.timestamps + end + end +end diff --git a/db/migrate/20241126092204_create_authentications.rb b/db/migrate/20241126092204_create_authentications.rb new file mode 100644 index 0000000..ad0a4c0 --- /dev/null +++ b/db/migrate/20241126092204_create_authentications.rb @@ -0,0 +1,13 @@ +class CreateAuthentications < ActiveRecord::Migration[7.2] + def change + create_table :authentications do |t| + t.integer :person_id + t.string :provider + t.string :uid + t.string :token + t.string :username + + t.timestamps + end + end +end diff --git a/db/migrate/20241126092217_create_api_keys.rb b/db/migrate/20241126092217_create_api_keys.rb new file mode 100644 index 0000000..29e797f --- /dev/null +++ b/db/migrate/20241126092217_create_api_keys.rb @@ -0,0 +1,10 @@ +class CreateApiKeys < ActiveRecord::Migration[7.2] + def change + create_table :api_keys do |t| + t.string :access_token + t.string :user + + t.timestamps + end + end +end diff --git a/db/migrate/20241126092230_add_certificate_id_to_gift_cards.rb b/db/migrate/20241126092230_add_certificate_id_to_gift_cards.rb new file mode 100644 index 0000000..6f1f706 --- /dev/null +++ b/db/migrate/20241126092230_add_certificate_id_to_gift_cards.rb @@ -0,0 +1,5 @@ +class AddCertificateIdToGiftCards < ActiveRecord::Migration[7.2] + def change + add_column :gift_cards, :certificate_id, :string + end +end diff --git a/db/migrate/20241126094659_create_active_admin_comments.rb b/db/migrate/20241126094659_create_active_admin_comments.rb new file mode 100644 index 0000000..73e3e00 --- /dev/null +++ b/db/migrate/20241126094659_create_active_admin_comments.rb @@ -0,0 +1,16 @@ +class CreateActiveAdminComments < ActiveRecord::Migration[7.2] + def self.up + create_table :active_admin_comments do |t| + t.string :namespace + t.text :body + t.references :resource, polymorphic: true + t.references :author, polymorphic: true + t.timestamps + end + add_index :active_admin_comments, [:namespace] + end + + def self.down + drop_table :active_admin_comments + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..ee52688 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,65 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[7.2].define(version: 2024_11_26_094659) do + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "active_admin_comments", force: :cascade do |t| + t.string "namespace" + t.text "body" + t.string "resource_type" + t.bigint "resource_id" + t.string "author_type" + t.bigint "author_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["author_type", "author_id"], name: "index_active_admin_comments_on_author" + t.index ["namespace"], name: "index_active_admin_comments_on_namespace" + t.index ["resource_type", "resource_id"], name: "index_active_admin_comments_on_resource" + end + + create_table "api_keys", force: :cascade do |t| + t.string "access_token" + t.string "user" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "authentications", force: :cascade do |t| + t.integer "person_id" + t.string "provider" + t.string "uid" + t.string "token" + t.string "username" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "gift_cards", force: :cascade do |t| + t.datetime "expiration_date" + t.integer "registrations_available" + t.string "associated_product" + t.decimal "certificate_value" + t.string "gl_code" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "certificate_id" + end + + create_table "people", force: :cascade do |t| + t.string "first_name" + t.string "last_name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end +end diff --git a/db/seeds.rb b/db/seeds.rb new file mode 100644 index 0000000..4fbd6ed --- /dev/null +++ b/db/seeds.rb @@ -0,0 +1,9 @@ +# This file should ensure the existence of records required to run the application in every environment (production, +# development, test). The code here should be idempotent so that it can be executed at any point in every environment. +# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). +# +# Example: +# +# ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| +# MovieGenre.find_or_create_by!(name: genre_name) +# end From cd8ba9eac828b2bb5e3e04b707c322fc7e55ac9b Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Tue, 26 Nov 2024 05:39:06 -0500 Subject: [PATCH 02/73] update build.sh and .ruby-version to what rails_8 was --- .ruby-version | 2 +- build.sh | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 build.sh diff --git a/.ruby-version b/.ruby-version index e391e18..9c25013 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-3.3.6 +3.3.6 diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..d107393 --- /dev/null +++ b/build.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +docker buildx build $DOCKER_ARGS \ + --build-arg SIDEKIQ_CREDS=$SIDEKIQ_CREDS \ + --build-arg RUBY_VERSION=$(cat .ruby-version) \ + . +rc=$? + +if [ $rc -ne 0 ]; then + echo -e "Docker build failed" + exit $rc +fi From 52cd8e8905e2afbf6deddff328b2f906c811e484 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 2 Dec 2024 03:19:32 -0500 Subject: [PATCH 03/73] WIP building out admin view to create gift cards --- .gitignore | 2 + Gemfile | 3 + Gemfile.lock | 18 +++ app/admin/gift_card_types.rb | 18 +++ app/admin/gift_cards.rb | 19 +++ app/admin/issuances.rb | 59 +++++++++ app/assets/javascripts/active_admin.js | 1 + app/assets/stylesheets/active_admin.scss | 1 + app/controllers/application_controller.rb | 9 ++ app/models/api_key.rb | 7 + app/models/authentication.rb | 7 + app/models/gift_card.rb | 10 ++ app/models/gift_card_type.rb | 15 +++ app/models/issuance.rb | 122 ++++++++++++++++++ app/models/person.rb | 11 ++ config/initializers/active_admin.rb | 4 +- config/initializers/activeadmin_addons.rb | 12 ++ config/locales/en.yml | 6 +- .../20241126092131_create_gift_cards.rb | 1 + ...092230_add_certificate_id_to_gift_cards.rb | 5 - .../20241126110902_create_gift_card_types.rb | 15 +++ db/migrate/20241126111253_create_issuances.rb | 16 +++ ..._allocated_certificate_ids_to_issuances.rb | 5 + ...241202051127_add_numbering_to_issuances.rb | 5 + ...202065714_add_issuance_id_to_gift_cards.rb | 5 + db/schema.rb | 32 ++++- test/fixtures/gift_card_types.yml | 19 +++ test/fixtures/issuances.yml | 19 +++ test/models/gift_card_type_test.rb | 7 + test/models/issuance_test.rb | 7 + 30 files changed, 450 insertions(+), 10 deletions(-) create mode 100644 app/admin/gift_card_types.rb create mode 100644 app/admin/gift_cards.rb create mode 100644 app/admin/issuances.rb create mode 100644 app/models/gift_card_type.rb create mode 100644 app/models/issuance.rb create mode 100644 config/initializers/activeadmin_addons.rb delete mode 100644 db/migrate/20241126092230_add_certificate_id_to_gift_cards.rb create mode 100644 db/migrate/20241126110902_create_gift_card_types.rb create mode 100644 db/migrate/20241126111253_create_issuances.rb create mode 100644 db/migrate/20241202051016_add_allocated_certificate_ids_to_issuances.rb create mode 100644 db/migrate/20241202051127_add_numbering_to_issuances.rb create mode 100644 db/migrate/20241202065714_add_issuance_id_to_gift_cards.rb create mode 100644 test/fixtures/gift_card_types.yml create mode 100644 test/fixtures/issuances.yml create mode 100644 test/models/gift_card_type_test.rb create mode 100644 test/models/issuance_test.rb diff --git a/.gitignore b/.gitignore index 52fe601..b99ebed 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,5 @@ bin/deploy/erd-service-account.json !/app/assets/builds/.keep /node_modules + +.byebug_history diff --git a/Gemfile b/Gemfile index a91f5e4..19a115d 100644 --- a/Gemfile +++ b/Gemfile @@ -66,3 +66,6 @@ group :test do end gem "activeadmin", "~> 3.2" +gem "activeadmin_addons" + +gem "aasm" diff --git a/Gemfile.lock b/Gemfile.lock index 22ca93d..f0f51a9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,8 @@ GEM remote: https://rubygems.org/ specs: + aasm (5.5.0) + concurrent-ruby (~> 1.0) actioncable (7.2.2) actionpack (= 7.2.2) activesupport (= 7.2.2) @@ -45,6 +47,7 @@ GEM erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) + active_material (2.1.2) activeadmin (3.2.5) arbre (~> 1.2, >= 1.2.1) csv @@ -55,6 +58,14 @@ GEM kaminari (>= 1.2.1) railties (>= 6.1) ransack (>= 4.0) + activeadmin_addons (1.10.1) + active_material + railties + redcarpet + require_all + sassc + sassc-rails + xdan-datetimepicker-rails (~> 2.5.1) activejob (7.2.2) activesupport (= 7.2.2) globalid (>= 0.3.6) @@ -284,9 +295,11 @@ GEM i18n rdoc (6.8.1) psych (>= 4.0.0) + redcarpet (3.6.0) regexp_parser (2.9.2) reline (0.5.11) io-console (~> 0.5) + require_all (3.0.0) responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) @@ -376,6 +389,9 @@ GEM websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) + xdan-datetimepicker-rails (2.5.4) + jquery-rails + rails (>= 3.2.16) xpath (3.2.0) nokogiri (~> 1.8) zeitwerk (2.7.1) @@ -397,7 +413,9 @@ PLATFORMS x86_64-linux-musl DEPENDENCIES + aasm activeadmin (~> 3.2) + activeadmin_addons bootsnap brakeman capybara diff --git a/app/admin/gift_card_types.rb b/app/admin/gift_card_types.rb new file mode 100644 index 0000000..68dcf3c --- /dev/null +++ b/app/admin/gift_card_types.rb @@ -0,0 +1,18 @@ +ActiveAdmin.register GiftCardType do + + # See permitted parameters documentation: + # https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters + # + # Uncomment all parameters which should be permitted for assignment + # + permit_params :label, :numbering, :contact, :prod_id, :isbn, :gl_acct, :department_number + # + # or + # + # permit_params do + # permitted = [:label, :numbering, :contact, :prod_id, :isbn, :gl_acct, :department_number] + # permitted << :other if params[:action] == 'create' && current_user.admin? + # permitted + # end + +end diff --git a/app/admin/gift_cards.rb b/app/admin/gift_cards.rb new file mode 100644 index 0000000..5de3599 --- /dev/null +++ b/app/admin/gift_cards.rb @@ -0,0 +1,19 @@ +ActiveAdmin.register GiftCard do + + # See permitted parameters documentation: + # https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters + # + # Uncomment all parameters which should be permitted for assignment + # + # permit_params :expiration_date, :registrations_available, :associated_product, :certificate_value, :gl_code, :certificate + # + # or + # + # permit_params do + # permitted = [:expiration_date, :registrations_available, :associated_product, :certificate_value, :gl_code, :certificate] + # permitted << :other if params[:action] == 'create' && current_user.admin? + # permitted + # end + + actions :all, :except => [:new] +end diff --git a/app/admin/issuances.rb b/app/admin/issuances.rb new file mode 100644 index 0000000..8cea3c7 --- /dev/null +++ b/app/admin/issuances.rb @@ -0,0 +1,59 @@ +ActiveAdmin.register Issuance do + actions :all, except: [:edit, :destroy] + + # See permitted parameters documentation: + # https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters + # + # Uncomment all parameters which should be permitted for assignment + # + permit_params :status, :gift_card_amount, :card_amount, :quantity, :begin_use_date, :end_use_date, :expiration_date, :gift_card_type_id + # + # or + # + # permit_params do + # permitted = [:status, :initiator_id, :card_amount, :quantity, :begin_use_date, :end_use_date, :expiration_date] + # permitted << :other if params[:action] == 'create' && current_user.admin? + # permitted + # end + + index do + [:initiator, :created_at, :gift_card_type, :card_amount, :quantity, :begin_use_date, :end_use_date, :expiration_date] + end + + form do |f| + f.semantic_errors + + inputs do + input :gift_card_type + input :card_amount + input :quantity + input :begin_use_date, as: :date_time_picker + input :end_use_date, as: :date_time_picker + input :expiration_date, as: :date_time_picker + + actions do + byebug + if params[:action] == "new" + submit_tag "Preview", action: :create + elsif params[:action] == "edit" && issuance.preview? + submit_tag "Issue Gift Cards", action: :create + end + end + end + end + + action_item only: :show do + if issuance.preview? + link_to 'Issue Gift Cards', issue_admin_issuance_path(issuance), method: :put, data: { confirm: 'Are you sure?' } + end + end + + member_action :issue, method: :put do + resource.issue! + redirect_to admin_issuance_path(resource), notice: "Gift cards issued!" + end + + before_create do |issuance| + issuance.initiator = Person.first # TODO current_user + end +end diff --git a/app/assets/javascripts/active_admin.js b/app/assets/javascripts/active_admin.js index d2b66c5..b656ec2 100644 --- a/app/assets/javascripts/active_admin.js +++ b/app/assets/javascripts/active_admin.js @@ -1 +1,2 @@ //= require active_admin/base +//= require activeadmin_addons/all diff --git a/app/assets/stylesheets/active_admin.scss b/app/assets/stylesheets/active_admin.scss index 41c27b3..8ef502e 100644 --- a/app/assets/stylesheets/active_admin.scss +++ b/app/assets/stylesheets/active_admin.scss @@ -1,3 +1,4 @@ +@import 'activeadmin_addons/all'; // Sass variable overrides must be declared before loading up Active Admin's styles. // // To view the variables that Active Admin provides, take a look at diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0d95db2..1235efc 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,4 +1,13 @@ class ApplicationController < ActionController::Base # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. allow_browser versions: :modern + + def current_admin_user + # TODO replace with okta user + Person.first + end + + def destroy_admin_user_session_path + "TODO" + end end diff --git a/app/models/api_key.rb b/app/models/api_key.rb index f44edba..8e4096e 100644 --- a/app/models/api_key.rb +++ b/app/models/api_key.rb @@ -1,2 +1,9 @@ class ApiKey < ApplicationRecord + def self.ransackable_attributes(auth_object = nil) + [] + end + + def self.ransackable_associations(auth_object = nil) + [] + end end diff --git a/app/models/authentication.rb b/app/models/authentication.rb index a3c8f98..4b77c46 100644 --- a/app/models/authentication.rb +++ b/app/models/authentication.rb @@ -1,2 +1,9 @@ class Authentication < ApplicationRecord + def self.ransackable_attributes(auth_object = nil) + [] + end + + def self.ransackable_associations(auth_object = nil) + [] + end end diff --git a/app/models/gift_card.rb b/app/models/gift_card.rb index 1b11df6..427dbac 100644 --- a/app/models/gift_card.rb +++ b/app/models/gift_card.rb @@ -1,2 +1,12 @@ class GiftCard < ApplicationRecord + belongs_to :issuance + + def self.ransackable_attributes(auth_object = nil) + [ :certificate, :expiration_date, :registrations_available, :associated_product, + :certificate_value, :gl_code, :created_at, :updated_at, :issuance_id ] + end + + def self.ransackable_associations(auth_object = nil) + %w(issuance) + end end diff --git a/app/models/gift_card_type.rb b/app/models/gift_card_type.rb new file mode 100644 index 0000000..80e4092 --- /dev/null +++ b/app/models/gift_card_type.rb @@ -0,0 +1,15 @@ +class GiftCardType < ApplicationRecord + validates :numbering, presence: true, format: { with: /\d*x+\d*/ } + + def to_s + label + end + + def self.ransackable_attributes(auth_object = nil) + [:label, :numbering, :contact, :prod_id, :isbn, :gl_acct, :department_number] + end + + def self.ransackable_associations(auth_object = nil) + [] + end +end diff --git a/app/models/issuance.rb b/app/models/issuance.rb new file mode 100644 index 0000000..54c17c4 --- /dev/null +++ b/app/models/issuance.rb @@ -0,0 +1,122 @@ +=begin + create_table "gift_cards", force: :cascade do |t| + t.integer "certificate" + t.datetime "expiration_date" + t.integer "registrations_available" + t.string "associated_product" + t.decimal "certificate_value" + t.string "gl_code" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "issuances", force: :cascade do |t| + t.string "status" + t.integer "initiator_id" + t.decimal "card_amount" + t.integer "quantity" + t.datetime "begin_use_date" + t.datetime "end_use_date" + t.datetime "expiration_date" + t.integer "gift_card_type_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "allocated_certificates" + t.string "numbering" + end + +=end + +class Issuance < ApplicationRecord + include AASM + + belongs_to :initiator, class_name: "Person" + belongs_to :gift_card_type + has_many :gift_cards + + validates :card_amount, numericality: true + validates :quantity, numericality: { only_integer: true } + + aasm column: :status do + state :configure, initial: true + state :preview + state :issued + + event :preview do + transitions from: :configure, to: :preview, after: :allocate_certificates + end + + event :issue do + transitions from: :preview, to: :issued, after: :create_gift_cards + transitions from: :issued, to: :issued + end + end + + before_save do + unless issued? + # keep a copy of numbering for posterity if still previewing + self.numbering = gift_card_type.numbering + end + end + + after_create do + preview! + end + + def to_s + if preview? + "Gift Card Issuance (Preview)" + elsif issued? + "Gift Card Issuance by #{initiator.full_name} at #{created_at}" + end + end + + def create_gift_cards + byebug + allocated_certificates.split(", ").each do |certificate| + gift_card = gift_cards.where(certificate: certificate).first_or_initialize + gift_card.expiration_date = expiration_date + end + end + + def numbering_regex + # add brackets around the x's so that it can be extracted, and use \d instead of x + @numbering_regex ||= numbering.gsub(/x+/) { |xs| "(#{xs.gsub("x", "\\d")})" } + end + + def allocate_certificates + largest_existing_certificate =~ numbering_regex + next_number = $1.to_i + + allocated_certificates = [] + quantity.times do + certificate = numbering.gsub(/x+/) { |xs| next_number.to_s.rjust(xs.length, "0") } + allocated_certificates << certificate + next_number += 1 + end + + self.update(allocated_certificates: allocated_certificates.join(", ")) + end + + def largest_existing_certificate + + # look for all numbers that match numbering + existing_matching_certificates = GiftCard.all.where("certificate::text LIKE ?", numbering_regex).pluck(:certificate) + + # pulling all allocated certificate ids instead of a rergex isn't ideal, but there shouldn't be many, if any, times there + # are previewed gift cards issuances while another one is being previewed + existing_matching_certificates += Issuance.preview.pluck(:allocated_certificates).find_all do |certificate| + certificate =~ numbering_regex + end + + existing_matching_certificates.collect(&:to_i).max + end + + def self.ransackable_attributes(auth_object = nil) + %w(status created_at updated_at initiator_id gift_card_type_id card_amount quantity allocated_certificates numbering gift_cards_id) + end + + def self.ransackable_associations(auth_object = nil) + %w(initiator gift_card_type gift_cards) + end +end diff --git a/app/models/person.rb b/app/models/person.rb index a8b1b85..fa0c41d 100644 --- a/app/models/person.rb +++ b/app/models/person.rb @@ -1,2 +1,13 @@ class Person < ApplicationRecord + def self.ransackable_attributes(auth_object = nil) + [:first_name, :last_name] + end + + def self.ransackable_associations(auth_object = nil) + [] + end + + def full_name + "#{first_name} #{last_name}" + end end diff --git a/config/initializers/active_admin.rb b/config/initializers/active_admin.rb index f8f22c8..048c77e 100644 --- a/config/initializers/active_admin.rb +++ b/config/initializers/active_admin.rb @@ -108,7 +108,7 @@ # # This setting changes the method which Active Admin calls # (within the application controller) to return the currently logged in user. - # config.current_user_method = :current_admin_user + config.current_user_method = :current_admin_user # == Logging Out # @@ -120,7 +120,7 @@ # will call the method to return the path. # # Default: - config.logout_link_path = :destroy_admin_user_session_path + # config.logout_link_path = :destroy_admin_user_session_path # This setting changes the http method used when rendering the # link. For example :get, :delete, :put, etc.. diff --git a/config/initializers/activeadmin_addons.rb b/config/initializers/activeadmin_addons.rb new file mode 100644 index 0000000..ac9ea44 --- /dev/null +++ b/config/initializers/activeadmin_addons.rb @@ -0,0 +1,12 @@ +ActiveadminAddons.setup do |config| + # Change to "default" if you want to use ActiveAdmin's default select control. + # config.default_select = "select2" + + # Set default options for DateTimePickerInput. The options you can provide are the same as in + # xdan's datetimepicker library (https://github.com/xdan/datetimepicker/tree/2.5.4). Yo need to + # pass a ruby hash, avoid camelCase keys. For example: use min_date instead of minDate key. + # config.datetime_picker_default_options = {} + + # Set DateTimePickerInput input format. This if for backend (Ruby) + # config.datetime_picker_input_format = "%Y-%m-%d %H:%M" +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 6c349ae..c166c64 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -28,4 +28,8 @@ # enabled: "ON" en: - hello: "Hello world" + flash: + admin: + issuances: + create: + notice: "Please review the allocated gift card ids and click \"Issue Gift Cards\" on the right to create the gift cards. Please be patient as creating the gift cards can take a moment." diff --git a/db/migrate/20241126092131_create_gift_cards.rb b/db/migrate/20241126092131_create_gift_cards.rb index 273ade8..3590a4a 100644 --- a/db/migrate/20241126092131_create_gift_cards.rb +++ b/db/migrate/20241126092131_create_gift_cards.rb @@ -1,6 +1,7 @@ class CreateGiftCards < ActiveRecord::Migration[7.2] def change create_table :gift_cards do |t| + t.integer :certificate, limit: 8 t.datetime :expiration_date t.integer :registrations_available t.string :associated_product diff --git a/db/migrate/20241126092230_add_certificate_id_to_gift_cards.rb b/db/migrate/20241126092230_add_certificate_id_to_gift_cards.rb deleted file mode 100644 index 6f1f706..0000000 --- a/db/migrate/20241126092230_add_certificate_id_to_gift_cards.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddCertificateIdToGiftCards < ActiveRecord::Migration[7.2] - def change - add_column :gift_cards, :certificate_id, :string - end -end diff --git a/db/migrate/20241126110902_create_gift_card_types.rb b/db/migrate/20241126110902_create_gift_card_types.rb new file mode 100644 index 0000000..62adde0 --- /dev/null +++ b/db/migrate/20241126110902_create_gift_card_types.rb @@ -0,0 +1,15 @@ +class CreateGiftCardTypes < ActiveRecord::Migration[7.2] + def change + create_table :gift_card_types do |t| + t.string :label + t.string :numbering + t.string :contact + t.string :prod_id + t.string :isbn + t.string :gl_acct + t.string :department_number + + t.timestamps + end + end +end diff --git a/db/migrate/20241126111253_create_issuances.rb b/db/migrate/20241126111253_create_issuances.rb new file mode 100644 index 0000000..2c15665 --- /dev/null +++ b/db/migrate/20241126111253_create_issuances.rb @@ -0,0 +1,16 @@ +class CreateIssuances < ActiveRecord::Migration[7.2] + def change + create_table :issuances do |t| + t.string :status + t.integer :initiator_id + t.decimal :card_amount + t.integer :quantity + t.datetime :begin_use_date + t.datetime :end_use_date + t.datetime :expiration_date + t.integer :gift_card_type_id + + t.timestamps + end + end +end diff --git a/db/migrate/20241202051016_add_allocated_certificate_ids_to_issuances.rb b/db/migrate/20241202051016_add_allocated_certificate_ids_to_issuances.rb new file mode 100644 index 0000000..4c7eb64 --- /dev/null +++ b/db/migrate/20241202051016_add_allocated_certificate_ids_to_issuances.rb @@ -0,0 +1,5 @@ +class AddAllocatedCertificateIdsToIssuances < ActiveRecord::Migration[7.2] + def change + add_column :issuances, :allocated_certificates, :text + end +end diff --git a/db/migrate/20241202051127_add_numbering_to_issuances.rb b/db/migrate/20241202051127_add_numbering_to_issuances.rb new file mode 100644 index 0000000..c612c9c --- /dev/null +++ b/db/migrate/20241202051127_add_numbering_to_issuances.rb @@ -0,0 +1,5 @@ +class AddNumberingToIssuances < ActiveRecord::Migration[7.2] + def change + add_column :issuances, :numbering, :string + end +end diff --git a/db/migrate/20241202065714_add_issuance_id_to_gift_cards.rb b/db/migrate/20241202065714_add_issuance_id_to_gift_cards.rb new file mode 100644 index 0000000..a52f85e --- /dev/null +++ b/db/migrate/20241202065714_add_issuance_id_to_gift_cards.rb @@ -0,0 +1,5 @@ +class AddIssuanceIdToGiftCards < ActiveRecord::Migration[7.2] + def change + add_column :gift_cards, :issuance_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index ee52688..11ce8bb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_11_26_094659) do +ActiveRecord::Schema[7.2].define(version: 2024_12_02_065714) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -45,7 +45,20 @@ t.datetime "updated_at", null: false end + create_table "gift_card_types", force: :cascade do |t| + t.string "label" + t.string "numbering" + t.string "contact" + t.string "prod_id" + t.string "isbn" + t.string "gl_acct" + t.string "department_number" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "gift_cards", force: :cascade do |t| + t.bigint "certificate" t.datetime "expiration_date" t.integer "registrations_available" t.string "associated_product" @@ -53,7 +66,22 @@ t.string "gl_code" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.string "certificate_id" + t.integer "issuance_id" + end + + create_table "issuances", force: :cascade do |t| + t.string "status" + t.integer "initiator_id" + t.decimal "card_amount" + t.integer "quantity" + t.datetime "begin_use_date" + t.datetime "end_use_date" + t.datetime "expiration_date" + t.integer "gift_card_type_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "numbering" + t.text "allocated_certificates" end create_table "people", force: :cascade do |t| diff --git a/test/fixtures/gift_card_types.yml b/test/fixtures/gift_card_types.yml new file mode 100644 index 0000000..8b8b9ec --- /dev/null +++ b/test/fixtures/gift_card_types.yml @@ -0,0 +1,19 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + label: MyString + numbering: MyString + contact: MyString + prod_id: MyString + isbn: MyString + gl_acct: MyString + department_number: MyString + +two: + label: MyString + numbering: MyString + contact: MyString + prod_id: MyString + isbn: MyString + gl_acct: MyString + department_number: MyString diff --git a/test/fixtures/issuances.yml b/test/fixtures/issuances.yml new file mode 100644 index 0000000..a7c3356 --- /dev/null +++ b/test/fixtures/issuances.yml @@ -0,0 +1,19 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + state: MyString + initiator_id: 1 + card_amount: 9.99 + quantity: 1 + begin_use_date: 2024-11-26 06:12:53 + end_use_date: 2024-11-26 06:12:53 + expiration_date: 2024-11-26 06:12:53 + +two: + state: MyString + initiator_id: 1 + card_amount: 9.99 + quantity: 1 + begin_use_date: 2024-11-26 06:12:53 + end_use_date: 2024-11-26 06:12:53 + expiration_date: 2024-11-26 06:12:53 diff --git a/test/models/gift_card_type_test.rb b/test/models/gift_card_type_test.rb new file mode 100644 index 0000000..9d0c7cf --- /dev/null +++ b/test/models/gift_card_type_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class GiftCardTypeTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/issuance_test.rb b/test/models/issuance_test.rb new file mode 100644 index 0000000..261f884 --- /dev/null +++ b/test/models/issuance_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class IssuanceTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From 4c904ca8a6f725b15cf30c7ee5c6d87c6d8d2964 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 2 Dec 2024 05:27:59 -0500 Subject: [PATCH 04/73] WIP generating gift cards able to generate now and proper auth on the admin for issuances - no editing issuance after gift cards are created --- .gitignore | 2 + app/admin/issuances.rb | 49 ++++++++++++++----- app/helpers/application_helper.rb | 3 ++ app/models/custom_authorization.rb | 13 +++++ app/models/issuance.rb | 38 ++++++++------ config/initializers/active_admin.rb | 2 +- db/migrate/20241126111253_create_issuances.rb | 6 ++- ..._allocated_certificate_ids_to_issuances.rb | 5 -- ...241202051127_add_numbering_to_issuances.rb | 5 -- db/schema.rb | 8 +-- 10 files changed, 89 insertions(+), 42 deletions(-) create mode 100644 app/models/custom_authorization.rb delete mode 100644 db/migrate/20241202051016_add_allocated_certificate_ids_to_issuances.rb delete mode 100644 db/migrate/20241202051127_add_numbering_to_issuances.rb diff --git a/.gitignore b/.gitignore index b99ebed..454e54c 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,5 @@ bin/deploy/erd-service-account.json /node_modules .byebug_history +*.zip +*.csv diff --git a/app/admin/issuances.rb b/app/admin/issuances.rb index 8cea3c7..70bca39 100644 --- a/app/admin/issuances.rb +++ b/app/admin/issuances.rb @@ -1,5 +1,5 @@ ActiveAdmin.register Issuance do - actions :all, except: [:edit, :destroy] + #actions :all, except: [:edit, :destroy] # See permitted parameters documentation: # https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters @@ -17,7 +17,22 @@ # end index do - [:initiator, :created_at, :gift_card_type, :card_amount, :quantity, :begin_use_date, :end_use_date, :expiration_date] + id_column + column :created_at + column :gift_card_type + column :creator + column :issuer + number_column :card_amount, as: :currency, unit: "$" + column :quantity + column :min_certificate do |issuance| + issuance.allocated_certificates.split(", ").first + end + column :max_certificate do |issuance| + issuance.allocated_certificates.split(", ").last + end + column :begin_use_date + column :end_use_date + column :expiration_date end form do |f| @@ -32,28 +47,40 @@ input :expiration_date, as: :date_time_picker actions do - byebug - if params[:action] == "new" - submit_tag "Preview", action: :create + unless issuance.issued? + f.action :submit, as: :button, label: "Save and Preview Issuance" + end + #f.action :cancel, as: :link, label: 'Cancel', class: 'cancel-link' + cancel_link +=begin + if %w(new create).include?(params[:action]) + submit_tag(:create, "Preview Issuance") elsif params[:action] == "edit" && issuance.preview? - submit_tag "Issue Gift Cards", action: :create + submit_tag(:update, "Preview Issuance") end + cancel_link +=end end end end - action_item only: :show do - if issuance.preview? - link_to 'Issue Gift Cards', issue_admin_issuance_path(issuance), method: :put, data: { confirm: 'Are you sure?' } - end +=begin + action_item :destroy, only: :show, if: -> { resource.preview? } do + link_to 'Delete Issuance', issue_admin_issuance_path(issuance), method: :destroy, data: { confirm: 'Are you sure?' } + end +=end + + action_item :issue, only: :show, if: -> { resource.preview? } do + link_to 'Issue Gift Cards', issue_admin_issuance_path(issuance), method: :put, data: { confirm: 'Are you sure?' } end member_action :issue, method: :put do resource.issue! + resource.update(issuer_id: current_admin_user.id, issued_at: Time.now) redirect_to admin_issuance_path(resource), notice: "Gift cards issued!" end before_create do |issuance| - issuance.initiator = Person.first # TODO current_user + issuance.creator = current_admin_user end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de6be79..ce706a1 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,2 +1,5 @@ module ApplicationHelper + def destroy_admin_user_session_path + "HERE" + end end diff --git a/app/models/custom_authorization.rb b/app/models/custom_authorization.rb new file mode 100644 index 0000000..59dfde9 --- /dev/null +++ b/app/models/custom_authorization.rb @@ -0,0 +1,13 @@ +class CustomAuthorization < ActiveAdmin::AuthorizationAdapter + def authorized?(action, subject = nil) + return true if action == :read + + case subject + when Issuance + !subject.issued? # can't do anything on issuances that already happened + else + true + end + end +end + diff --git a/app/models/issuance.rb b/app/models/issuance.rb index 54c17c4..f5074da 100644 --- a/app/models/issuance.rb +++ b/app/models/issuance.rb @@ -30,9 +30,10 @@ class Issuance < ApplicationRecord include AASM - belongs_to :initiator, class_name: "Person" + belongs_to :creator, class_name: "Person" + belongs_to :issuer, class_name: "Person", optional: true belongs_to :gift_card_type - has_many :gift_cards + has_many :gift_cards validates :card_amount, numericality: true validates :quantity, numericality: { only_integer: true } @@ -43,7 +44,7 @@ class Issuance < ApplicationRecord state :issued event :preview do - transitions from: :configure, to: :preview, after: :allocate_certificates + transitions from: :configure, to: :preview end event :issue do @@ -53,9 +54,10 @@ class Issuance < ApplicationRecord end before_save do - unless issued? + unless issued? # keep a copy of numbering for posterity if still previewing self.numbering = gift_card_type.numbering + set_allocated_certificates end end @@ -67,25 +69,29 @@ def to_s if preview? "Gift Card Issuance (Preview)" elsif issued? - "Gift Card Issuance by #{initiator.full_name} at #{created_at}" + "Gift Card Issuance by #{issuer.full_name} at #{created_at}" end end def create_gift_cards byebug allocated_certificates.split(", ").each do |certificate| - gift_card = gift_cards.where(certificate: certificate).first_or_initialize + gift_card = gift_cards.where(certificate: certificate).first_or_initialize gift_card.expiration_date = expiration_date end end + def numbering_regex_str + numbering.gsub(/x+/) { |xs| "(#{xs.gsub("x", "\\d")})" } + end + def numbering_regex # add brackets around the x's so that it can be extracted, and use \d instead of x - @numbering_regex ||= numbering.gsub(/x+/) { |xs| "(#{xs.gsub("x", "\\d")})" } + @numbering_regex ||= /#{numbering_regex_str}/ end - def allocate_certificates - largest_existing_certificate =~ numbering_regex + def set_allocated_certificates + largest_existing_certificate.to_s =~ numbering_regex next_number = $1.to_i allocated_certificates = [] @@ -95,28 +101,28 @@ def allocate_certificates next_number += 1 end - self.update(allocated_certificates: allocated_certificates.join(", ")) + self.allocated_certificates = allocated_certificates.join(", ") end def largest_existing_certificate # look for all numbers that match numbering - existing_matching_certificates = GiftCard.all.where("certificate::text LIKE ?", numbering_regex).pluck(:certificate) + existing_matching_certificates = GiftCard.all.where("certificate::text LIKE ?", numbering_regex_str).pluck(:certificate) # pulling all allocated certificate ids instead of a rergex isn't ideal, but there shouldn't be many, if any, times there # are previewed gift cards issuances while another one is being previewed - existing_matching_certificates += Issuance.preview.pluck(:allocated_certificates).find_all do |certificate| - certificate =~ numbering_regex - end + existing_matching_certificates += Issuance.preview.pluck(:allocated_certificates).collect do |allocated_certificates| + allocated_certificates.split(", ").find_all { |certificate| certificate =~ numbering_regex } + end.flatten existing_matching_certificates.collect(&:to_i).max end def self.ransackable_attributes(auth_object = nil) - %w(status created_at updated_at initiator_id gift_card_type_id card_amount quantity allocated_certificates numbering gift_cards_id) + %w(status created_at updated_at creator_id issuer_id gift_card_type_id card_amount quantity allocated_certificates numbering gift_cards_id) end def self.ransackable_associations(auth_object = nil) - %w(initiator gift_card_type gift_cards) + %w(creator issuer gift_card_type gift_cards) end end diff --git a/config/initializers/active_admin.rb b/config/initializers/active_admin.rb index 048c77e..85c4cc7 100644 --- a/config/initializers/active_admin.rb +++ b/config/initializers/active_admin.rb @@ -79,7 +79,7 @@ # method in a before filter of all controller actions to # ensure that there is a user with proper rights. You can use # CanCanAdapter or make your own. Please refer to documentation. - # config.authorization_adapter = ActiveAdmin::CanCanAdapter + config.authorization_adapter = "CustomAuthorization" # In case you prefer Pundit over other solutions you can here pass # the name of default policy class. This policy will be used in every diff --git a/db/migrate/20241126111253_create_issuances.rb b/db/migrate/20241126111253_create_issuances.rb index 2c15665..476973a 100644 --- a/db/migrate/20241126111253_create_issuances.rb +++ b/db/migrate/20241126111253_create_issuances.rb @@ -2,13 +2,17 @@ class CreateIssuances < ActiveRecord::Migration[7.2] def change create_table :issuances do |t| t.string :status - t.integer :initiator_id + t.integer :creator_id + t.integer :issuer_id t.decimal :card_amount t.integer :quantity t.datetime :begin_use_date t.datetime :end_use_date t.datetime :expiration_date t.integer :gift_card_type_id + t.text :allocated_certificates + t.string :numbering + t.datetime :issued_at t.timestamps end diff --git a/db/migrate/20241202051016_add_allocated_certificate_ids_to_issuances.rb b/db/migrate/20241202051016_add_allocated_certificate_ids_to_issuances.rb deleted file mode 100644 index 4c7eb64..0000000 --- a/db/migrate/20241202051016_add_allocated_certificate_ids_to_issuances.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddAllocatedCertificateIdsToIssuances < ActiveRecord::Migration[7.2] - def change - add_column :issuances, :allocated_certificates, :text - end -end diff --git a/db/migrate/20241202051127_add_numbering_to_issuances.rb b/db/migrate/20241202051127_add_numbering_to_issuances.rb deleted file mode 100644 index c612c9c..0000000 --- a/db/migrate/20241202051127_add_numbering_to_issuances.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddNumberingToIssuances < ActiveRecord::Migration[7.2] - def change - add_column :issuances, :numbering, :string - end -end diff --git a/db/schema.rb b/db/schema.rb index 11ce8bb..e34f938 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -71,17 +71,19 @@ create_table "issuances", force: :cascade do |t| t.string "status" - t.integer "initiator_id" + t.integer "creator_id" + t.integer "issuer_id" t.decimal "card_amount" t.integer "quantity" t.datetime "begin_use_date" t.datetime "end_use_date" t.datetime "expiration_date" t.integer "gift_card_type_id" + t.text "allocated_certificates" + t.string "numbering" + t.datetime "issued_at" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.string "numbering" - t.text "allocated_certificates" end create_table "people", force: :cascade do |t| From 497781f082f5d78ba6feefa9a1ba6299979c41d7 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 2 Dec 2024 06:23:54 -0500 Subject: [PATCH 05/73] properly check existing ids with regex various other fixes and progress towards issuing and displaying gift cards --- app/admin/issuances.rb | 1 + app/assets/stylesheets/active_admin.scss | 4 ++++ app/models/gift_card.rb | 5 ++--- app/models/issuance.rb | 17 +++++++++-------- db/migrate/20241126092131_create_gift_cards.rb | 3 ++- ...41202065714_add_issuance_id_to_gift_cards.rb | 5 ----- db/schema.rb | 6 +++--- 7 files changed, 21 insertions(+), 20 deletions(-) delete mode 100644 db/migrate/20241202065714_add_issuance_id_to_gift_cards.rb diff --git a/app/admin/issuances.rb b/app/admin/issuances.rb index 70bca39..07c1a2b 100644 --- a/app/admin/issuances.rb +++ b/app/admin/issuances.rb @@ -18,6 +18,7 @@ index do id_column + tag_column :status column :created_at column :gift_card_type column :creator diff --git a/app/assets/stylesheets/active_admin.scss b/app/assets/stylesheets/active_admin.scss index 8ef502e..6af7e57 100644 --- a/app/assets/stylesheets/active_admin.scss +++ b/app/assets/stylesheets/active_admin.scss @@ -16,3 +16,7 @@ // For example, to change the default status-tag color: // // .status_tag { background: #6090DB; } + +.status_tag.issued { + background: black; +} diff --git a/app/models/gift_card.rb b/app/models/gift_card.rb index 427dbac..feb81b9 100644 --- a/app/models/gift_card.rb +++ b/app/models/gift_card.rb @@ -2,11 +2,10 @@ class GiftCard < ApplicationRecord belongs_to :issuance def self.ransackable_attributes(auth_object = nil) - [ :certificate, :expiration_date, :registrations_available, :associated_product, - :certificate_value, :gl_code, :created_at, :updated_at, :issuance_id ] + %w(certificate expiration_date registrations_available associated_product certificate_value gl_code created_at updated_at issuance_id) end def self.ransackable_associations(auth_object = nil) - %w(issuance) + %w(issuance issuance_id) end end diff --git a/app/models/issuance.rb b/app/models/issuance.rb index f5074da..8b75394 100644 --- a/app/models/issuance.rb +++ b/app/models/issuance.rb @@ -69,12 +69,11 @@ def to_s if preview? "Gift Card Issuance (Preview)" elsif issued? - "Gift Card Issuance by #{issuer.full_name} at #{created_at}" + "Issuance by #{issuer.full_name} #{created_at}" end end def create_gift_cards - byebug allocated_certificates.split(", ").each do |certificate| gift_card = gift_cards.where(certificate: certificate).first_or_initialize gift_card.expiration_date = expiration_date @@ -91,8 +90,7 @@ def numbering_regex end def set_allocated_certificates - largest_existing_certificate.to_s =~ numbering_regex - next_number = $1.to_i + next_number = largest_existing_number_in_certificate + 1 allocated_certificates = [] quantity.times do @@ -104,10 +102,10 @@ def set_allocated_certificates self.allocated_certificates = allocated_certificates.join(", ") end - def largest_existing_certificate - + # largest number in certificates represented in x's in format, for all certificates matching format + def largest_existing_number_in_certificate # look for all numbers that match numbering - existing_matching_certificates = GiftCard.all.where("certificate::text LIKE ?", numbering_regex_str).pluck(:certificate) + existing_matching_certificates = GiftCard.all.where("certificate ~* ?", numbering_regex_str).pluck(:certificate) # pulling all allocated certificate ids instead of a rergex isn't ideal, but there shouldn't be many, if any, times there # are previewed gift cards issuances while another one is being previewed @@ -115,7 +113,10 @@ def largest_existing_certificate allocated_certificates.split(", ").find_all { |certificate| certificate =~ numbering_regex } end.flatten - existing_matching_certificates.collect(&:to_i).max + existing_matching_certificates.collect{ |certificate| + certificate =~ numbering_regex + $1.to_i + }.max || 1 end def self.ransackable_attributes(auth_object = nil) diff --git a/db/migrate/20241126092131_create_gift_cards.rb b/db/migrate/20241126092131_create_gift_cards.rb index 3590a4a..8128582 100644 --- a/db/migrate/20241126092131_create_gift_cards.rb +++ b/db/migrate/20241126092131_create_gift_cards.rb @@ -1,7 +1,8 @@ class CreateGiftCards < ActiveRecord::Migration[7.2] def change create_table :gift_cards do |t| - t.integer :certificate, limit: 8 + t.integer :issuance_id + t.string :certificate t.datetime :expiration_date t.integer :registrations_available t.string :associated_product diff --git a/db/migrate/20241202065714_add_issuance_id_to_gift_cards.rb b/db/migrate/20241202065714_add_issuance_id_to_gift_cards.rb deleted file mode 100644 index a52f85e..0000000 --- a/db/migrate/20241202065714_add_issuance_id_to_gift_cards.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddIssuanceIdToGiftCards < ActiveRecord::Migration[7.2] - def change - add_column :gift_cards, :issuance_id, :integer - end -end diff --git a/db/schema.rb b/db/schema.rb index e34f938..7f25982 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_12_02_065714) do +ActiveRecord::Schema[7.2].define(version: 2024_11_26_111253) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -58,7 +58,8 @@ end create_table "gift_cards", force: :cascade do |t| - t.bigint "certificate" + t.integer "issuance_id" + t.string "certificate" t.datetime "expiration_date" t.integer "registrations_available" t.string "associated_product" @@ -66,7 +67,6 @@ t.string "gl_code" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.integer "issuance_id" end create_table "issuances", force: :cascade do |t| From 4349b2bb25c9c075af8d5865435af83cf379ea1b Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Tue, 3 Dec 2024 15:28:16 -0500 Subject: [PATCH 06/73] more tweaks and import script store gift card type on each gift card --- Gemfile | 2 + Gemfile.lock | 3 + app/models/gift_card.rb | 5 +- app/models/gift_card_type.rb | 2 + app/models/issuance.rb | 13 +- .../20241126092131_create_gift_cards.rb | 1 + db/schema.rb | 1 + import.rb | 136 ++++++++++++++++++ lib/has_numbering.rb | 10 ++ 9 files changed, 162 insertions(+), 11 deletions(-) create mode 100644 import.rb create mode 100644 lib/has_numbering.rb diff --git a/Gemfile b/Gemfile index 19a115d..45d6ce8 100644 --- a/Gemfile +++ b/Gemfile @@ -69,3 +69,5 @@ gem "activeadmin", "~> 3.2" gem "activeadmin_addons" gem "aasm" + +gem "activerecord-import" diff --git a/Gemfile.lock b/Gemfile.lock index f0f51a9..5af6688 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -75,6 +75,8 @@ GEM activemodel (= 7.2.2) activesupport (= 7.2.2) timeout (>= 0.4.0) + activerecord-import (1.8.1) + activerecord (>= 4.2) activestorage (7.2.2) actionpack (= 7.2.2) activejob (= 7.2.2) @@ -416,6 +418,7 @@ DEPENDENCIES aasm activeadmin (~> 3.2) activeadmin_addons + activerecord-import bootsnap brakeman capybara diff --git a/app/models/gift_card.rb b/app/models/gift_card.rb index feb81b9..1f3336d 100644 --- a/app/models/gift_card.rb +++ b/app/models/gift_card.rb @@ -1,11 +1,12 @@ class GiftCard < ApplicationRecord belongs_to :issuance + belongs_to :gift_card_type def self.ransackable_attributes(auth_object = nil) - %w(certificate expiration_date registrations_available associated_product certificate_value gl_code created_at updated_at issuance_id) + %w(certificate expiration_date registrations_available associated_product certificate_value gl_code created_at updated_at issuance_id gift_card_type_id) end def self.ransackable_associations(auth_object = nil) - %w(issuance issuance_id) + %w(issuance issuance_id gift_card_type gift_card_type_id) end end diff --git a/app/models/gift_card_type.rb b/app/models/gift_card_type.rb index 80e4092..797c103 100644 --- a/app/models/gift_card_type.rb +++ b/app/models/gift_card_type.rb @@ -1,4 +1,6 @@ class GiftCardType < ApplicationRecord + include HasNumbering + validates :numbering, presence: true, format: { with: /\d*x+\d*/ } def to_s diff --git a/app/models/issuance.rb b/app/models/issuance.rb index 8b75394..a37255e 100644 --- a/app/models/issuance.rb +++ b/app/models/issuance.rb @@ -29,6 +29,7 @@ class Issuance < ApplicationRecord include AASM + include HasNumbering belongs_to :creator, class_name: "Person" belongs_to :issuer, class_name: "Person", optional: true @@ -77,18 +78,12 @@ def create_gift_cards allocated_certificates.split(", ").each do |certificate| gift_card = gift_cards.where(certificate: certificate).first_or_initialize gift_card.expiration_date = expiration_date + gift_card.gift_card_type = gift_card_type + gift_card.issuance = self + gift_card.save! end end - def numbering_regex_str - numbering.gsub(/x+/) { |xs| "(#{xs.gsub("x", "\\d")})" } - end - - def numbering_regex - # add brackets around the x's so that it can be extracted, and use \d instead of x - @numbering_regex ||= /#{numbering_regex_str}/ - end - def set_allocated_certificates next_number = largest_existing_number_in_certificate + 1 diff --git a/db/migrate/20241126092131_create_gift_cards.rb b/db/migrate/20241126092131_create_gift_cards.rb index 8128582..7b3d478 100644 --- a/db/migrate/20241126092131_create_gift_cards.rb +++ b/db/migrate/20241126092131_create_gift_cards.rb @@ -2,6 +2,7 @@ class CreateGiftCards < ActiveRecord::Migration[7.2] def change create_table :gift_cards do |t| t.integer :issuance_id + t.integer :gift_card_type_id t.string :certificate t.datetime :expiration_date t.integer :registrations_available diff --git a/db/schema.rb b/db/schema.rb index 7f25982..d858b07 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -59,6 +59,7 @@ create_table "gift_cards", force: :cascade do |t| t.integer "issuance_id" + t.integer "gift_card_type_id" t.string "certificate" t.datetime "expiration_date" t.integer "registrations_available" diff --git a/import.rb b/import.rb new file mode 100644 index 0000000..4facf94 --- /dev/null +++ b/import.rb @@ -0,0 +1,136 @@ +#=> GiftCardType(id: integer, label: string, numbering: string, contact: string, prod_id: string, isbn: string, gl_acct: string, department_number: string, created_at: datetime, updated_at: datetime) + +GiftCardType.delete_all + +t = GiftCardType.where(label: "Regular Rate (Green) Paid Gift Card").first_or_initialize +t.numbering = "3500xxxxx0" +t.prod_id = "CER21842" +t.isbn = "978-1602005761" +t.save! + +t = GiftCardType.where(label: "Half Price (Blue) Paid Gift Card").first_or_initialize +t.numbering = "1750xxxxx0" +t.prod_id = "CER21841" +t.isbn = "978-1602006676" +t.save! + +t = GiftCardType.where(label: "Marketing Department Card").first_or_initialize +t.numbering = "4240xxxxx0" +t.gl_acct = "63301" +t.department_number = "4240-15999" +t.contact = "Taylor" +t.save! + +t = GiftCardType.where(label: "Donor Dept. Department Card").first_or_initialize +t.numbering = "3210xxxxx0" +t.gl_acct = "63301" +t.department_number = "3210-38100" +t.contact = "Quinton/Gene" +t.save! + +t = GiftCardType.where(label: "Partners Department Card").first_or_initialize +t.numbering = "3215xxxxx0" +t.gl_acct = "63301" +t.department_number = "3215-38116" +t.contact = "Quinton/Gene" +t.save! + +t = GiftCardType.where(label: "President Department Card").first_or_initialize +t.numbering = "1100xxxxx0" +t.gl_acct = "63301" +t.department_number = "1100-50050" +t.contact = "Brian Borger" +t.save! + +t = GiftCardType.where(label: "Field Core Department Card").first_or_initialize +t.numbering = "4710xxxxx0" +t.gl_acct = "63301" +t.department_number = "4710-15999" +t.contact = "Brandon" +t.save! + +t = GiftCardType.where(label: "Innovative Events Department Card").first_or_initialize +t.numbering = "4228xxxxx0" +t.gl_acct = "63301" +t.department_number = "4228-14400" +t.contact = "Tim Bell/Tanya" +t.save! + +t = GiftCardType.where(label: "Speaker Dept. Card").first_or_initialize +t.numbering = "1470xxxxx0" +t.gl_acct = "63301" +t.department_number = "1470-34998" +t.contact = "Jennifer Abbott" +t.save! + +t = GiftCardType.where(label: "Corporate Department Card").first_or_initialize +t.numbering = "4241xxxx0" +t.gl_acct = "63301" +t.department_number = "4241-45100" +t.contact = "Glen Flagerstrom" +t.save! + +t = GiftCardType.where(label: "Station Relations Card").first_or_initialize +t.numbering = "1420xxxxx0" +t.gl_acct = "63301" +t.department_number = "1420-34001" +t.contact = "Maddison Villafane" +t.save! + +t = GiftCardType.where(label: "Unknown").first_or_initialize +t.numbering = "xxxxx" +t.contact = "This is for initial import cards with a certificate id that doesn't match any gift card numbering" +t.save! + +#=> GiftCardType(id: integer, label: string, numbering: string, contact: string, prod_id: string, isbn: string, gl_acct: string, department_number: string, created_at: datetime, updated_at: datetime) + +#=> GiftCard(id: integer, certificate: integer, expiration_date: datetime, registrations_available: integer, associated_product: string, certificate_value: decimal, gl_code: string, created_at: datetime, updated_at: datetime, issuance_id: integer) + +total = `wc -l "FL_EventCertificate_202411261112.csv"`.split(" ").first.to_i +unknown_gct = GiftCardType.last + +system = Person.where(first_name: "Initial", last_name: "Import").first_or_create + +GiftCard.delete_all +Issuance.delete_all + +initial_issuances = GiftCardType.all.collect do |gct| + issuance = Issuance.where(creator_id: system, issuer_id: system, gift_card_type: gct).first_or_create + issuance.card_amount = 0 + issuance.quantity = 0 + issuance.save! + [ gct, issuance ] +end.to_h + +all_types = GiftCardType.all +batch = [] + +i = 0 +CSV.foreach("FL_EventCertificate_202411261112.csv", headers: true) do |row| + puts("[#{i += 1}/#{total}, #{(i / total.to_f * 100).round(2)}%]") + + # ex: # + # + gct = all_types.detect{ |gtt| gtt.numbering_regex.match(row["certificateId"]) } || unknown_gct + issuance = initial_issuances[gct] + + #gc = GiftCard.where(certificate_id: row["certificateId"]).first_or_initialize + gc = GiftCard.new + gc.certificate = row["certificateId"] + gc.issuance = issuance + gc.gift_card_type = gct + gc.expiration_date = DateTime.parse(row["expirationDate"]) + gc.registrations_available = row["numberRegistrations"].to_i + gc.certificate_value = row["certificateValue"].to_d + gc.gl_code = row["glCode"].to_d + gc.created_at = row["addDate"].to_d + gc.updated_at = row["modifiedDate"].to_d + gc.associated_product = row["associatedProduct"].to_d + + batch << gc + + if batch.length >= 1000 + GiftCard.import(batch) + batch = [] + end +end diff --git a/lib/has_numbering.rb b/lib/has_numbering.rb new file mode 100644 index 0000000..2a2d383 --- /dev/null +++ b/lib/has_numbering.rb @@ -0,0 +1,10 @@ +module HasNumbering + def numbering_regex_str + numbering.gsub(/x+/) { |xs| "(#{xs.gsub("x", "\\d")})" } + end + + def numbering_regex + # add brackets around the x's so that it can be extracted, and use \d instead of x + @numbering_regex ||= /#{numbering_regex_str}/ + end +end From 92212e1ecc59394f07049ae0da94fc3287bbe8a1 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Tue, 3 Dec 2024 19:25:49 -0500 Subject: [PATCH 07/73] fix numbering bug --- import.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/import.rb b/import.rb index 4facf94..b960a21 100644 --- a/import.rb +++ b/import.rb @@ -64,7 +64,7 @@ t.save! t = GiftCardType.where(label: "Corporate Department Card").first_or_initialize -t.numbering = "4241xxxx0" +t.numbering = "4241xxxxx0" t.gl_acct = "63301" t.department_number = "4241-45100" t.contact = "Glen Flagerstrom" From 7e5610fab23e1485fe52269423f2333d388705ca Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Wed, 4 Dec 2024 10:20:09 -0500 Subject: [PATCH 08/73] add back workflow for build-deploy-ecs --- .github/workflows/build-deploy-ecs.yml | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/build-deploy-ecs.yml diff --git a/.github/workflows/build-deploy-ecs.yml b/.github/workflows/build-deploy-ecs.yml new file mode 100644 index 0000000..9a90cb6 --- /dev/null +++ b/.github/workflows/build-deploy-ecs.yml @@ -0,0 +1,37 @@ +name: Build & Deploy ECS + +on: + push: + branches: + # Automatically build and deploy master and staging. Additional branches may be added. + # - main + - staging + workflow_dispatch: + # Allows manual build and deploy of any branch/ref + inputs: + auto-deploy: + type: boolean + description: Deploy image after building? + required: true + default: 'false' + +jobs: + # Build and push container image to ECR. Builds occur in the project repository. + build: + name: Build + uses: CruGlobal/.github/.github/workflows/build-ecs.yml@v1 + + # Triggers an ECS deployment in https://github.com/CruGlobal/cru-deploy/actions. + # All deployments happen in the cru-deploy repo. + deploy: + name: Deploy + runs-on: ubuntu-latest + needs: build + if: github.event_name != 'workflow_dispatch' || github.event.inputs.auto-deploy == 'true' + steps: + - uses: CruGlobal/.github/actions/trigger-deploy@v1 + with: + github-token: ${{ secrets.CRU_DEPLOY_GITHUB_TOKEN }} + project-name: ${{ needs.build.outputs.project-name }} + environment: ${{ needs.build.outputs.environment }} + build-number: ${{ needs.build.outputs.build-number }} From 7309aca977305f0f4f63d689c986f4e5332db87d Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Wed, 4 Dec 2024 13:00:02 -0500 Subject: [PATCH 09/73] add +x to build.sh --- build.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 build.sh diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 From 097c78907afeb7c7020cd232134acdc7973eb342 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Wed, 4 Dec 2024 13:04:43 -0500 Subject: [PATCH 10/73] add libpq-dev for pg gem to build --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 492a06c..7793165 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ FROM base AS build # Install packages needed to build gems RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y build-essential git pkg-config && \ + apt-get install --no-install-recommends -y build-essential git pkg-config libpq-dev && \ rm -rf /var/lib/apt/lists /var/cache/apt/archives # Install application gems From ee17a95cae674f49c9f712e107cc5d2a3e54a362 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Wed, 4 Dec 2024 14:08:10 -0500 Subject: [PATCH 11/73] switch to copy of mobile-content-api Dockerfile --- Dockerfile | 107 ++++++++++++++++++++++++----------------------------- 1 file changed, 48 insertions(+), 59 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7793165..714c575 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,69 +1,58 @@ -# syntax = docker/dockerfile:1 +# RUBY_VERSION set by build.sh based on .ruby-version file +ARG RUBY_VERSION +FROM public.ecr.aws/docker/library/ruby:${RUBY_VERSION}-alpine -# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand: -# docker build -t my-app . -# docker run -d -p 80:80 -p 443:443 --name my-app -e RAILS_MASTER_KEY= my-app +# DataDog logs source +LABEL com.datadoghq.ad.logs='[{"source": "ruby"}]' -# Make sure RUBY_VERSION matches the Ruby version in .ruby-version -ARG RUBY_VERSION=3.3.6 -FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base +# Create web application user to run as non-root +RUN addgroup -g 1000 webapp \ + && adduser -u 1000 -G webapp -s /bin/sh -D webapp \ + && mkdir -p /home/webapp/app +WORKDIR /home/webapp/app -# Rails app lives here -WORKDIR /rails +# Upgrade alpine packages (useful for security fixes) +RUN apk upgrade --no-cache -# Install base packages -RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 && \ - rm -rf /var/lib/apt/lists /var/cache/apt/archives +# Install rails/app dependencies +RUN apk --no-cache add libc6-compat git postgresql-libs tzdata -# Set production environment -ENV RAILS_ENV="production" \ - BUNDLE_DEPLOYMENT="1" \ - BUNDLE_PATH="/usr/local/bundle" \ - BUNDLE_WITHOUT="development" - -# Throw-away build stage to reduce size of final image -FROM base AS build - -# Install packages needed to build gems -RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y build-essential git pkg-config libpq-dev && \ - rm -rf /var/lib/apt/lists /var/cache/apt/archives - -# Install application gems +# Copy dependency definitions and lock files 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 - -# Copy application code -COPY . . - -# Precompile bootsnap code for faster boot times -RUN bundle exec bootsnap precompile app/ lib/ -# Precompiling assets for production without requiring secret RAILS_MASTER_KEY -RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile +# Install bundler version which created the lock file and configure it +ARG SIDEKIQ_CREDS +RUN gem install bundler -v $(awk '/^BUNDLED WITH/ { getline; print $1; exit }' Gemfile.lock) \ + && bundle config --global gems.contribsys.com $SIDEKIQ_CREDS +# Install build-dependencies, then install gems, subsequently removing build-dependencies +RUN apk --no-cache add --virtual build-deps build-base postgresql-dev \ + && bundle install --jobs 20 --retry 2 \ + && apk del build-deps +# Copy the application +COPY . . - -# Final stage for app image -FROM base - -# Copy built artifacts: gems, application -COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" -COPY --from=build /rails /rails - -# 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 the server by default, this can be overwritten at runtime -EXPOSE 3000 -CMD ["./bin/rails", "server"] +# Environment required to build the application +ARG RAILS_ENV=production +ARG TEST_DB_USER=postgres +ARG TEST_DB_PASSWORD +ARG TEST_DB_HOST=localhost +ARG TEST_DB_PORT=5432 + +# Compile assets and fix permissions +# just like in Actions, we need to copy the fake cred json so that our tests can function +RUN cp spec/fixtures/service_account_cred.json.actions config/secure/service_account_cred.json \ + && RAILS_ENV=test bundle exec rails db:create db:schema:load docs:generate \ + && rm config/secure/service_account_cred.json \ + && chown -R webapp:webapp /home/webapp/ + +# Define volumes used by ECS to share public html and extra nginx config with nginx container +VOLUME /home/webapp/app/public +VOLUME /home/webapp/app/nginx-conf + +# Run container process as non-root user +USER webapp + +# Command to start rails +CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"] From 1f8734689e035717f9252608fb8c894f48da5d94 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Wed, 4 Dec 2024 14:22:53 -0500 Subject: [PATCH 12/73] use maintenance Dockerfile and build.sh --- Dockerfile | 19 ++++++------------- build.sh | 8 -------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/Dockerfile b/Dockerfile index 714c575..3f532fb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,9 +21,7 @@ RUN apk --no-cache add libc6-compat git postgresql-libs tzdata COPY Gemfile Gemfile.lock ./ # Install bundler version which created the lock file and configure it -ARG SIDEKIQ_CREDS -RUN gem install bundler -v $(awk '/^BUNDLED WITH/ { getline; print $1; exit }' Gemfile.lock) \ - && bundle config --global gems.contribsys.com $SIDEKIQ_CREDS +RUN gem install bundler -v $(awk '/^BUNDLED WITH/ { getline; print $1; exit }' Gemfile.lock) # Install build-dependencies, then install gems, subsequently removing build-dependencies RUN apk --no-cache add --virtual build-deps build-base postgresql-dev \ @@ -35,16 +33,11 @@ COPY . . # Environment required to build the application ARG RAILS_ENV=production -ARG TEST_DB_USER=postgres -ARG TEST_DB_PASSWORD -ARG TEST_DB_HOST=localhost -ARG TEST_DB_PORT=5432 - -# Compile assets and fix permissions -# just like in Actions, we need to copy the fake cred json so that our tests can function -RUN cp spec/fixtures/service_account_cred.json.actions config/secure/service_account_cred.json \ - && RAILS_ENV=test bundle exec rails db:create db:schema:load docs:generate \ - && rm config/secure/service_account_cred.json \ +ARG STORAGE_REDIS_HOST=localhost +ARG SECRET_KEY_BASE=abc123 + +# Compile assets +RUN RAILS_ENV=production bundle exec rake assets:clobber assets:precompile \ && chown -R webapp:webapp /home/webapp/ # Define volumes used by ECS to share public html and extra nginx config with nginx container diff --git a/build.sh b/build.sh index d107393..a5e8c0f 100755 --- a/build.sh +++ b/build.sh @@ -1,12 +1,4 @@ #!/bin/bash - docker buildx build $DOCKER_ARGS \ - --build-arg SIDEKIQ_CREDS=$SIDEKIQ_CREDS \ --build-arg RUBY_VERSION=$(cat .ruby-version) \ . -rc=$? - -if [ $rc -ne 0 ]; then - echo -e "Docker build failed" - exit $rc -fi From f1a0a6df358faefbe6c72b1619372761e7a7254f Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Wed, 4 Dec 2024 14:38:35 -0500 Subject: [PATCH 13/73] add monitor --- app/controllers/monitors_controller.rb | 8 ++++++++ config/routes.rb | 2 ++ 2 files changed, 10 insertions(+) create mode 100644 app/controllers/monitors_controller.rb diff --git a/app/controllers/monitors_controller.rb b/app/controllers/monitors_controller.rb new file mode 100644 index 0000000..fa1cf6b --- /dev/null +++ b/app/controllers/monitors_controller.rb @@ -0,0 +1,8 @@ +class MonitorsController < ApplicationController + #skip_before_action :require_login, raise: false + + def lb + Person.first + render plain: "OK" + end +end diff --git a/config/routes.rb b/config/routes.rb index 2d82e1c..e1f7d8c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,6 +10,8 @@ get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker get "manifest" => "rails/pwa#manifest", as: :pwa_manifest + get "monitors/lb" => "monitors#lb" + # Defines the root path route ("/") # root "posts#index" end From caece4ad6e22b2227badc5a019731923579af9be Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Wed, 4 Dec 2024 16:34:45 -0500 Subject: [PATCH 14/73] rm test, we'll be using spec --- test/application_system_test_case.rb | 5 ----- .../application_cable/connection_test.rb | 13 ------------- test/controllers/.keep | 0 test/fixtures/api_keys.yml | 9 --------- test/fixtures/authentications.yml | 15 --------------- test/fixtures/files/.keep | 0 test/fixtures/gift_card_types.yml | 19 ------------------- test/fixtures/gift_cards.yml | 15 --------------- test/fixtures/issuances.yml | 19 ------------------- test/fixtures/people.yml | 9 --------- test/helpers/.keep | 0 test/integration/.keep | 0 test/mailers/.keep | 0 test/models/.keep | 0 test/models/api_key_test.rb | 7 ------- test/models/authentication_test.rb | 7 ------- test/models/gift_card_test.rb | 7 ------- test/models/gift_card_type_test.rb | 7 ------- test/models/issuance_test.rb | 7 ------- test/models/person_test.rb | 7 ------- test/system/.keep | 0 test/test_helper.rb | 15 --------------- 22 files changed, 161 deletions(-) delete mode 100644 test/application_system_test_case.rb delete mode 100644 test/channels/application_cable/connection_test.rb delete mode 100644 test/controllers/.keep delete mode 100644 test/fixtures/api_keys.yml delete mode 100644 test/fixtures/authentications.yml delete mode 100644 test/fixtures/files/.keep delete mode 100644 test/fixtures/gift_card_types.yml delete mode 100644 test/fixtures/gift_cards.yml delete mode 100644 test/fixtures/issuances.yml delete mode 100644 test/fixtures/people.yml delete mode 100644 test/helpers/.keep delete mode 100644 test/integration/.keep delete mode 100644 test/mailers/.keep delete mode 100644 test/models/.keep delete mode 100644 test/models/api_key_test.rb delete mode 100644 test/models/authentication_test.rb delete mode 100644 test/models/gift_card_test.rb delete mode 100644 test/models/gift_card_type_test.rb delete mode 100644 test/models/issuance_test.rb delete mode 100644 test/models/person_test.rb delete mode 100644 test/system/.keep delete mode 100644 test/test_helper.rb diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb deleted file mode 100644 index cee29fd..0000000 --- a/test/application_system_test_case.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "test_helper" - -class ApplicationSystemTestCase < ActionDispatch::SystemTestCase - driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1400 ] -end diff --git a/test/channels/application_cable/connection_test.rb b/test/channels/application_cable/connection_test.rb deleted file mode 100644 index 6340bf9..0000000 --- a/test/channels/application_cable/connection_test.rb +++ /dev/null @@ -1,13 +0,0 @@ -require "test_helper" - -module ApplicationCable - class ConnectionTest < ActionCable::Connection::TestCase - # test "connects with cookies" do - # cookies.signed[:user_id] = 42 - # - # connect - # - # assert_equal connection.user_id, "42" - # end - end -end diff --git a/test/controllers/.keep b/test/controllers/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/api_keys.yml b/test/fixtures/api_keys.yml deleted file mode 100644 index 5a8674a..0000000 --- a/test/fixtures/api_keys.yml +++ /dev/null @@ -1,9 +0,0 @@ -# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html - -one: - access_token: MyString - user: MyString - -two: - access_token: MyString - user: MyString diff --git a/test/fixtures/authentications.yml b/test/fixtures/authentications.yml deleted file mode 100644 index 4d0715c..0000000 --- a/test/fixtures/authentications.yml +++ /dev/null @@ -1,15 +0,0 @@ -# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html - -one: - person_id: 1 - provider: MyString - uid: MyString - token: MyString - username: MyString - -two: - person_id: 1 - provider: MyString - uid: MyString - token: MyString - username: MyString diff --git a/test/fixtures/files/.keep b/test/fixtures/files/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/gift_card_types.yml b/test/fixtures/gift_card_types.yml deleted file mode 100644 index 8b8b9ec..0000000 --- a/test/fixtures/gift_card_types.yml +++ /dev/null @@ -1,19 +0,0 @@ -# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html - -one: - label: MyString - numbering: MyString - contact: MyString - prod_id: MyString - isbn: MyString - gl_acct: MyString - department_number: MyString - -two: - label: MyString - numbering: MyString - contact: MyString - prod_id: MyString - isbn: MyString - gl_acct: MyString - department_number: MyString diff --git a/test/fixtures/gift_cards.yml b/test/fixtures/gift_cards.yml deleted file mode 100644 index 3ddaad3..0000000 --- a/test/fixtures/gift_cards.yml +++ /dev/null @@ -1,15 +0,0 @@ -# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html - -one: - expiration_date: 2024-11-26 04:21:31 - registrations_available: 1 - associated_product: MyString - certificate_value: 9.99 - gl_code: MyString - -two: - expiration_date: 2024-11-26 04:21:31 - registrations_available: 1 - associated_product: MyString - certificate_value: 9.99 - gl_code: MyString diff --git a/test/fixtures/issuances.yml b/test/fixtures/issuances.yml deleted file mode 100644 index a7c3356..0000000 --- a/test/fixtures/issuances.yml +++ /dev/null @@ -1,19 +0,0 @@ -# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html - -one: - state: MyString - initiator_id: 1 - card_amount: 9.99 - quantity: 1 - begin_use_date: 2024-11-26 06:12:53 - end_use_date: 2024-11-26 06:12:53 - expiration_date: 2024-11-26 06:12:53 - -two: - state: MyString - initiator_id: 1 - card_amount: 9.99 - quantity: 1 - begin_use_date: 2024-11-26 06:12:53 - end_use_date: 2024-11-26 06:12:53 - expiration_date: 2024-11-26 06:12:53 diff --git a/test/fixtures/people.yml b/test/fixtures/people.yml deleted file mode 100644 index 7da5ff7..0000000 --- a/test/fixtures/people.yml +++ /dev/null @@ -1,9 +0,0 @@ -# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html - -one: - first_name: MyString - last_name: MyString - -two: - first_name: MyString - last_name: MyString diff --git a/test/helpers/.keep b/test/helpers/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/integration/.keep b/test/integration/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/mailers/.keep b/test/mailers/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/models/.keep b/test/models/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/models/api_key_test.rb b/test/models/api_key_test.rb deleted file mode 100644 index 6dc74dc..0000000 --- a/test/models/api_key_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require "test_helper" - -class ApiKeyTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/models/authentication_test.rb b/test/models/authentication_test.rb deleted file mode 100644 index bb67860..0000000 --- a/test/models/authentication_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require "test_helper" - -class AuthenticationTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/models/gift_card_test.rb b/test/models/gift_card_test.rb deleted file mode 100644 index 30317c0..0000000 --- a/test/models/gift_card_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require "test_helper" - -class GiftCardTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/models/gift_card_type_test.rb b/test/models/gift_card_type_test.rb deleted file mode 100644 index 9d0c7cf..0000000 --- a/test/models/gift_card_type_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require "test_helper" - -class GiftCardTypeTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/models/issuance_test.rb b/test/models/issuance_test.rb deleted file mode 100644 index 261f884..0000000 --- a/test/models/issuance_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require "test_helper" - -class IssuanceTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/models/person_test.rb b/test/models/person_test.rb deleted file mode 100644 index 357c9af..0000000 --- a/test/models/person_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require "test_helper" - -class PersonTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/system/.keep b/test/system/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/test_helper.rb b/test/test_helper.rb deleted file mode 100644 index 0c22470..0000000 --- a/test/test_helper.rb +++ /dev/null @@ -1,15 +0,0 @@ -ENV["RAILS_ENV"] ||= "test" -require_relative "../config/environment" -require "rails/test_help" - -module ActiveSupport - class TestCase - # Run tests in parallel with specified workers - parallelize(workers: :number_of_processors) - - # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. - fixtures :all - - # Add more helper methods to be used by all tests here... - end -end From 6cb75f26d6ad3140122009a41f20f7d1eeed3906 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Wed, 4 Dec 2024 16:35:07 -0500 Subject: [PATCH 15/73] add hosts and config for stage --- config/environments/production.rb | 7 +- config/environments/stage.rb | 104 ++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 config/environments/stage.rb diff --git a/config/environments/production.rb b/config/environments/production.rb index 67b4816..f07b7ef 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -96,10 +96,9 @@ config.active_record.attributes_for_inspect = [ :id ] # Enable DNS rebinding protection and other `Host` header attacks. - # config.hosts = [ - # "example.com", # Allow requests from example.com - # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` - # ] + config.hosts = [ + "familylife-gift-cards.cru.org" + ] # Skip DNS rebinding protection for the default health check endpoint. # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } end diff --git a/config/environments/stage.rb b/config/environments/stage.rb new file mode 100644 index 0000000..c5520f7 --- /dev/null +++ b/config/environments/stage.rb @@ -0,0 +1,104 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment + # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true + + # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead. + # config.public_file_server.enabled = false + + # Compress CSS using a preprocessor. + # config.assets.css_compressor = :sass + + # Do not fall back to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache + # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Mount Action Cable outside main process or domain. + # config.action_cable.mount_path = nil + # config.action_cable.url = "wss://example.com/cable" + # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies. + # config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + + # Log to STDOUT by default + config.logger = ActiveSupport::Logger.new(STDOUT) + .tap { |logger| logger.formatter = ::Logger::Formatter.new } + .then { |logger| ActiveSupport::TaggedLogging.new(logger) } + + # Prepend all log lines with the following tags. + config.log_tags = [ :request_id ] + + # "info" includes generic and useful information about system operation, but avoids logging too much + # information to avoid inadvertent exposure of personally identifiable information (PII). If you + # want to log everything, set the level to "debug". + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment). + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "familylife_gift_cards_production" + + # Disable caching for Action Mailer templates even if Action Controller + # caching is enabled. + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [ :id ] + + # Enable DNS rebinding protection and other `Host` header attacks. + config.hosts = [ + "familylife-gift-cards-stage.cru.org" + ] + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end From 36f11007bf61d98517bd94c2567962e6ae8a2b9f Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Wed, 4 Dec 2024 16:51:18 -0500 Subject: [PATCH 16/73] it's staging and not stage --- config/environments/stage.rb | 104 ----------------------------------- 1 file changed, 104 deletions(-) delete mode 100644 config/environments/stage.rb diff --git a/config/environments/stage.rb b/config/environments/stage.rb deleted file mode 100644 index c5520f7..0000000 --- a/config/environments/stage.rb +++ /dev/null @@ -1,104 +0,0 @@ -require "active_support/core_ext/integer/time" - -Rails.application.configure do - # Settings specified here will take precedence over those in config/application.rb. - - # Code is not reloaded between requests. - config.enable_reloading = false - - # Eager load code on boot. This eager loads most of Rails and - # your application in memory, allowing both threaded web servers - # and those relying on copy on write to perform better. - # Rake tasks automatically ignore this option for performance. - config.eager_load = true - - # Full error reports are disabled and caching is turned on. - config.consider_all_requests_local = false - config.action_controller.perform_caching = true - - # Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment - # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files). - # config.require_master_key = true - - # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead. - # config.public_file_server.enabled = false - - # Compress CSS using a preprocessor. - # config.assets.css_compressor = :sass - - # Do not fall back to assets pipeline if a precompiled asset is missed. - config.assets.compile = false - - # Enable serving of images, stylesheets, and JavaScripts from an asset server. - # config.asset_host = "http://assets.example.com" - - # Specifies the header that your server uses for sending files. - # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache - # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX - - # Store uploaded files on the local file system (see config/storage.yml for options). - config.active_storage.service = :local - - # Mount Action Cable outside main process or domain. - # config.action_cable.mount_path = nil - # config.action_cable.url = "wss://example.com/cable" - # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] - - # Assume all access to the app is happening through a SSL-terminating reverse proxy. - # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies. - # config.assume_ssl = true - - # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - config.force_ssl = true - - # Skip http-to-https redirect for the default health check endpoint. - # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } - - # Log to STDOUT by default - config.logger = ActiveSupport::Logger.new(STDOUT) - .tap { |logger| logger.formatter = ::Logger::Formatter.new } - .then { |logger| ActiveSupport::TaggedLogging.new(logger) } - - # Prepend all log lines with the following tags. - config.log_tags = [ :request_id ] - - # "info" includes generic and useful information about system operation, but avoids logging too much - # information to avoid inadvertent exposure of personally identifiable information (PII). If you - # want to log everything, set the level to "debug". - config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") - - # Use a different cache store in production. - # config.cache_store = :mem_cache_store - - # Use a real queuing backend for Active Job (and separate queues per environment). - # config.active_job.queue_adapter = :resque - # config.active_job.queue_name_prefix = "familylife_gift_cards_production" - - # Disable caching for Action Mailer templates even if Action Controller - # caching is enabled. - config.action_mailer.perform_caching = false - - # Ignore bad email addresses and do not raise email delivery errors. - # Set this to true and configure the email server for immediate delivery to raise delivery errors. - # config.action_mailer.raise_delivery_errors = false - - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation cannot be found). - config.i18n.fallbacks = true - - # Don't log any deprecations. - config.active_support.report_deprecations = false - - # Do not dump schema after migrations. - config.active_record.dump_schema_after_migration = false - - # Only use :id for inspections in production. - config.active_record.attributes_for_inspect = [ :id ] - - # Enable DNS rebinding protection and other `Host` header attacks. - config.hosts = [ - "familylife-gift-cards-stage.cru.org" - ] - # Skip DNS rebinding protection for the default health check endpoint. - # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } -end From 8ca9f1e66f503172e0ae0e16be7cbdea212fe9e7 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 9 Dec 2024 11:01:27 -0500 Subject: [PATCH 17/73] add stage to production hosts to see if that makes it load --- config/environments/production.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index f07b7ef..670b68e 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -97,7 +97,8 @@ # Enable DNS rebinding protection and other `Host` header attacks. config.hosts = [ - "familylife-gift-cards.cru.org" + "familylife-gift-cards.cru.org", + "familylife-gift-cards-stage.cru.org" ] # Skip DNS rebinding protection for the default health check endpoint. # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } From da8c51703366e2d5b29db4a5f7a1d5c2ea1f7418 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 9 Dec 2024 11:07:19 -0500 Subject: [PATCH 18/73] forgot to add staging --- config/environments/production.rb | 3 +- config/environments/staging.rb | 104 ++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 config/environments/staging.rb diff --git a/config/environments/production.rb b/config/environments/production.rb index 670b68e..f07b7ef 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -97,8 +97,7 @@ # Enable DNS rebinding protection and other `Host` header attacks. config.hosts = [ - "familylife-gift-cards.cru.org", - "familylife-gift-cards-stage.cru.org" + "familylife-gift-cards.cru.org" ] # Skip DNS rebinding protection for the default health check endpoint. # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } diff --git a/config/environments/staging.rb b/config/environments/staging.rb new file mode 100644 index 0000000..c5520f7 --- /dev/null +++ b/config/environments/staging.rb @@ -0,0 +1,104 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment + # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true + + # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead. + # config.public_file_server.enabled = false + + # Compress CSS using a preprocessor. + # config.assets.css_compressor = :sass + + # Do not fall back to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache + # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Mount Action Cable outside main process or domain. + # config.action_cable.mount_path = nil + # config.action_cable.url = "wss://example.com/cable" + # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies. + # config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + + # Log to STDOUT by default + config.logger = ActiveSupport::Logger.new(STDOUT) + .tap { |logger| logger.formatter = ::Logger::Formatter.new } + .then { |logger| ActiveSupport::TaggedLogging.new(logger) } + + # Prepend all log lines with the following tags. + config.log_tags = [ :request_id ] + + # "info" includes generic and useful information about system operation, but avoids logging too much + # information to avoid inadvertent exposure of personally identifiable information (PII). If you + # want to log everything, set the level to "debug". + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment). + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "familylife_gift_cards_production" + + # Disable caching for Action Mailer templates even if Action Controller + # caching is enabled. + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [ :id ] + + # Enable DNS rebinding protection and other `Host` header attacks. + config.hosts = [ + "familylife-gift-cards-stage.cru.org" + ] + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end From d3558a5317f64a4ec71b4e32fda643d85a561ac7 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 9 Dec 2024 15:30:22 -0500 Subject: [PATCH 19/73] add sidekiq and add api key model we don't use sidekiq but it's already set up in terraform and could be used in the future, so it's good to have --- Gemfile | 7 +++++++ Gemfile.lock | 32 ++++++++++++++++++++++++++++++++ app/models/api_key.rb | 9 +-------- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/Gemfile b/Gemfile index 45d6ce8..b9e5e94 100644 --- a/Gemfile +++ b/Gemfile @@ -9,6 +9,11 @@ gem "sassc-rails" gem "pg" gem "dotenv" +gem "sidekiq" +gem "sidekiq-cron" +gem "sidekiq-failures" +gem "sidekiq-unique-jobs" + # Use the Puma web server [https://github.com/puma/puma] gem "puma", ">= 5.0" # Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails] @@ -71,3 +76,5 @@ gem "activeadmin_addons" gem "aasm" gem "activerecord-import" + + diff --git a/Gemfile.lock b/Gemfile.lock index 5af6688..284db63 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -126,6 +126,9 @@ GEM concurrent-ruby (1.3.4) connection_pool (2.4.1) crass (1.0.6) + cronex (0.15.0) + tzinfo + unicode (>= 0.4.4.5) csv (3.3.0) date (3.4.0) debug (1.9.2) @@ -138,6 +141,8 @@ GEM railties (>= 6.1) drb (2.2.1) erubi (1.13.0) + et-orbi (1.2.11) + tzinfo ffi (1.17.0-aarch64-linux-gnu) ffi (1.17.0-aarch64-linux-musl) ffi (1.17.0-arm-linux-gnu) @@ -151,6 +156,9 @@ GEM formtastic (5.0.0) actionpack (>= 6.0.0) formtastic_i18n (0.7.0) + fugit (1.11.1) + et-orbi (~> 1, >= 1.2.11) + raabro (~> 1.4) globalid (1.2.1) activesupport (>= 6.1) has_scope (0.8.2) @@ -252,6 +260,7 @@ GEM public_suffix (6.0.1) puma (6.5.0) nio4r (~> 2.0) + raabro (1.4.0) racc (1.8.1) rack (3.1.8) rack-session (2.0.0) @@ -298,6 +307,8 @@ GEM rdoc (6.8.1) psych (>= 4.0.0) redcarpet (3.6.0) + redis-client (0.22.2) + connection_pool regexp_parser (2.9.2) reline (0.5.11) io-console (~> 0.5) @@ -352,6 +363,22 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) + sidekiq (7.3.6) + connection_pool (>= 2.3.0) + logger + rack (>= 2.2.4) + redis-client (>= 0.22.2) + sidekiq-cron (2.0.1) + cronex (>= 0.13.0) + fugit (~> 1.8, >= 1.11.1) + globalid (>= 1.0.1) + sidekiq (>= 6.5.0) + sidekiq-failures (1.0.4) + sidekiq (>= 4.0.0) + sidekiq-unique-jobs (8.0.10) + concurrent-ruby (~> 1.0, >= 1.0.5) + sidekiq (>= 7.0.0, < 8.0.0) + thor (>= 1.0, < 3.0) slop (3.6.0) sprockets (4.2.1) concurrent-ruby (~> 1.0) @@ -380,6 +407,7 @@ GEM timeout (0.4.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) + unicode (0.4.4.5) unicode-display_width (2.6.0) useragent (0.16.10) web-console (4.2.1) @@ -436,6 +464,10 @@ DEPENDENCIES rubocop-rails-omakase sassc-rails selenium-webdriver + sidekiq + sidekiq-cron + sidekiq-failures + sidekiq-unique-jobs sprockets-rails standard stimulus-rails diff --git a/app/models/api_key.rb b/app/models/api_key.rb index 8e4096e..8018946 100644 --- a/app/models/api_key.rb +++ b/app/models/api_key.rb @@ -1,9 +1,2 @@ -class ApiKey < ApplicationRecord - def self.ransackable_attributes(auth_object = nil) - [] - end - - def self.ransackable_associations(auth_object = nil) - [] - end +class APiKey < ApplicationRecord end From 9d43a53071c636f872e9fc9675ef3d58861877b4 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 9 Dec 2024 15:35:26 -0500 Subject: [PATCH 20/73] add secrets --- config/secrets.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 config/secrets.yml diff --git a/config/secrets.yml b/config/secrets.yml new file mode 100644 index 0000000..47a1414 --- /dev/null +++ b/config/secrets.yml @@ -0,0 +1,27 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key is used for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! + +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +# You can use `rails secret` to generate a secure secret key. + +# Make sure the secrets in this file are kept private +# if you're sharing your code publicly. + +development: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> + +test: + secret_key_base: 8d4da753bd716d3b5160c0cb2de9135f0bb262b234516519817c25ef9fa278607d6ccae41b979bf626e7f3f7d17b2d370793d8213b4aeb61bcefff7b06e58218 + +# Do not keep production secrets in the unencrypted secrets file. +# Instead, either read values from the environment. +# Or, use `bin/rails secrets:setup` to configure encrypted secrets +# and move the `production:` environment over there. +staging: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> + +production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> From e34164bab780233e79f2ea0e2203e98a3430377b Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 9 Dec 2024 16:01:23 -0500 Subject: [PATCH 21/73] fix ApiKey class not defining ApiKey properly --- app/models/api_key.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/api_key.rb b/app/models/api_key.rb index 8018946..f44edba 100644 --- a/app/models/api_key.rb +++ b/app/models/api_key.rb @@ -1,2 +1,2 @@ -class APiKey < ApplicationRecord +class ApiKey < ApplicationRecord end From 0e3d608e1b53885842ea18ee5d495cc9a73bb23c Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 9 Dec 2024 21:51:45 -0500 Subject: [PATCH 22/73] add session store for staging --- Gemfile | 5 +-- Gemfile.lock | 14 ++++++++ config/application.rb | 6 +++- config/initializers/redis.rb | 7 ++++ config/initializers/session_store.rb | 7 ++++ config/initializers/sidekiq.rb | 54 ++++++++++++++++++++++++++++ config/redis.yml | 17 +++++++++ 7 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 config/initializers/redis.rb create mode 100644 config/initializers/session_store.rb create mode 100644 config/initializers/sidekiq.rb create mode 100644 config/redis.yml diff --git a/Gemfile b/Gemfile index b9e5e94..907d531 100644 --- a/Gemfile +++ b/Gemfile @@ -23,7 +23,8 @@ gem "stimulus-rails" # Build JSON APIs with ease [https://github.com/rails/jbuilder] gem "jbuilder" # Use Redis adapter to run Action Cable in production -# gem "redis", ">= 4.0.1" +gem "redis", "< 5.0" +gem "redis-actionpack" # Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis] # gem "kredis" @@ -77,4 +78,4 @@ gem "aasm" gem "activerecord-import" - +gem "dogstatsd-ruby", "~> 5.3" diff --git a/Gemfile.lock b/Gemfile.lock index 284db63..cf30111 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -135,6 +135,7 @@ GEM irb (~> 1.10) reline (>= 0.3.8) debug_inspector (1.2.0) + dogstatsd-ruby (5.6.3) dotenv (3.1.4) dotenv-rails (3.1.4) dotenv (= 3.1.4) @@ -307,8 +308,18 @@ GEM rdoc (6.8.1) psych (>= 4.0.0) redcarpet (3.6.0) + redis (4.8.1) + redis-actionpack (5.5.0) + actionpack (>= 5) + redis-rack (>= 2.1.0, < 4) + redis-store (>= 1.1.0, < 2) redis-client (0.22.2) connection_pool + redis-rack (3.0.0) + rack-session (>= 0.2.0) + redis-store (>= 1.2, < 2) + redis-store (1.11.0) + redis (>= 4, < 6) regexp_parser (2.9.2) reline (0.5.11) io-console (~> 0.5) @@ -451,6 +462,7 @@ DEPENDENCIES brakeman capybara debug + dogstatsd-ruby (~> 5.3) dotenv dotenv-rails importmap-rails @@ -461,6 +473,8 @@ DEPENDENCIES pry-stack_explorer puma (>= 5.0) rails (~> 7.2.2) + redis (< 5.0) + redis-actionpack rubocop-rails-omakase sassc-rails selenium-webdriver diff --git a/config/application.rb b/config/application.rb index 01f415f..40daa3f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -16,6 +16,10 @@ class Application < Rails::Application # Common ones are `templates`, `generators`, or `middleware`, for example. config.autoload_lib(ignore: %w[assets tasks]) + redis_conf = YAML.safe_load(ERB.new(File.read(Rails.root.join("config", "redis.yml"))).result, permitted_classes: [Symbol], aliases: true)["cache"] + redis_conf[:url] = "redis://" + redis_conf[:host] + "/" + redis_conf[:db].to_s + config.cache_store = :redis_cache_store, redis_conf + # Configuration for the application, engines, and railties goes here. # # These settings can be overridden in specific environments using the files @@ -23,5 +27,5 @@ class Application < Rails::Application # # config.time_zone = "Central Time (US & Canada)" # config.eager_load_paths << Rails.root.join("extras") - end + end end diff --git a/config/initializers/redis.rb b/config/initializers/redis.rb new file mode 100644 index 0000000..9fc3e32 --- /dev/null +++ b/config/initializers/redis.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "redis" + +Redis.current = Redis.new(host: ENV.fetch("STORAGE_REDIS_HOST"), + port: ENV.fetch("STORAGE_REDIS_PORT", 6379), + db: ENV.fetch("STORAGE_REDIS_DB_INDEX", 3)) diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb new file mode 100644 index 0000000..d9b5ca1 --- /dev/null +++ b/config/initializers/session_store.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "redis" + +redis_conf = YAML.safe_load(ERB.new(File.read(Rails.root.join("config", "redis.yml"))).result, permitted_classes: [Symbol], aliases: true)["session"] + +Rails.application.config.session_store :redis_store, servers: [redis_conf], expire_after: 2.hours diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb new file mode 100644 index 0000000..77aefab --- /dev/null +++ b/config/initializers/sidekiq.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "redis" +require "sidekiq-unique-jobs" +require "datadog/statsd" + +redis_conf = YAML.safe_load(ERB.new(File.read(Rails.root.join("config", "redis.yml"))).result, permitted_classes: [Symbol], aliases: true)["sidekiq"] + +redis_settings = {url: Redis.new(redis_conf).id} + +SidekiqUniqueJobs.configure do |config| + # don't use SidekiqUniqueJobs in test env because it will cause head-scratching + # https://github.com/mhenrixon/sidekiq-unique-jobs#uniqueness + # https://github.com/mperham/sidekiq/wiki/Ent-Unique-Jobs#enable (not our gem but Sidekiq Enterprise suggested the same thing) + config.enabled = !Rails.env.test? +end + +Sidekiq.configure_client do |config| + config.redis = redis_settings + + config.client_middleware do |chain| + chain.add SidekiqUniqueJobs::Middleware::Client + end +end + +if Sidekiq::Client.method_defined? :reliable_push! + Sidekiq::Client.reliable_push! unless Rails.env.test? +end + +Sidekiq.configure_server do |config| + config.super_fetch! + config.reliable_scheduler! + config.redis = redis_settings + config.failures_default_mode = :exhausted + + config.client_middleware do |chain| + chain.add SidekiqUniqueJobs::Middleware::Client + end + + config.server_middleware do |chain| + chain.add SidekiqUniqueJobs::Middleware::Server + end + + SidekiqUniqueJobs::Server.configure(config) +end + +Sidekiq.default_job_options = { + backtrace: true, + lock: :until_executed +} + +if ENV["AWS_EXECUTION_ENV"].present? + Sidekiq::Pro.dogstatsd = -> { Datadog::Statsd.new socket_path: "/var/run/datadog/dsd.socket" } +end diff --git a/config/redis.yml b/config/redis.yml new file mode 100644 index 0000000..1d81561 --- /dev/null +++ b/config/redis.yml @@ -0,0 +1,17 @@ +default: &DEFAULT + :db: <%= ENV.fetch('STORAGE_REDIS_DB_INDEX') %> + +sidekiq: + <<: *DEFAULT + :host: <%= ENV.fetch('STORAGE_REDIS_HOST') %> + :port: <%= ENV.fetch('STORAGE_REDIS_PORT', 6379) %> + +session: &SESSION + <<: *DEFAULT + :host: <%= ENV.fetch('SESSION_REDIS_HOST') %> + :port: <%= ENV.fetch('SESSION_REDIS_PORT', 6379) %> + :db: <%= ENV.fetch('SESSION_REDIS_DB_INDEX') %> + :expires_in: <%= 2.hours %> + +cache: + <<: *SESSION From 50c67894f60d23e8e354317e67d332315e11f86f Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 9 Dec 2024 22:39:54 -0500 Subject: [PATCH 23/73] add .env for build that looks for some env vars --- .env | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 0000000..6a904e7 --- /dev/null +++ b/.env @@ -0,0 +1,9 @@ +RAILS_SERVE_STATIC_FILES=true +SITE_URL=https://familylife-gift-cards-stage.cru.org +SESSION_REDIS_DB_INDEX=1 +SESSION_REDIS_HOST=localhost +SESSION_REDIS_PORT=6379 +STORAGE_REDIS_DB_INDEX=1 +STORAGE_REDIS_HOST=localhost +STORAGE_REDIS_PORT=6379 + From 292bf4768645fef1fd9a657bc09fe145fc7cbbe6 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 9 Dec 2024 23:26:29 -0500 Subject: [PATCH 24/73] seems the redis initializer isn't necessary --- Dockerfile | 1 + config/initializers/redis.rb | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 config/initializers/redis.rb diff --git a/Dockerfile b/Dockerfile index 3f532fb..faaaf33 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,6 +34,7 @@ COPY . . # Environment required to build the application ARG RAILS_ENV=production ARG STORAGE_REDIS_HOST=localhost +ARG STORAGE_REDIS_DB_INDEX=1 ARG SECRET_KEY_BASE=abc123 # Compile assets diff --git a/config/initializers/redis.rb b/config/initializers/redis.rb deleted file mode 100644 index 9fc3e32..0000000 --- a/config/initializers/redis.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require "redis" - -Redis.current = Redis.new(host: ENV.fetch("STORAGE_REDIS_HOST"), - port: ENV.fetch("STORAGE_REDIS_PORT", 6379), - db: ENV.fetch("STORAGE_REDIS_DB_INDEX", 3)) From 4c53375a0e353e2774883fc5d195ae8e76850409 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 9 Dec 2024 23:30:41 -0500 Subject: [PATCH 25/73] copy args needed to build from one-app --- Dockerfile | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index faaaf33..aff5481 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,9 +33,14 @@ COPY . . # Environment required to build the application ARG RAILS_ENV=production -ARG STORAGE_REDIS_HOST=localhost +ARG RAILS_ENV=production +ARG SESSION_REDIS_DB_INDEX=1 +ARG SESSION_REDIS_HOST=localhost +ARG SESSION_REDIS_PORT=6379 ARG STORAGE_REDIS_DB_INDEX=1 -ARG SECRET_KEY_BASE=abc123 +ARG STORAGE_REDIS_HOST=localhost +ARG STORAGE_REDIS_PORT=6379 +ARG SECRET_KEY_BASE=asdf # Compile assets RUN RAILS_ENV=production bundle exec rake assets:clobber assets:precompile \ From 1793f329551cddce30656a9596e2e5cea5eba515 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 9 Dec 2024 23:39:15 -0500 Subject: [PATCH 26/73] disable sidekiq pro for now --- config/initializers/sidekiq.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 77aefab..40c2dc9 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -49,6 +49,6 @@ lock: :until_executed } -if ENV["AWS_EXECUTION_ENV"].present? - Sidekiq::Pro.dogstatsd = -> { Datadog::Statsd.new socket_path: "/var/run/datadog/dsd.socket" } -end +#if ENV["AWS_EXECUTION_ENV"].present? +# Sidekiq::Pro.dogstatsd = -> { Datadog::Statsd.new socket_path: "/var/run/datadog/dsd.socket" } +#end From f9619f640fdfb509551b1421b7588e71b4308386 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 9 Dec 2024 23:59:10 -0500 Subject: [PATCH 27/73] allow 10.* ips --- config/environments/staging.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/environments/staging.rb b/config/environments/staging.rb index c5520f7..ff6a9f0 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -97,7 +97,8 @@ # Enable DNS rebinding protection and other `Host` header attacks. config.hosts = [ - "familylife-gift-cards-stage.cru.org" + "familylife-gift-cards-stage.cru.org", + IPAddr.new("10.0.0.0/8"), ] # Skip DNS rebinding protection for the default health check endpoint. # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } From cc6d0948a2f1c50b12f1c536154c4bffa8dde6d7 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Tue, 10 Dec 2024 00:59:36 -0500 Subject: [PATCH 28/73] just disable sidekiq config for now --- config/initializers/sidekiq.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 40c2dc9..028ac37 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +=begin require "redis" require "sidekiq-unique-jobs" require "datadog/statsd" @@ -49,6 +50,7 @@ lock: :until_executed } -#if ENV["AWS_EXECUTION_ENV"].present? -# Sidekiq::Pro.dogstatsd = -> { Datadog::Statsd.new socket_path: "/var/run/datadog/dsd.socket" } -#end +if ENV["AWS_EXECUTION_ENV"].present? + Sidekiq::Pro.dogstatsd = -> { Datadog::Statsd.new socket_path: "/var/run/datadog/dsd.socket" } +end +=end From 457cd2189597026fd1174f0c70aa77bf3f0d9b04 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Tue, 10 Dec 2024 02:07:10 -0500 Subject: [PATCH 29/73] make sure sidekiq can start --- Gemfile | 7 +++-- Gemfile.lock | 51 +++++++++++++++++++++------------- config/initializers/sidekiq.rb | 18 +++--------- 3 files changed, 40 insertions(+), 36 deletions(-) diff --git a/Gemfile b/Gemfile index 907d531..7a69d00 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,10 @@ gem "sassc-rails" gem "pg" gem "dotenv" -gem "sidekiq" +gem "sidekiq", "~> 6.5" +source "https://gems.contribsys.com/" do + gem "sidekiq-pro" +end gem "sidekiq-cron" gem "sidekiq-failures" gem "sidekiq-unique-jobs" @@ -23,7 +26,7 @@ gem "stimulus-rails" # Build JSON APIs with ease [https://github.com/rails/jbuilder] gem "jbuilder" # Use Redis adapter to run Action Cable in production -gem "redis", "< 5.0" +gem "redis", "~> 4.0" gem "redis-actionpack" # Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis] diff --git a/Gemfile.lock b/Gemfile.lock index cf30111..7fccc45 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,9 @@ +GEM + remote: https://gems.contribsys.com/ + specs: + sidekiq-pro (5.5.8) + sidekiq (~> 6.0, >= 6.5.6) + GEM remote: https://rubygems.org/ specs: @@ -111,6 +117,9 @@ GEM msgpack (~> 1.2) brakeman (6.2.2) racc + brpoplpush-redis_script (0.1.3) + concurrent-ruby (~> 1.0, >= 1.0.5) + redis (>= 1.0, < 6) builder (3.3.0) byebug (11.1.3) capybara (3.40.0) @@ -202,7 +211,7 @@ GEM kaminari-core (1.2.2) language_server-protocol (3.17.0.3) lint_roller (1.1.0) - logger (1.6.1) + logger (1.6.2) loofah (2.23.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) @@ -215,7 +224,7 @@ GEM matrix (0.4.2) method_source (1.1.0) mini_mime (1.1.5) - minitest (5.25.2) + minitest (5.25.4) msgpack (1.7.5) net-imap (0.5.1) date @@ -263,13 +272,14 @@ GEM nio4r (~> 2.0) raabro (1.4.0) racc (1.8.1) - rack (3.1.8) - rack-session (2.0.0) - rack (>= 3.0.0) + rack (2.2.10) + rack-session (1.0.2) + rack (< 3) rack-test (2.1.0) rack (>= 1.3) - rackup (2.2.1) - rack (>= 3) + rackup (1.0.1) + rack (< 3) + webrick rails (7.2.2) actioncable (= 7.2.2) actionmailbox (= 7.2.2) @@ -313,8 +323,6 @@ GEM actionpack (>= 5) redis-rack (>= 2.1.0, < 4) redis-store (>= 1.1.0, < 2) - redis-client (0.22.2) - connection_pool redis-rack (3.0.0) rack-session (>= 0.2.0) redis-store (>= 1.2, < 2) @@ -367,18 +375,17 @@ GEM sprockets (> 3.0) sprockets-rails tilt - securerandom (0.3.2) + securerandom (0.4.0) selenium-webdriver (4.27.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - sidekiq (7.3.6) - connection_pool (>= 2.3.0) - logger - rack (>= 2.2.4) - redis-client (>= 0.22.2) + sidekiq (6.5.12) + connection_pool (>= 2.2.5, < 3) + rack (~> 2.0) + redis (>= 4.5.0, < 5) sidekiq-cron (2.0.1) cronex (>= 0.13.0) fugit (~> 1.8, >= 1.11.1) @@ -386,10 +393,12 @@ GEM sidekiq (>= 6.5.0) sidekiq-failures (1.0.4) sidekiq (>= 4.0.0) - sidekiq-unique-jobs (8.0.10) + sidekiq-unique-jobs (7.1.33) + brpoplpush-redis_script (> 0.1.1, <= 2.0.0) concurrent-ruby (~> 1.0, >= 1.0.5) - sidekiq (>= 7.0.0, < 8.0.0) - thor (>= 1.0, < 3.0) + redis (< 5.0) + sidekiq (>= 5.0, < 7.0) + thor (>= 0.20, < 3.0) slop (3.6.0) sprockets (4.2.1) concurrent-ruby (~> 1.0) @@ -426,6 +435,7 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) + webrick (1.9.1) websocket (1.2.11) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) @@ -473,14 +483,15 @@ DEPENDENCIES pry-stack_explorer puma (>= 5.0) rails (~> 7.2.2) - redis (< 5.0) + redis (~> 4.0) redis-actionpack rubocop-rails-omakase sassc-rails selenium-webdriver - sidekiq + sidekiq (~> 6.5) sidekiq-cron sidekiq-failures + sidekiq-pro! sidekiq-unique-jobs sprockets-rails standard diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 028ac37..2a13409 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -1,9 +1,5 @@ -# frozen_string_literal: true - -=begin -require "redis" -require "sidekiq-unique-jobs" require "datadog/statsd" +require "redis" redis_conf = YAML.safe_load(ERB.new(File.read(Rails.root.join("config", "redis.yml"))).result, permitted_classes: [Symbol], aliases: true)["sidekiq"] @@ -24,9 +20,7 @@ end end -if Sidekiq::Client.method_defined? :reliable_push! - Sidekiq::Client.reliable_push! unless Rails.env.test? -end +Sidekiq::Client.reliable_push! Sidekiq.configure_server do |config| config.super_fetch! @@ -45,12 +39,8 @@ SidekiqUniqueJobs::Server.configure(config) end -Sidekiq.default_job_options = { - backtrace: true, - lock: :until_executed -} - if ENV["AWS_EXECUTION_ENV"].present? Sidekiq::Pro.dogstatsd = -> { Datadog::Statsd.new socket_path: "/var/run/datadog/dsd.socket" } end -=end + +Sidekiq.default_worker_options = {"backtrace" => true} From 3651f43674c635413a420e8181fa4e50cf63abf2 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Tue, 10 Dec 2024 02:18:30 -0500 Subject: [PATCH 30/73] support sidekiq pro --- Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index aff5481..57ca6ce 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,10 @@ RUN apk --no-cache add libc6-compat git postgresql-libs tzdata COPY Gemfile Gemfile.lock ./ # Install bundler version which created the lock file and configure it -RUN gem install bundler -v $(awk '/^BUNDLED WITH/ { getline; print $1; exit }' Gemfile.lock) +ARG SIDEKIQ_CREDS +ENV LD_LIBRARY_PATH /usr/lib/instantclient +RUN gem install bundler -v $(awk '/^BUNDLED WITH/ { getline; print $1; exit }' Gemfile.lock) \ + && bundle config --global gems.contribsys.com $SIDEKIQ_CREDS # Install build-dependencies, then install gems, subsequently removing build-dependencies RUN apk --no-cache add --virtual build-deps build-base postgresql-dev \ From 3ca8f4cb0b1bee55eded6cb75c2cefdcf3b32d83 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Tue, 10 Dec 2024 04:00:50 -0500 Subject: [PATCH 31/73] pass in sidekiq pro creds --- build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/build.sh b/build.sh index a5e8c0f..894cf01 100755 --- a/build.sh +++ b/build.sh @@ -1,4 +1,5 @@ #!/bin/bash docker buildx build $DOCKER_ARGS \ + --build-arg SIDEKIQ_CREDS=$SIDEKIQ_CREDS \ --build-arg RUBY_VERSION=$(cat .ruby-version) \ . From 491b594b4d9ddfc992aeb373926ffeff57831f3a Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Tue, 10 Dec 2024 11:23:35 -0500 Subject: [PATCH 32/73] use puma config from cap --- config/puma.rb | 59 +++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/config/puma.rb b/config/puma.rb index 03c166f..5b2ed4d 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -1,34 +1,43 @@ -# This configuration file will be evaluated by Puma. The top-level methods that -# are invoked here are part of Puma's configuration DSL. For more information -# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers: a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum; this matches the default thread size of Active Record. +# +max_threads_count = ENV.fetch("RAILS_MAX_THREADS", 5) +min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } +threads min_threads_count, max_threads_count -# Puma starts a configurable number of processes (workers) and each process -# serves each request in a thread from an internal thread pool. +# Specifies the `worker_timeout` threshold that Puma will use to wait before +# terminating a worker in development environments. # -# The ideal number of threads per worker depends both on how much time the -# application spends waiting for IO operations and on how much you wish to -# to prioritize throughput over latency. +worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. # -# As a rule of thumb, increasing the number of threads will increase how much -# traffic a given process can handle (throughput), but due to CRuby's -# Global VM Lock (GVL) it has diminishing returns and will degrade the -# response time (latency) of the application. +port ENV.fetch("PORT", 3000) + +# Specifies the `environment` that Puma will run in. # -# The default is set to 3 threads as it's deemed a decent compromise between -# throughput and latency for the average Rails application. +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the `pidfile` that Puma will use. +pidfile ENV.fetch("PIDFILE") { "/tmp/puma.pid" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked web server processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). # -# Any libraries that use a connection pool or another resource pool should -# be configured to provide at least as many connections as the number of -# threads. This includes Active Record's `pool` parameter in `database.yml`. -threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) -threads threads_count, threads_count +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } -# Specifies the `port` that Puma will listen on to receive requests; default is 3000. -port ENV.fetch("PORT", 3000) +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. +# +# preload_app! # Allow puma to be restarted by `bin/rails restart` command. plugin :tmp_restart - -# Specify the PID file. Defaults to tmp/pids/server.pid in development. -# In other environments, only set the PID file if requested. -pidfile ENV["PIDFILE"] if ENV["PIDFILE"] From 8cbd5b5c32bf32cbf6fcf2eac19418120c818b1c Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Tue, 10 Dec 2024 11:49:00 -0500 Subject: [PATCH 33/73] try to allow monitors/lb without host_authorization --- config/environments/staging.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/environments/staging.rb b/config/environments/staging.rb index ff6a9f0..70cc653 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -100,6 +100,7 @@ "familylife-gift-cards-stage.cru.org", IPAddr.new("10.0.0.0/8"), ] + # Skip DNS rebinding protection for the default health check endpoint. - # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } + config.host_authorization = { exclude: ->(request) { request.path == "/monitors/lb" } } end From 9fb046315a6c7c2e6fa5ab3c59162a6e8c92ff23 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Tue, 10 Dec 2024 12:16:41 -0500 Subject: [PATCH 34/73] copy cutmeoff which allows monitor check without ssl --- config/environments/production.rb | 53 +++++---------- config/environments/staging.rb | 108 +----------------------------- 2 files changed, 20 insertions(+), 141 deletions(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index f07b7ef..24f9071 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -36,31 +36,27 @@ # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX - # Store uploaded files on the local file system (see config/storage.yml for options). - config.active_storage.service = :local - - # Mount Action Cable outside main process or domain. - # config.action_cable.mount_path = nil - # config.action_cable.url = "wss://example.com/cable" - # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] - # Assume all access to the app is happening through a SSL-terminating reverse proxy. # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies. # config.assume_ssl = true # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - config.force_ssl = true - - # Skip http-to-https redirect for the default health check endpoint. - # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + if ENV["AWS_EXECUTION_ENV"].present? + config.force_ssl = true + config.ssl_options = { + redirect: { + exclude: ->(request) { request.fullpath == "/monitors/lb" } + } + } + end # Log to STDOUT by default - config.logger = ActiveSupport::Logger.new(STDOUT) - .tap { |logger| logger.formatter = ::Logger::Formatter.new } - .then { |logger| ActiveSupport::TaggedLogging.new(logger) } + # config.logger = ActiveSupport::Logger.new($stdout) + # .tap { |logger| logger.formatter = ::Logger::Formatter.new } + # .then { |logger| ActiveSupport::TaggedLogging.new(logger) } # Prepend all log lines with the following tags. - config.log_tags = [ :request_id ] + config.log_tags = [:request_id] # "info" includes generic and useful information about system operation, but avoids logging too much # information to avoid inadvertent exposure of personally identifiable information (PII). If you @@ -70,18 +66,6 @@ # Use a different cache store in production. # config.cache_store = :mem_cache_store - # Use a real queuing backend for Active Job (and separate queues per environment). - # config.active_job.queue_adapter = :resque - # config.active_job.queue_name_prefix = "familylife_gift_cards_production" - - # Disable caching for Action Mailer templates even if Action Controller - # caching is enabled. - config.action_mailer.perform_caching = false - - # Ignore bad email addresses and do not raise email delivery errors. - # Set this to true and configure the email server for immediate delivery to raise delivery errors. - # config.action_mailer.raise_delivery_errors = false - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). config.i18n.fallbacks = true @@ -92,13 +76,12 @@ # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false - # Only use :id for inspections in production. - config.active_record.attributes_for_inspect = [ :id ] - # Enable DNS rebinding protection and other `Host` header attacks. - config.hosts = [ - "familylife-gift-cards.cru.org" - ] + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] # Skip DNS rebinding protection for the default health check endpoint. - # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } + config.host_authorization = {exclude: ->(request) { request.path == "/monitors/lb" }} + config.hosts << "familylife-gift-cards-stage.cru.org" end diff --git a/config/environments/staging.rb b/config/environments/staging.rb index 70cc653..8e233f3 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -1,106 +1,2 @@ -require "active_support/core_ext/integer/time" - -Rails.application.configure do - # Settings specified here will take precedence over those in config/application.rb. - - # Code is not reloaded between requests. - config.enable_reloading = false - - # Eager load code on boot. This eager loads most of Rails and - # your application in memory, allowing both threaded web servers - # and those relying on copy on write to perform better. - # Rake tasks automatically ignore this option for performance. - config.eager_load = true - - # Full error reports are disabled and caching is turned on. - config.consider_all_requests_local = false - config.action_controller.perform_caching = true - - # Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment - # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files). - # config.require_master_key = true - - # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead. - # config.public_file_server.enabled = false - - # Compress CSS using a preprocessor. - # config.assets.css_compressor = :sass - - # Do not fall back to assets pipeline if a precompiled asset is missed. - config.assets.compile = false - - # Enable serving of images, stylesheets, and JavaScripts from an asset server. - # config.asset_host = "http://assets.example.com" - - # Specifies the header that your server uses for sending files. - # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache - # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX - - # Store uploaded files on the local file system (see config/storage.yml for options). - config.active_storage.service = :local - - # Mount Action Cable outside main process or domain. - # config.action_cable.mount_path = nil - # config.action_cable.url = "wss://example.com/cable" - # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] - - # Assume all access to the app is happening through a SSL-terminating reverse proxy. - # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies. - # config.assume_ssl = true - - # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - config.force_ssl = true - - # Skip http-to-https redirect for the default health check endpoint. - # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } - - # Log to STDOUT by default - config.logger = ActiveSupport::Logger.new(STDOUT) - .tap { |logger| logger.formatter = ::Logger::Formatter.new } - .then { |logger| ActiveSupport::TaggedLogging.new(logger) } - - # Prepend all log lines with the following tags. - config.log_tags = [ :request_id ] - - # "info" includes generic and useful information about system operation, but avoids logging too much - # information to avoid inadvertent exposure of personally identifiable information (PII). If you - # want to log everything, set the level to "debug". - config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") - - # Use a different cache store in production. - # config.cache_store = :mem_cache_store - - # Use a real queuing backend for Active Job (and separate queues per environment). - # config.active_job.queue_adapter = :resque - # config.active_job.queue_name_prefix = "familylife_gift_cards_production" - - # Disable caching for Action Mailer templates even if Action Controller - # caching is enabled. - config.action_mailer.perform_caching = false - - # Ignore bad email addresses and do not raise email delivery errors. - # Set this to true and configure the email server for immediate delivery to raise delivery errors. - # config.action_mailer.raise_delivery_errors = false - - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation cannot be found). - config.i18n.fallbacks = true - - # Don't log any deprecations. - config.active_support.report_deprecations = false - - # Do not dump schema after migrations. - config.active_record.dump_schema_after_migration = false - - # Only use :id for inspections in production. - config.active_record.attributes_for_inspect = [ :id ] - - # Enable DNS rebinding protection and other `Host` header attacks. - config.hosts = [ - "familylife-gift-cards-stage.cru.org", - IPAddr.new("10.0.0.0/8"), - ] - - # Skip DNS rebinding protection for the default health check endpoint. - config.host_authorization = { exclude: ->(request) { request.path == "/monitors/lb" } } -end +# Any differences between prod and stage should be handled in ENV variables +require Rails.root.join("config", "environments", "production").to_s From 5dd752cda2c399aab4243b7648e351ceb0bdb8d2 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Tue, 10 Dec 2024 12:23:33 -0500 Subject: [PATCH 35/73] use SITE_URL env var for allowed host to work in prod & stage --- config/environments/production.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index 24f9071..cc1e822 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -83,5 +83,5 @@ # ] # Skip DNS rebinding protection for the default health check endpoint. config.host_authorization = {exclude: ->(request) { request.path == "/monitors/lb" }} - config.hosts << "familylife-gift-cards-stage.cru.org" + config.hosts << ENV.fetch("SITE_URL") end From 1758cc6a409a307a8df90a5e46a7fc8a4b132a4d Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Tue, 10 Dec 2024 12:33:57 -0500 Subject: [PATCH 36/73] add site_url to build --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 57ca6ce..9babd15 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,6 +44,7 @@ ARG STORAGE_REDIS_DB_INDEX=1 ARG STORAGE_REDIS_HOST=localhost ARG STORAGE_REDIS_PORT=6379 ARG SECRET_KEY_BASE=asdf +ARG SITE_URL=asdf # Compile assets RUN RAILS_ENV=production bundle exec rake assets:clobber assets:precompile \ From 508a24ccc1779009f91d0b47c230b1f33bce5320 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Wed, 11 Dec 2024 15:25:45 -0500 Subject: [PATCH 37/73] use official redis db index --- .env | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.env b/.env index 6a904e7..f1d1c42 100644 --- a/.env +++ b/.env @@ -1,9 +1,8 @@ RAILS_SERVE_STATIC_FILES=true SITE_URL=https://familylife-gift-cards-stage.cru.org -SESSION_REDIS_DB_INDEX=1 +SESSION_REDIS_DB_INDEX=59 SESSION_REDIS_HOST=localhost SESSION_REDIS_PORT=6379 -STORAGE_REDIS_DB_INDEX=1 +STORAGE_REDIS_DB_INDEX=59 STORAGE_REDIS_HOST=localhost STORAGE_REDIS_PORT=6379 - From 7fa41f72cad60d6b4890ef30473a7fc2f274c1b9 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Wed, 11 Dec 2024 15:38:17 -0500 Subject: [PATCH 38/73] Update .github/workflows/build-deploy-ecs.yml Use main instead of master Co-authored-by: Mark Knutsen --- .github/workflows/build-deploy-ecs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-deploy-ecs.yml b/.github/workflows/build-deploy-ecs.yml index 9a90cb6..398f339 100644 --- a/.github/workflows/build-deploy-ecs.yml +++ b/.github/workflows/build-deploy-ecs.yml @@ -3,7 +3,7 @@ name: Build & Deploy ECS on: push: branches: - # Automatically build and deploy master and staging. Additional branches may be added. + # Automatically build and deploy main and staging. Additional branches may be added. # - main - staging workflow_dispatch: From 6edaf6c025839eb60190ce15cdf884ca512a6e97 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Wed, 11 Dec 2024 16:08:26 -0500 Subject: [PATCH 39/73] Update Dockerfile Use RAILS_ENV var and rails instead of rake as binary Co-authored-by: Mark Knutsen --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9babd15..e5b7a08 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,7 +47,7 @@ ARG SECRET_KEY_BASE=asdf ARG SITE_URL=asdf # Compile assets -RUN RAILS_ENV=production bundle exec rake assets:clobber assets:precompile \ +RUN RAILS_ENV=$RAILS_ENV bundle exec rails assets:clobber assets:precompile \ && chown -R webapp:webapp /home/webapp/ # Define volumes used by ECS to share public html and extra nginx config with nginx container From 476f170c314890ee68d2fa9e55e61ab9f3fe1325 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Wed, 11 Dec 2024 17:28:03 -0500 Subject: [PATCH 40/73] various tweaks as per code review --- Dockerfile | 2 -- Gemfile | 6 +++++- Gemfile.lock | 13 ++++++++++++- config/application.rb | 7 +++++++ lib/log/logger.rb | 22 ++++++++++++++++++++++ lib/log/logger/formatter.rb | 28 ++++++++++++++++++++++++++++ lib/log/logger/formatter_readable.rb | 14 ++++++++++++++ 7 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 lib/log/logger.rb create mode 100644 lib/log/logger/formatter.rb create mode 100644 lib/log/logger/formatter_readable.rb diff --git a/Dockerfile b/Dockerfile index 9babd15..172444c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,7 +22,6 @@ COPY Gemfile Gemfile.lock ./ # Install bundler version which created the lock file and configure it ARG SIDEKIQ_CREDS -ENV LD_LIBRARY_PATH /usr/lib/instantclient RUN gem install bundler -v $(awk '/^BUNDLED WITH/ { getline; print $1; exit }' Gemfile.lock) \ && bundle config --global gems.contribsys.com $SIDEKIQ_CREDS @@ -36,7 +35,6 @@ COPY . . # Environment required to build the application ARG RAILS_ENV=production -ARG RAILS_ENV=production ARG SESSION_REDIS_DB_INDEX=1 ARG SESSION_REDIS_HOST=localhost ARG SESSION_REDIS_PORT=6379 diff --git a/Gemfile b/Gemfile index 7a69d00..f5dfb00 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,7 @@ source "https://rubygems.org" +ruby file: ".ruby-version" + # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" gem "rails", "~> 7.2.2" # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] @@ -7,7 +9,6 @@ gem "sprockets-rails" gem "sassc-rails" gem "pg" -gem "dotenv" gem "sidekiq", "~> 6.5" source "https://gems.contribsys.com/" do @@ -82,3 +83,6 @@ gem "aasm" gem "activerecord-import" gem "dogstatsd-ruby", "~> 5.3" + +gem "ougai", "~> 1.7" +gem "amazing_print" diff --git a/Gemfile.lock b/Gemfile.lock index 7fccc45..0257eae 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -103,6 +103,7 @@ GEM tzinfo (~> 2.0, >= 2.0.5) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) + amazing_print (1.6.0) arbre (1.7.0) activesupport (>= 3.0.0) ruby2_keywords (>= 0.0.2) @@ -248,6 +249,12 @@ GEM racc (~> 1.4) nokogiri (1.16.7-x86_64-linux) racc (~> 1.4) + oj (3.16.7) + bigdecimal (>= 3.0) + ostruct (>= 0.2) + ostruct (0.6.1) + ougai (1.9.1) + oj (~> 3.10) parallel (1.26.3) parser (3.3.6.0) ast (~> 2.4.1) @@ -468,15 +475,16 @@ DEPENDENCIES activeadmin (~> 3.2) activeadmin_addons activerecord-import + amazing_print bootsnap brakeman capybara debug dogstatsd-ruby (~> 5.3) - dotenv dotenv-rails importmap-rails jbuilder + ougai (~> 1.7) pg pry-byebug pry-remote @@ -499,5 +507,8 @@ DEPENDENCIES tzinfo-data web-console +RUBY VERSION + ruby 3.3.6p108 + BUNDLED WITH 2.5.22 diff --git a/config/application.rb b/config/application.rb index 40daa3f..3b31bc9 100644 --- a/config/application.rb +++ b/config/application.rb @@ -6,6 +6,7 @@ # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) +require_relative '../lib/log/logger' module FamilylifeGiftCards class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. @@ -27,5 +28,11 @@ class Application < Rails::Application # # config.time_zone = "Central Time (US & Canada)" # config.eager_load_paths << Rails.root.join("extras") + + # Use sidekiq for active_job + config.active_job.queue_adapter = :sidekiq + + # Send all logs to stdout, which docker reads and sends to datadog. + config.logger = Log::Logger.new($stdout) unless Rails.env.test? # we don't need a logger in test env end end diff --git a/lib/log/logger.rb b/lib/log/logger.rb new file mode 100644 index 0000000..ad54128 --- /dev/null +++ b/lib/log/logger.rb @@ -0,0 +1,22 @@ +require "ougai" +require File.expand_path("logger/formatter", __dir__) +require File.expand_path("logger/formatter_readable", __dir__) +module Log + class Logger < Ougai::Logger + include ActiveSupport::LoggerThreadSafeLevel + include ActiveSupport::LoggerSilence + + def initialize(*args) + @readable = args[0] == $stdout + super + end + + def create_formatter + if ENV["AWS_EXECUTION_ENV"].present? + Log::Logger::Formatter.new(ENV["PROJECT_NAME"]) + else + Log::Logger::FormatterReadable.new($stdout) + end + end + end +end diff --git a/lib/log/logger/formatter.rb b/lib/log/logger/formatter.rb new file mode 100644 index 0000000..a78fb05 --- /dev/null +++ b/lib/log/logger/formatter.rb @@ -0,0 +1,28 @@ +module Log + class Logger < Ougai::Logger + class Formatter < Ougai::Formatters::Bunyan + def initialize(*args) + super + end + + def _call(severity, time, progname, data) + request = data.delete(:request) + if request + data[:network] = {client: {ip: request.ip}} + data[:amzn_trace_id] = request.headers["X-Amzn-Trace-Id"] + data[:request_id] = request.uuid + end + + dump({ + :name => progname || @app_name, + :host => @hostname, + :level => severity, + :time => time, + :env => Rails.env, + "dd.trace_id" => Datadog::Tracing.correlation.trace_id, + "dd.span_id" => Datadog::Tracing.correlation.span_id + }.merge(data)) + end + end + end +end diff --git a/lib/log/logger/formatter_readable.rb b/lib/log/logger/formatter_readable.rb new file mode 100644 index 0000000..b2b204f --- /dev/null +++ b/lib/log/logger/formatter_readable.rb @@ -0,0 +1,14 @@ +module Log + class Logger < Ougai::Logger + class FormatterReadable < Ougai::Formatters::Readable + def initialize(*args) + super + end + + def _call(severity, time, progname, data) + data.delete(:request) + super(severity, time, progname, data) + end + end + end +end From 7009d7f6c17c9ff7d08e80007817fd725f1a6157 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Wed, 11 Dec 2024 17:31:05 -0500 Subject: [PATCH 41/73] keep session for 30 days --- config/initializers/session_store.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index d9b5ca1..9af0b6b 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -4,4 +4,4 @@ redis_conf = YAML.safe_load(ERB.new(File.read(Rails.root.join("config", "redis.yml"))).result, permitted_classes: [Symbol], aliases: true)["session"] -Rails.application.config.session_store :redis_store, servers: [redis_conf], expire_after: 2.hours +Rails.application.config.session_store :redis_store, servers: [redis_conf], expire_after: 30.days From 54974ec7a1fad9b65819c915ae7b333cd5fa1356 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Wed, 11 Dec 2024 17:34:03 -0500 Subject: [PATCH 42/73] make sessions also last 30 days --- config/redis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/redis.yml b/config/redis.yml index 1d81561..8204ebe 100644 --- a/config/redis.yml +++ b/config/redis.yml @@ -11,7 +11,7 @@ session: &SESSION :host: <%= ENV.fetch('SESSION_REDIS_HOST') %> :port: <%= ENV.fetch('SESSION_REDIS_PORT', 6379) %> :db: <%= ENV.fetch('SESSION_REDIS_DB_INDEX') %> - :expires_in: <%= 2.hours %> + :expires_in: <%= 30.days %> cache: <<: *SESSION From 90955d179cec74e23c78ab0cba2d88836362f91b Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Wed, 11 Dec 2024 18:03:31 -0500 Subject: [PATCH 43/73] Update app/models/issuance.rb fix typo Co-authored-by: Mark Knutsen --- app/models/issuance.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/issuance.rb b/app/models/issuance.rb index a37255e..f67f921 100644 --- a/app/models/issuance.rb +++ b/app/models/issuance.rb @@ -102,7 +102,7 @@ def largest_existing_number_in_certificate # look for all numbers that match numbering existing_matching_certificates = GiftCard.all.where("certificate ~* ?", numbering_regex_str).pluck(:certificate) - # pulling all allocated certificate ids instead of a rergex isn't ideal, but there shouldn't be many, if any, times there + # pulling all allocated certificate ids instead of a regex isn't ideal, but there shouldn't be many, if any, times there # are previewed gift cards issuances while another one is being previewed existing_matching_certificates += Issuance.preview.pluck(:allocated_certificates).collect do |allocated_certificates| allocated_certificates.split(", ").find_all { |certificate| certificate =~ numbering_regex } From f83024cad9f14fef38b0008e61fec7230ccc2c08 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Thu, 12 Dec 2024 14:43:20 -0500 Subject: [PATCH 44/73] use issuing as state name instead of issued; various other code review tweaks --- .env | 1 + app/admin/issuances.rb | 6 ++--- app/models/gift_card_type.rb | 2 +- app/models/issuance.rb | 43 +++++++----------------------------- config/routes.rb | 6 ++--- lib/has_numbering.rb | 2 +- 6 files changed, 16 insertions(+), 44 deletions(-) diff --git a/.env b/.env index f1d1c42..b287d04 100644 --- a/.env +++ b/.env @@ -6,3 +6,4 @@ SESSION_REDIS_PORT=6379 STORAGE_REDIS_DB_INDEX=59 STORAGE_REDIS_HOST=localhost STORAGE_REDIS_PORT=6379 +SECRET_KEY_BASE=secret diff --git a/app/admin/issuances.rb b/app/admin/issuances.rb index 07c1a2b..99d2916 100644 --- a/app/admin/issuances.rb +++ b/app/admin/issuances.rb @@ -26,10 +26,10 @@ number_column :card_amount, as: :currency, unit: "$" column :quantity column :min_certificate do |issuance| - issuance.allocated_certificates.split(", ").first + issuance.allocated_certificates.split(Issuance::CERTIFICATE_DISPLAY_SEPARATOR).first end column :max_certificate do |issuance| - issuance.allocated_certificates.split(", ").last + issuance.allocated_certificates.split(Issuance::CERTIFICATE_DISPLAY_SEPARATOR).last end column :begin_use_date column :end_use_date @@ -71,7 +71,7 @@ end =end - action_item :issue, only: :show, if: -> { resource.preview? } do + action_item :issue, only: :show, if: -> { resource.previewing? } do link_to 'Issue Gift Cards', issue_admin_issuance_path(issuance), method: :put, data: { confirm: 'Are you sure?' } end diff --git a/app/models/gift_card_type.rb b/app/models/gift_card_type.rb index 797c103..1ffe0ca 100644 --- a/app/models/gift_card_type.rb +++ b/app/models/gift_card_type.rb @@ -1,7 +1,7 @@ class GiftCardType < ApplicationRecord include HasNumbering - validates :numbering, presence: true, format: { with: /\d*x+\d*/ } + validates :numbering, presence: true, format: { with: /^[^x]*x+[^x]*$/ } def to_s label diff --git a/app/models/issuance.rb b/app/models/issuance.rb index a37255e..41148f5 100644 --- a/app/models/issuance.rb +++ b/app/models/issuance.rb @@ -1,33 +1,6 @@ -=begin - create_table "gift_cards", force: :cascade do |t| - t.integer "certificate" - t.datetime "expiration_date" - t.integer "registrations_available" - t.string "associated_product" - t.decimal "certificate_value" - t.string "gl_code" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "issuances", force: :cascade do |t| - t.string "status" - t.integer "initiator_id" - t.decimal "card_amount" - t.integer "quantity" - t.datetime "begin_use_date" - t.datetime "end_use_date" - t.datetime "expiration_date" - t.integer "gift_card_type_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.text "allocated_certificates" - t.string "numbering" - end - -=end - class Issuance < ApplicationRecord + CERTIFICATE_DISPLAY_SEPARATOR = ", " + include AASM include HasNumbering @@ -40,12 +13,12 @@ class Issuance < ApplicationRecord validates :quantity, numericality: { only_integer: true } aasm column: :status do - state :configure, initial: true - state :preview + state :configuring, initial: true + state :previewing state :issued event :preview do - transitions from: :configure, to: :preview + transitions from: :configuring, to: :previewing end event :issue do @@ -75,7 +48,7 @@ def to_s end def create_gift_cards - allocated_certificates.split(", ").each do |certificate| + allocated_certificates.split(CERTIFICATE_DISPLAY_SEPARATOR).each do |certificate| gift_card = gift_cards.where(certificate: certificate).first_or_initialize gift_card.expiration_date = expiration_date gift_card.gift_card_type = gift_card_type @@ -94,7 +67,7 @@ def set_allocated_certificates next_number += 1 end - self.allocated_certificates = allocated_certificates.join(", ") + self.allocated_certificates = allocated_certificates.join(CERTIFICATE_DISPLAY_SEPARATOR) end # largest number in certificates represented in x's in format, for all certificates matching format @@ -105,7 +78,7 @@ def largest_existing_number_in_certificate # pulling all allocated certificate ids instead of a rergex isn't ideal, but there shouldn't be many, if any, times there # are previewed gift cards issuances while another one is being previewed existing_matching_certificates += Issuance.preview.pluck(:allocated_certificates).collect do |allocated_certificates| - allocated_certificates.split(", ").find_all { |certificate| certificate =~ numbering_regex } + allocated_certificates.split(CERTIFICATE_DISPLAY_SEPARATOR).find_all { |certificate| certificate =~ numbering_regex } end.flatten existing_matching_certificates.collect{ |certificate| diff --git a/config/routes.rb b/config/routes.rb index e1f7d8c..8036f37 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,14 +2,12 @@ ActiveAdmin.routes(self) # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html - # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. - # Can be used by load balancers and uptime monitors to verify that the app is live. - get "up" => "rails/health#show", as: :rails_health_check - # Render dynamic PWA files from app/views/pwa/* get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker get "manifest" => "rails/pwa#manifest", as: :pwa_manifest + # Reveal health status that returns 200 if the app boots with no exceptions, otherwise 500. + # Can be used by load balancers and uptime monitors to verify that the app is live. get "monitors/lb" => "monitors#lb" # Defines the root path route ("/") diff --git a/lib/has_numbering.rb b/lib/has_numbering.rb index 2a2d383..1538fc4 100644 --- a/lib/has_numbering.rb +++ b/lib/has_numbering.rb @@ -1,10 +1,10 @@ module HasNumbering def numbering_regex_str + # add brackets around the x's so that it can be extracted, and use \d instead of x numbering.gsub(/x+/) { |xs| "(#{xs.gsub("x", "\\d")})" } end def numbering_regex - # add brackets around the x's so that it can be extracted, and use \d instead of x @numbering_regex ||= /#{numbering_regex_str}/ end end From c00f5272589e354d7c18ce72fd8b0f5e4ca89f94 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Thu, 12 Dec 2024 15:06:19 -0500 Subject: [PATCH 45/73] import final batch explicitly --- import.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/import.rb b/import.rb index b960a21..84a35a5 100644 --- a/import.rb +++ b/import.rb @@ -134,3 +134,5 @@ batch = [] end end + +GiftCard.import(batch) From 41dbb42d89f510f56087f29bcae1422d2f80e9f7 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Thu, 12 Dec 2024 16:17:50 -0500 Subject: [PATCH 46/73] fix gift card type regex don't filter out certificate column in gift cards --- app/models/gift_card_type.rb | 2 +- app/models/issuance.rb | 2 +- config/initializers/filter_parameter_logging.rb | 2 +- db/schema.rb | 3 +++ 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/models/gift_card_type.rb b/app/models/gift_card_type.rb index 1ffe0ca..2f2f0d6 100644 --- a/app/models/gift_card_type.rb +++ b/app/models/gift_card_type.rb @@ -1,7 +1,7 @@ class GiftCardType < ApplicationRecord include HasNumbering - validates :numbering, presence: true, format: { with: /^[^x]*x+[^x]*$/ } + validates :numbering, presence: true, format: { with: /\A[^x]*x+[^x]*\z/ } def to_s label diff --git a/app/models/issuance.rb b/app/models/issuance.rb index 10df740..0e16be9 100644 --- a/app/models/issuance.rb +++ b/app/models/issuance.rb @@ -77,7 +77,7 @@ def largest_existing_number_in_certificate # pulling all allocated certificate ids instead of a regex isn't ideal, but there shouldn't be many, if any, times there # are previewed gift cards issuances while another one is being previewed - existing_matching_certificates += Issuance.preview.pluck(:allocated_certificates).collect do |allocated_certificates| + existing_matching_certificates += Issuance.previewing.pluck(:allocated_certificates).collect do |allocated_certificates| allocated_certificates.split(CERTIFICATE_DISPLAY_SEPARATOR).find_all { |certificate| certificate =~ numbering_regex } end.flatten diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb index c010b83..9794e5c 100644 --- a/config/initializers/filter_parameter_logging.rb +++ b/config/initializers/filter_parameter_logging.rb @@ -4,5 +4,5 @@ # Use this to limit dissemination of sensitive information. # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. Rails.application.config.filter_parameters += [ - :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn + :passw, :email, :secret, :token, :_key, :crypt, :salt, :otp, :ssn ] diff --git a/db/schema.rb b/db/schema.rb index d858b07..c76fe44 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -12,7 +12,10 @@ ActiveRecord::Schema[7.2].define(version: 2024_11_26_111253) do # These are extensions that must be enabled in order to support this database + enable_extension "pg_stat_statements" + enable_extension "pgcrypto" enable_extension "plpgsql" + enable_extension "uuid-ossp" create_table "active_admin_comments", force: :cascade do |t| t.string "namespace" From 236c71885df047fd535c52ddc3571cda928d7a1d Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Thu, 12 Dec 2024 16:50:09 -0500 Subject: [PATCH 47/73] fix build error by copying ruby-version --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0002ed5..42d9bfc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ RUN apk upgrade --no-cache RUN apk --no-cache add libc6-compat git postgresql-libs tzdata # Copy dependency definitions and lock files -COPY Gemfile Gemfile.lock ./ +COPY Gemfile Gemfile.lock .ruby-version ./ # Install bundler version which created the lock file and configure it ARG SIDEKIQ_CREDS From d3c4e69723907ae289baa710e9ccc94fdcd6baac Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Thu, 12 Dec 2024 17:08:30 -0500 Subject: [PATCH 48/73] add datadog --- Gemfile | 4 ++-- Gemfile.lock | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index f5dfb00..320e967 100644 --- a/Gemfile +++ b/Gemfile @@ -79,10 +79,10 @@ gem "activeadmin", "~> 3.2" gem "activeadmin_addons" gem "aasm" - gem "activerecord-import" - gem "dogstatsd-ruby", "~> 5.3" +gem "ddtrace", "~> 1.4" gem "ougai", "~> 1.7" gem "amazing_print" + diff --git a/Gemfile.lock b/Gemfile.lock index 0257eae..9f08b98 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -140,7 +140,16 @@ GEM tzinfo unicode (>= 0.4.4.5) csv (3.3.0) + datadog-ci (0.8.3) + msgpack date (3.4.0) + ddtrace (1.23.3) + datadog-ci (~> 0.8.1) + debase-ruby_core_source (= 3.3.1) + libdatadog (~> 7.0.0.1.0) + libddwaf (~> 1.14.0.0.0) + msgpack + debase-ruby_core_source (3.3.1) debug (1.9.2) irb (~> 1.10) reline (>= 0.3.8) @@ -211,6 +220,19 @@ GEM kaminari-core (= 1.2.2) kaminari-core (1.2.2) language_server-protocol (3.17.0.3) + libdatadog (7.0.0.1.0) + libdatadog (7.0.0.1.0-aarch64-linux) + libdatadog (7.0.0.1.0-x86_64-linux) + libddwaf (1.14.0.0.0) + ffi (~> 1.0) + libddwaf (1.14.0.0.0-aarch64-linux) + ffi (~> 1.0) + libddwaf (1.14.0.0.0-arm64-darwin) + ffi (~> 1.0) + libddwaf (1.14.0.0.0-x86_64-darwin) + ffi (~> 1.0) + libddwaf (1.14.0.0.0-x86_64-linux) + ffi (~> 1.0) lint_roller (1.1.0) logger (1.6.2) loofah (2.23.1) @@ -479,6 +501,7 @@ DEPENDENCIES bootsnap brakeman capybara + ddtrace (~> 1.4) debug dogstatsd-ruby (~> 5.3) dotenv-rails From cabe7761ffd349351546feabb796927240372f13 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Thu, 12 Dec 2024 17:10:33 -0500 Subject: [PATCH 49/73] more state name change renames Co-authored-by: Mark Knutsen --- app/models/issuance.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/issuance.rb b/app/models/issuance.rb index 0e16be9..37ad270 100644 --- a/app/models/issuance.rb +++ b/app/models/issuance.rb @@ -22,7 +22,7 @@ class Issuance < ApplicationRecord end event :issue do - transitions from: :preview, to: :issued, after: :create_gift_cards + transitions from: :previewing, to: :issued, after: :create_gift_cards transitions from: :issued, to: :issued end end @@ -40,7 +40,7 @@ class Issuance < ApplicationRecord end def to_s - if preview? + if previewing? "Gift Card Issuance (Preview)" elsif issued? "Issuance by #{issuer.full_name} #{created_at}" From 85a727552639b6aaee3d223a32495e19a3cd24c5 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Thu, 12 Dec 2024 17:57:30 -0500 Subject: [PATCH 50/73] use SITE_HOST for hosts allowed --- config/environments/production.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index cc1e822..abc6343 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -83,5 +83,5 @@ # ] # Skip DNS rebinding protection for the default health check endpoint. config.host_authorization = {exclude: ->(request) { request.path == "/monitors/lb" }} - config.hosts << ENV.fetch("SITE_URL") + config.hosts << ENV.fetch("SITE_HOST") end From e970c9bb00523b15d563e719dabd3298a7ca8c13 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Thu, 12 Dec 2024 18:05:49 -0500 Subject: [PATCH 51/73] add SITE_HOST to build --- .env | 1 + Dockerfile | 1 + 2 files changed, 2 insertions(+) diff --git a/.env b/.env index b287d04..a6bdd64 100644 --- a/.env +++ b/.env @@ -1,5 +1,6 @@ RAILS_SERVE_STATIC_FILES=true SITE_URL=https://familylife-gift-cards-stage.cru.org +SITE_HOST=familylife-gift-cards-stage.cru.org SESSION_REDIS_DB_INDEX=59 SESSION_REDIS_HOST=localhost SESSION_REDIS_PORT=6379 diff --git a/Dockerfile b/Dockerfile index 42d9bfc..9f6ec29 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,6 +43,7 @@ ARG STORAGE_REDIS_HOST=localhost ARG STORAGE_REDIS_PORT=6379 ARG SECRET_KEY_BASE=asdf ARG SITE_URL=asdf +ARG SITE_HOST=asdf # Compile assets RUN RAILS_ENV=$RAILS_ENV bundle exec rails assets:clobber assets:precompile \ From 25acefd1134ad488e86702f29d10683334732731 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Thu, 12 Dec 2024 18:40:11 -0500 Subject: [PATCH 52/73] fix crashes; add counts to initial import --- app/admin/issuances.rb | 20 ++++++++++++++++++++ app/models/issuance.rb | 2 +- import.rb | 4 ++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app/admin/issuances.rb b/app/admin/issuances.rb index 99d2916..bf9ec65 100644 --- a/app/admin/issuances.rb +++ b/app/admin/issuances.rb @@ -16,6 +16,21 @@ # permitted # end + filter :creator + filter :issuer + filter :gift_card_type + filter :status + filter :card_amount + filter :quantitiy + filter :begin_use_date + filter :end_use_date + filter :expiration_date + filter :allocated_certificates + filter :numbering + filter :issued_at + filter :created_at + filter :updated_at + index do id_column tag_column :status @@ -31,6 +46,11 @@ column :max_certificate do |issuance| issuance.allocated_certificates.split(Issuance::CERTIFICATE_DISPLAY_SEPARATOR).last end + column :used do |issuance| + used = issuance.gift_cards.where(registrations_available: 0).count + total = issuance.gift_cards.count + raw("#{number_with_delimiter(used)}/#{number_with_delimiter(issuance.gift_cards.count)}
(#{(used / total.to_f * 100).round(1)}%)") + end column :begin_use_date column :end_use_date column :expiration_date diff --git a/app/models/issuance.rb b/app/models/issuance.rb index 0e16be9..1b21454 100644 --- a/app/models/issuance.rb +++ b/app/models/issuance.rb @@ -40,7 +40,7 @@ class Issuance < ApplicationRecord end def to_s - if preview? + if previewing? "Gift Card Issuance (Preview)" elsif issued? "Issuance by #{issuer.full_name} #{created_at}" diff --git a/import.rb b/import.rb index 84a35a5..8fc94aa 100644 --- a/import.rb +++ b/import.rb @@ -136,3 +136,7 @@ end GiftCard.import(batch) + +Issuance.where(quantity: [nil, 0]).each do |i| + i.update(quantity: i.gift_cards.count) +end From 7007fad83e9bb3627b162743c35f21809ddd3552 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Fri, 13 Dec 2024 12:58:49 -0500 Subject: [PATCH 53/73] start api; store copy of some gift card type fields in gift card --- app/admin/issuances.rb | 2 +- .../api/v1/application_controller.rb | 39 +++++++++++++++++++ .../api/v1/gift_cards_controller.rb | 24 ++++++++++++ app/models/gift_card.rb | 3 +- app/models/issuance.rb | 8 +++- ...12235258_add_extra_fields_to_gift_cards.rb | 8 ++++ db/schema.rb | 9 +++-- import.rb | 2 + 8 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 app/controllers/api/v1/application_controller.rb create mode 100644 app/controllers/api/v1/gift_cards_controller.rb create mode 100644 db/migrate/20241212235258_add_extra_fields_to_gift_cards.rb diff --git a/app/admin/issuances.rb b/app/admin/issuances.rb index bf9ec65..532cc83 100644 --- a/app/admin/issuances.rb +++ b/app/admin/issuances.rb @@ -49,7 +49,7 @@ column :used do |issuance| used = issuance.gift_cards.where(registrations_available: 0).count total = issuance.gift_cards.count - raw("#{number_with_delimiter(used)}/#{number_with_delimiter(issuance.gift_cards.count)}
(#{(used / total.to_f * 100).round(1)}%)") + raw("#{number_with_delimiter(used)} / #{number_with_delimiter(issuance.gift_cards.count)}
(#{(used / total.to_f * 100).round(1)}%)") end column :begin_use_date column :end_use_date diff --git a/app/controllers/api/v1/application_controller.rb b/app/controllers/api/v1/application_controller.rb new file mode 100644 index 0000000..4c412ce --- /dev/null +++ b/app/controllers/api/v1/application_controller.rb @@ -0,0 +1,39 @@ +class Api::V1::ApplicationController < ApplicationController + before_action :restrict_access + + respond_to :json + + skip_before_action :require_login, raise: false + skip_before_action :check_url + + protected + + def restrict_access + api_key = ApiKey.find_by(access_token: oauth_access_token) + unless api_key + render json: {error: "You either didn't pass in an access token, or the token you did pass in was wrong."}, + status: :unauthorized, + callback: params[:callback] + return false + end + @current_user = api_key.user + true + end + + def current_user + @current_user || "API" + end + + def oauth_access_token + @oauth_access_token ||= (params[:access_token] || oauth_access_token_from_header) + end + + # grabs access_token from header if one is present + def oauth_access_token_from_header + auth_header = request.env["HTTP_AUTHORIZATION"] || "" + match = auth_header.match(/^token\s(.*)/) || auth_header.match(/^Bearer\s(.*)/) + return match[1] if match.present? + + false + end +end diff --git a/app/controllers/api/v1/gift_cards_controller.rb b/app/controllers/api/v1/gift_cards_controller.rb new file mode 100644 index 0000000..93a6b67 --- /dev/null +++ b/app/controllers/api/v1/gift_cards_controller.rb @@ -0,0 +1,24 @@ +class Api::V1::GiftCardsController < Api::V1::ApplicationController + def show + gift_card = GiftCard.find(params[:id]) + + render json: gift_card + end + + # an update automatically means to use up one registration + def update + gift_card = GiftCard.find(params[:id]) + + if gift_card.registrations_available.to_i == 0 + render json: { + error: "No registrations available", + status: 403 + }, status: 403 + return + end + + gift_card.update(registrations_available: registrations_available - 1) + render json: gift_card + end +end + diff --git a/app/models/gift_card.rb b/app/models/gift_card.rb index 1f3336d..1bcaba8 100644 --- a/app/models/gift_card.rb +++ b/app/models/gift_card.rb @@ -3,7 +3,8 @@ class GiftCard < ApplicationRecord belongs_to :gift_card_type def self.ransackable_attributes(auth_object = nil) - %w(certificate expiration_date registrations_available associated_product certificate_value gl_code created_at updated_at issuance_id gift_card_type_id) + %w(certificate expiration_date registrations_available associated_product certificate_value gl_code created_at updated_at + issuance_id gift_card_type_id prod isbn gl_acct department_number) end def self.ransackable_associations(auth_object = nil) diff --git a/app/models/issuance.rb b/app/models/issuance.rb index 1b21454..75dc43a 100644 --- a/app/models/issuance.rb +++ b/app/models/issuance.rb @@ -22,7 +22,7 @@ class Issuance < ApplicationRecord end event :issue do - transitions from: :preview, to: :issued, after: :create_gift_cards + transitions from: :previewing, to: :issued, after: :create_gift_cards transitions from: :issued, to: :issued end end @@ -43,7 +43,7 @@ def to_s if previewing? "Gift Card Issuance (Preview)" elsif issued? - "Issuance by #{issuer.full_name} #{created_at}" + "Issuance by #{issuer.full_name} #{created_at} (#{quantity} @ $#{card_amount})" end end @@ -53,6 +53,10 @@ def create_gift_cards gift_card.expiration_date = expiration_date gift_card.gift_card_type = gift_card_type gift_card.issuance = self + gift_card.prod = gift_card_type.prod + gift_card.isbn = gift_card_type.isbn + gift_card.gl_account = gift_card_type.gl_account + gift_card.department_number = gift_card_type.department_number gift_card.save! end end diff --git a/db/migrate/20241212235258_add_extra_fields_to_gift_cards.rb b/db/migrate/20241212235258_add_extra_fields_to_gift_cards.rb new file mode 100644 index 0000000..3378fe6 --- /dev/null +++ b/db/migrate/20241212235258_add_extra_fields_to_gift_cards.rb @@ -0,0 +1,8 @@ +class AddExtraFieldsToGiftCards < ActiveRecord::Migration[7.2] + def change + add_column :gift_cards, :prod, :string + add_column :gift_cards, :isbn, :string + add_column :gift_cards, :gl_acct, :string + add_column :gift_cards, :department_number, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index c76fe44..bf08dc1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,12 +10,9 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_11_26_111253) do +ActiveRecord::Schema[7.2].define(version: 2024_12_12_235258) do # These are extensions that must be enabled in order to support this database - enable_extension "pg_stat_statements" - enable_extension "pgcrypto" enable_extension "plpgsql" - enable_extension "uuid-ossp" create_table "active_admin_comments", force: :cascade do |t| t.string "namespace" @@ -71,6 +68,10 @@ t.string "gl_code" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "prod" + t.string "isbn" + t.string "gl_acct" + t.string "department_number" end create_table "issuances", force: :cascade do |t| diff --git a/import.rb b/import.rb index 8fc94aa..2e5084b 100644 --- a/import.rb +++ b/import.rb @@ -140,3 +140,5 @@ Issuance.where(quantity: [nil, 0]).each do |i| i.update(quantity: i.gift_cards.count) end + +Issuance.update_column(:status, "issued") From 0567fc9ee505af9e129a21ee28e60f2aff2a4777 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Fri, 13 Dec 2024 14:46:25 -0500 Subject: [PATCH 54/73] set more values on issuance create gift cards --- app/models/issuance.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/models/issuance.rb b/app/models/issuance.rb index 75dc43a..f028980 100644 --- a/app/models/issuance.rb +++ b/app/models/issuance.rb @@ -50,13 +50,14 @@ def to_s def create_gift_cards allocated_certificates.split(CERTIFICATE_DISPLAY_SEPARATOR).each do |certificate| gift_card = gift_cards.where(certificate: certificate).first_or_initialize + gift_card.registrations_available = 2 + gift_card.certificate_value = card_amount gift_card.expiration_date = expiration_date gift_card.gift_card_type = gift_card_type gift_card.issuance = self - gift_card.prod = gift_card_type.prod gift_card.isbn = gift_card_type.isbn - gift_card.gl_account = gift_card_type.gl_account - gift_card.department_number = gift_card_type.department_number + gift_card.associated_product = gift_card_type.prod + gift_card.gl_code = "#{gift_card_type.gl_acct}.#{gift_card_type.department_number.to_s.gsub("-", ".")}" gift_card.save! end end From f01aed79f60ec2cb1f3c99683a06a73094396062 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Fri, 13 Dec 2024 14:47:04 -0500 Subject: [PATCH 55/73] turns out some new columns were redundant --- ...41212235258_add_extra_fields_to_gift_cards.rb | 8 -------- db/schema.rb | 9 ++++----- import.rb | 16 +++++++++------- 3 files changed, 13 insertions(+), 20 deletions(-) delete mode 100644 db/migrate/20241212235258_add_extra_fields_to_gift_cards.rb diff --git a/db/migrate/20241212235258_add_extra_fields_to_gift_cards.rb b/db/migrate/20241212235258_add_extra_fields_to_gift_cards.rb deleted file mode 100644 index 3378fe6..0000000 --- a/db/migrate/20241212235258_add_extra_fields_to_gift_cards.rb +++ /dev/null @@ -1,8 +0,0 @@ -class AddExtraFieldsToGiftCards < ActiveRecord::Migration[7.2] - def change - add_column :gift_cards, :prod, :string - add_column :gift_cards, :isbn, :string - add_column :gift_cards, :gl_acct, :string - add_column :gift_cards, :department_number, :string - end -end diff --git a/db/schema.rb b/db/schema.rb index bf08dc1..c76fe44 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,9 +10,12 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_12_12_235258) do +ActiveRecord::Schema[7.2].define(version: 2024_11_26_111253) do # These are extensions that must be enabled in order to support this database + enable_extension "pg_stat_statements" + enable_extension "pgcrypto" enable_extension "plpgsql" + enable_extension "uuid-ossp" create_table "active_admin_comments", force: :cascade do |t| t.string "namespace" @@ -68,10 +71,6 @@ t.string "gl_code" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.string "prod" - t.string "isbn" - t.string "gl_acct" - t.string "department_number" end create_table "issuances", force: :cascade do |t| diff --git a/import.rb b/import.rb index 2e5084b..736a23e 100644 --- a/import.rb +++ b/import.rb @@ -99,6 +99,10 @@ issuance.card_amount = 0 issuance.quantity = 0 issuance.save! + + issuance.update_column(:status, "issued") + issuance = Issuance.find(issuance.id) + [ gct, issuance ] end.to_h @@ -121,11 +125,11 @@ gc.gift_card_type = gct gc.expiration_date = DateTime.parse(row["expirationDate"]) gc.registrations_available = row["numberRegistrations"].to_i - gc.certificate_value = row["certificateValue"].to_d - gc.gl_code = row["glCode"].to_d - gc.created_at = row["addDate"].to_d - gc.updated_at = row["modifiedDate"].to_d - gc.associated_product = row["associatedProduct"].to_d + gc.certificate_value = row["certificateValue"] + gc.gl_code = row["glCode"] + gc.created_at = DateTime.parse(row["addDate"]) if row["created_at"] + gc.updated_at = DateTime.parse(row["modifiedDate"]) if row["modifiedDate"] + gc.associated_product = row["associatedProduct"] batch << gc @@ -140,5 +144,3 @@ Issuance.where(quantity: [nil, 0]).each do |i| i.update(quantity: i.gift_cards.count) end - -Issuance.update_column(:status, "issued") From fd9cd066a4e22e857e3f3237cbec5dbc867f6e7e Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Fri, 13 Dec 2024 14:52:51 -0500 Subject: [PATCH 56/73] certificate value should be converted to decimal --- import.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/import.rb b/import.rb index 736a23e..6348aa5 100644 --- a/import.rb +++ b/import.rb @@ -125,7 +125,7 @@ gc.gift_card_type = gct gc.expiration_date = DateTime.parse(row["expirationDate"]) gc.registrations_available = row["numberRegistrations"].to_i - gc.certificate_value = row["certificateValue"] + gc.certificate_value = row["certificateValue"].to_d gc.gl_code = row["glCode"] gc.created_at = DateTime.parse(row["addDate"]) if row["created_at"] gc.updated_at = DateTime.parse(row["modifiedDate"]) if row["modifiedDate"] From 31589b31faad4d661d57dffbbfdbb40d4e836a44 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Fri, 13 Dec 2024 15:10:44 -0500 Subject: [PATCH 57/73] hide skip_before_actions that don't exist for now --- app/controllers/api/v1/application_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/application_controller.rb b/app/controllers/api/v1/application_controller.rb index 4c412ce..1f136f4 100644 --- a/app/controllers/api/v1/application_controller.rb +++ b/app/controllers/api/v1/application_controller.rb @@ -3,8 +3,8 @@ class Api::V1::ApplicationController < ApplicationController respond_to :json - skip_before_action :require_login, raise: false - skip_before_action :check_url + #skip_before_action :require_login, raise: false + #skip_before_action :check_url protected From e67817c7987b9c89300551b4221784947773c432 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Fri, 13 Dec 2024 15:51:49 -0500 Subject: [PATCH 58/73] improve gift card admin table --- app/admin/gift_cards.rb | 24 +++++++++++++++++++ .../20241213203609_add_isbn_to_gift_cards.rb | 5 ++++ db/schema.rb | 3 ++- 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20241213203609_add_isbn_to_gift_cards.rb diff --git a/app/admin/gift_cards.rb b/app/admin/gift_cards.rb index 5de3599..77cbd6b 100644 --- a/app/admin/gift_cards.rb +++ b/app/admin/gift_cards.rb @@ -16,4 +16,28 @@ # end actions :all, :except => [:new] + + index do + selectable_column + id_column + column :gift_card_type + column :issuance + column :certificate + number_column "Value", :certificate_value, as: :currency, unit: "$", sortable: :certificate_value + #column "Value", :certificate_value do |gift_card| + # number_to_currency(gift_card.certificate_value) + #end + column :expiration_date + column "Reg'ns Avail" do |gift_card| + gift_card.registrations_available + end + column "Assoc'd Prod", :associated_product, sortable: :associated_product do |gift_card| + gift_card.associated_product + end + column :gl_code + column :isbn + column :created_at + column :updated_at + actions + end end diff --git a/db/migrate/20241213203609_add_isbn_to_gift_cards.rb b/db/migrate/20241213203609_add_isbn_to_gift_cards.rb new file mode 100644 index 0000000..a2a4196 --- /dev/null +++ b/db/migrate/20241213203609_add_isbn_to_gift_cards.rb @@ -0,0 +1,5 @@ +class AddIsbnToGiftCards < ActiveRecord::Migration[7.2] + def change + add_column :gift_cards, :isbn, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index c76fe44..219d1d1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_11_26_111253) do +ActiveRecord::Schema[7.2].define(version: 2024_12_13_203609) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "pgcrypto" @@ -71,6 +71,7 @@ t.string "gl_code" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "isbn" end create_table "issuances", force: :cascade do |t| From bb2ab5566cf25feceed76de2a8ef22c21b4f5fd4 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Fri, 13 Dec 2024 15:58:28 -0500 Subject: [PATCH 59/73] remove prod and gl_account since they're redundant after all --- app/models/gift_card.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/gift_card.rb b/app/models/gift_card.rb index 1bcaba8..b0ee6bd 100644 --- a/app/models/gift_card.rb +++ b/app/models/gift_card.rb @@ -4,7 +4,7 @@ class GiftCard < ApplicationRecord def self.ransackable_attributes(auth_object = nil) %w(certificate expiration_date registrations_available associated_product certificate_value gl_code created_at updated_at - issuance_id gift_card_type_id prod isbn gl_acct department_number) + issuance_id gift_card_type_id isbn department_number) end def self.ransackable_associations(auth_object = nil) From 9c55aa9a397404c3d853f01cd513eff5b531396b Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Fri, 13 Dec 2024 16:08:54 -0500 Subject: [PATCH 60/73] fix some issues with issuing new cards --- app/admin/gift_cards.rb | 2 +- app/models/gift_card.rb | 2 ++ app/models/issuance.rb | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/admin/gift_cards.rb b/app/admin/gift_cards.rb index 77cbd6b..4ead4e7 100644 --- a/app/admin/gift_cards.rb +++ b/app/admin/gift_cards.rb @@ -5,7 +5,7 @@ # # Uncomment all parameters which should be permitted for assignment # - # permit_params :expiration_date, :registrations_available, :associated_product, :certificate_value, :gl_code, :certificate + permit_params :issuance_id, :gift_card_type_id, :certificate, :expiration_date, :registrations_available, :associated_product, :certificate_value, :gl_code, :created_at, :updated_at, :isbn # # or # diff --git a/app/models/gift_card.rb b/app/models/gift_card.rb index b0ee6bd..1c8896b 100644 --- a/app/models/gift_card.rb +++ b/app/models/gift_card.rb @@ -2,6 +2,8 @@ class GiftCard < ApplicationRecord belongs_to :issuance belongs_to :gift_card_type + validates :certificate, uniqueness: true + def self.ransackable_attributes(auth_object = nil) %w(certificate expiration_date registrations_available associated_product certificate_value gl_code created_at updated_at issuance_id gift_card_type_id isbn department_number) diff --git a/app/models/issuance.rb b/app/models/issuance.rb index f028980..e6154f2 100644 --- a/app/models/issuance.rb +++ b/app/models/issuance.rb @@ -56,8 +56,8 @@ def create_gift_cards gift_card.gift_card_type = gift_card_type gift_card.issuance = self gift_card.isbn = gift_card_type.isbn - gift_card.associated_product = gift_card_type.prod - gift_card.gl_code = "#{gift_card_type.gl_acct}.#{gift_card_type.department_number.to_s.gsub("-", ".")}" + gift_card.associated_product = gift_card_type.prod_id + gift_card.gl_code = [gift_card_type.gl_acct, gift_card_type.department_number.to_s.gsub("-", ".")].compact.join(".") gift_card.save! end end From 2726c67c73ecbda26d441f32a3f7a0bbbe274e22 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Fri, 13 Dec 2024 16:41:31 -0500 Subject: [PATCH 61/73] get basic api working --- app/admin/api_keys.rb | 20 +++++++++++++++++++ .../api/v1/application_controller.rb | 1 + .../api/v1/gift_cards_controller.rb | 8 ++++---- app/models/api_key.rb | 17 ++++++++++++++++ config/routes.rb | 12 ++++++----- 5 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 app/admin/api_keys.rb diff --git a/app/admin/api_keys.rb b/app/admin/api_keys.rb new file mode 100644 index 0000000..05ba062 --- /dev/null +++ b/app/admin/api_keys.rb @@ -0,0 +1,20 @@ +ActiveAdmin.register ApiKey do + + # See permitted parameters documentation: + # https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters + # + # Uncomment all parameters which should be permitted for assignment + # + # + permit_params :access_token, :user + + # + # or + # + # permit_params do + # permitted = [:access_token, :user] + # permitted << :other if params[:action] == 'create' && current_user.admin? + # permitted + # end + +end diff --git a/app/controllers/api/v1/application_controller.rb b/app/controllers/api/v1/application_controller.rb index 1f136f4..8168cb9 100644 --- a/app/controllers/api/v1/application_controller.rb +++ b/app/controllers/api/v1/application_controller.rb @@ -3,6 +3,7 @@ class Api::V1::ApplicationController < ApplicationController respond_to :json + skip_forgery_protection #skip_before_action :require_login, raise: false #skip_before_action :check_url diff --git a/app/controllers/api/v1/gift_cards_controller.rb b/app/controllers/api/v1/gift_cards_controller.rb index 93a6b67..f36a07e 100644 --- a/app/controllers/api/v1/gift_cards_controller.rb +++ b/app/controllers/api/v1/gift_cards_controller.rb @@ -1,15 +1,15 @@ class Api::V1::GiftCardsController < Api::V1::ApplicationController def show - gift_card = GiftCard.find(params[:id]) + gift_card = GiftCard.find_by(certificate: params[:id]) render json: gift_card end # an update automatically means to use up one registration def update - gift_card = GiftCard.find(params[:id]) + gift_card = GiftCard.find_by(certificate: params[:id]) - if gift_card.registrations_available.to_i == 0 + if gift_card.registrations_available.to_i <= 0 render json: { error: "No registrations available", status: 403 @@ -17,7 +17,7 @@ def update return end - gift_card.update(registrations_available: registrations_available - 1) + gift_card.update(registrations_available: gift_card.registrations_available - 1) render json: gift_card end end diff --git a/app/models/api_key.rb b/app/models/api_key.rb index f44edba..8f995de 100644 --- a/app/models/api_key.rb +++ b/app/models/api_key.rb @@ -1,2 +1,19 @@ class ApiKey < ApplicationRecord + before_create :generate_access_token + after_initialize :generate_access_token + + def self.ransackable_attributes(auth_object = nil) + ["access_token", "created_at", "id", "id_value", "updated_at", "user"] + end + + private + + def generate_access_token + return if !new_record? || access_token.present? + + loop do + self.access_token ||= SecureRandom.hex + break unless self.class.exists?(access_token: access_token) + end + end end diff --git a/config/routes.rb b/config/routes.rb index 8036f37..eb4e250 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,14 +2,16 @@ ActiveAdmin.routes(self) # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html - # Render dynamic PWA files from app/views/pwa/* - get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker - get "manifest" => "rails/pwa#manifest", as: :pwa_manifest - # Reveal health status that returns 200 if the app boots with no exceptions, otherwise 500. # Can be used by load balancers and uptime monitors to verify that the app is live. get "monitors/lb" => "monitors#lb" + namespace :api do + namespace :v1 do + resources :gift_cards, only: [:show, :update] + end + end + # Defines the root path route ("/") - # root "posts#index" + get "/", to: redirect("/admin") end From 92ce1eecdbf5af0de888fea9ff59b10904bc5be6 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Tue, 7 Jan 2025 01:12:08 -0500 Subject: [PATCH 62/73] switch to batches db model take out dynamic numbering and use static algorithm Mona gave --- app/admin/{gift_card_types.rb => batches.rb} | 6 +- app/admin/issuances.rb | 7 +- app/models/batch.rb | 18 ++ app/models/gift_card.rb | 20 +- app/models/gift_card_type.rb | 17 -- app/models/issuance.rb | 26 +-- .../20241126092131_create_gift_cards.rb | 4 +- db/migrate/20241126110902_create_batches.rb | 24 +++ .../20241126110902_create_gift_card_types.rb | 15 -- db/migrate/20241126111253_create_issuances.rb | 7 +- db/schema.rb | 39 ++-- import.rb | 204 ++++++++++-------- lib/has_numbering.rb | 10 - 13 files changed, 223 insertions(+), 174 deletions(-) rename app/admin/{gift_card_types.rb => batches.rb} (70%) create mode 100644 app/models/batch.rb delete mode 100644 app/models/gift_card_type.rb create mode 100644 db/migrate/20241126110902_create_batches.rb delete mode 100644 db/migrate/20241126110902_create_gift_card_types.rb delete mode 100644 lib/has_numbering.rb diff --git a/app/admin/gift_card_types.rb b/app/admin/batches.rb similarity index 70% rename from app/admin/gift_card_types.rb rename to app/admin/batches.rb index 68dcf3c..e161c0c 100644 --- a/app/admin/gift_card_types.rb +++ b/app/admin/batches.rb @@ -1,11 +1,13 @@ -ActiveAdmin.register GiftCardType do +ActiveAdmin.register Batch do # See permitted parameters documentation: # https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters # # Uncomment all parameters which should be permitted for assignment # - permit_params :label, :numbering, :contact, :prod_id, :isbn, :gl_acct, :department_number + permit_params :description, :gift_card_type, :price, :registrations_available, :begin_use_date, :end_use_date, :expiration_date, + :associated_product, :isbn, :gl_code, :dept + # # or # diff --git a/app/admin/issuances.rb b/app/admin/issuances.rb index 532cc83..753bd16 100644 --- a/app/admin/issuances.rb +++ b/app/admin/issuances.rb @@ -34,17 +34,18 @@ index do id_column tag_column :status + column :batch column :created_at column :gift_card_type column :creator column :issuer - number_column :card_amount, as: :currency, unit: "$" + number_column :price, as: :currency, unit: "$" column :quantity column :min_certificate do |issuance| - issuance.allocated_certificates.split(Issuance::CERTIFICATE_DISPLAY_SEPARATOR).first + issuance.allocated_certificates.to_s.split(Issuance::CERTIFICATE_DISPLAY_SEPARATOR).first end column :max_certificate do |issuance| - issuance.allocated_certificates.split(Issuance::CERTIFICATE_DISPLAY_SEPARATOR).last + issuance.allocated_certificates.to_s.split(Issuance::CERTIFICATE_DISPLAY_SEPARATOR).last end column :used do |issuance| used = issuance.gift_cards.where(registrations_available: 0).count diff --git a/app/models/batch.rb b/app/models/batch.rb new file mode 100644 index 0000000..e3e7b84 --- /dev/null +++ b/app/models/batch.rb @@ -0,0 +1,18 @@ +class Batch < ApplicationRecord + + validates_presence_of :description, :gift_card_type, :price, :registrations_available, :begin_use_date, :end_use_date, :expiration_date, + :associated_product, :isbn + validates :gl_code, :dept, presence: true, if: -> { gift_card_type == GiftCard::TYPE_PAID_FULL_PRICE } + + def to_s + description + end + + def self.ransackable_attributes(auth_object = nil) + %w(description gift_card_type price registrations_available begin_use_date end_use_date expiration_date associated_product isbn gl_code dept contact) + end + + def self.ransackable_associations(auth_object = nil) + %w(issuances) + end +end diff --git a/app/models/gift_card.rb b/app/models/gift_card.rb index 1c8896b..c577e0f 100644 --- a/app/models/gift_card.rb +++ b/app/models/gift_card.rb @@ -1,15 +1,27 @@ class GiftCard < ApplicationRecord + TYPE_PAID_HALF_PRICE = "paid_half_price" + TYPE_PAID_FULL_PRICE = "paid_full_price" + TYPE_PAID_OTHER = "paid_other" + TYPE_DEPT = "type_dept" + + TYPE_DESCRIPTIONS = { + TYPE_PAID_HALF_PRICE => "Paid, Half Type", + TYPE_PAID_FULL_PRICE => "Paid, Full Type", + TYPE_PAID_OTHER => "Paid, Other", + TYPE_DEPT => "Paid, Department" + } + belongs_to :issuance - belongs_to :gift_card_type + belongs_to :batch validates :certificate, uniqueness: true def self.ransackable_attributes(auth_object = nil) - %w(certificate expiration_date registrations_available associated_product certificate_value gl_code created_at updated_at - issuance_id gift_card_type_id isbn department_number) + %w(certificate expiration_date registrations_available associated_product certificate_value gl_code created_at updated_at + issuance_id gift_card_type_id isbn department_number batch batch_id price gift_card_type) end def self.ransackable_associations(auth_object = nil) - %w(issuance issuance_id gift_card_type gift_card_type_id) + %w(batch batch_id issuance issuance_id gift_card_type_id) end end diff --git a/app/models/gift_card_type.rb b/app/models/gift_card_type.rb deleted file mode 100644 index 2f2f0d6..0000000 --- a/app/models/gift_card_type.rb +++ /dev/null @@ -1,17 +0,0 @@ -class GiftCardType < ApplicationRecord - include HasNumbering - - validates :numbering, presence: true, format: { with: /\A[^x]*x+[^x]*\z/ } - - def to_s - label - end - - def self.ransackable_attributes(auth_object = nil) - [:label, :numbering, :contact, :prod_id, :isbn, :gl_acct, :department_number] - end - - def self.ransackable_associations(auth_object = nil) - [] - end -end diff --git a/app/models/issuance.rb b/app/models/issuance.rb index e6154f2..eee4809 100644 --- a/app/models/issuance.rb +++ b/app/models/issuance.rb @@ -2,15 +2,15 @@ class Issuance < ApplicationRecord CERTIFICATE_DISPLAY_SEPARATOR = ", " include AASM - include HasNumbering belongs_to :creator, class_name: "Person" belongs_to :issuer, class_name: "Person", optional: true - belongs_to :gift_card_type + belongs_to :batch has_many :gift_cards - validates :card_amount, numericality: true validates :quantity, numericality: { only_integer: true } + delegate :price, to: :batch + delegate :gift_card_type, to: :batch aasm column: :status do state :configuring, initial: true @@ -27,14 +27,6 @@ class Issuance < ApplicationRecord end end - before_save do - unless issued? - # keep a copy of numbering for posterity if still previewing - self.numbering = gift_card_type.numbering - set_allocated_certificates - end - end - after_create do preview! end @@ -43,7 +35,7 @@ def to_s if previewing? "Gift Card Issuance (Preview)" elsif issued? - "Issuance by #{issuer.full_name} #{created_at} (#{quantity} @ $#{card_amount})" + "Issuance by #{issuer.full_name} #{created_at} (#{quantity} @ $#{price})" end end @@ -51,7 +43,7 @@ def create_gift_cards allocated_certificates.split(CERTIFICATE_DISPLAY_SEPARATOR).each do |certificate| gift_card = gift_cards.where(certificate: certificate).first_or_initialize gift_card.registrations_available = 2 - gift_card.certificate_value = card_amount + gift_card.price = price gift_card.expiration_date = expiration_date gift_card.gift_card_type = gift_card_type gift_card.issuance = self @@ -67,7 +59,7 @@ def set_allocated_certificates allocated_certificates = [] quantity.times do - certificate = numbering.gsub(/x+/) { |xs| next_number.to_s.rjust(xs.length, "0") } + certificate = "#{price.to_s.rjust(3, "0")}#{next_number.to_s.rjust(5, "0")}0" allocated_certificates << certificate next_number += 1 end @@ -77,6 +69,8 @@ def set_allocated_certificates # largest number in certificates represented in x's in format, for all certificates matching format def largest_existing_number_in_certificate + numbering_regex_str = /#{batch.price}(.*)0/ + # look for all numbers that match numbering existing_matching_certificates = GiftCard.all.where("certificate ~* ?", numbering_regex_str).pluck(:certificate) @@ -93,10 +87,10 @@ def largest_existing_number_in_certificate end def self.ransackable_attributes(auth_object = nil) - %w(status created_at updated_at creator_id issuer_id gift_card_type_id card_amount quantity allocated_certificates numbering gift_cards_id) + %w(status created_at updated_at creator_id issuer_id quantity allocated_certificates numbering gift_cards_id) end def self.ransackable_associations(auth_object = nil) - %w(creator issuer gift_card_type gift_cards) + %w(creator issuer gift_cards) end end diff --git a/db/migrate/20241126092131_create_gift_cards.rb b/db/migrate/20241126092131_create_gift_cards.rb index 7b3d478..797d1ba 100644 --- a/db/migrate/20241126092131_create_gift_cards.rb +++ b/db/migrate/20241126092131_create_gift_cards.rb @@ -2,7 +2,9 @@ class CreateGiftCards < ActiveRecord::Migration[7.2] def change create_table :gift_cards do |t| t.integer :issuance_id - t.integer :gift_card_type_id + t.integer :batch_id + t.decimal :price, :precision => 8, :scale => 2 + t.string :gift_card_type t.string :certificate t.datetime :expiration_date t.integer :registrations_available diff --git a/db/migrate/20241126110902_create_batches.rb b/db/migrate/20241126110902_create_batches.rb new file mode 100644 index 0000000..28a3787 --- /dev/null +++ b/db/migrate/20241126110902_create_batches.rb @@ -0,0 +1,24 @@ +class CreateBatches < ActiveRecord::Migration[7.2] + def change + create_table :batches do |t| + t.string :description + t.string :contact + t.string :gift_card_type + t.decimal :price, :precision => 8, :scale => 2 + t.integer :registrations_available + + t.datetime :begin_use_date + t.datetime :end_use_date + t.datetime :expiration_date + + t.string :associated_product + t.string :isbn # optional + + # dept cards have these + t.string :gl_code + t.string :dept + + t.timestamps + end + end +end diff --git a/db/migrate/20241126110902_create_gift_card_types.rb b/db/migrate/20241126110902_create_gift_card_types.rb deleted file mode 100644 index 62adde0..0000000 --- a/db/migrate/20241126110902_create_gift_card_types.rb +++ /dev/null @@ -1,15 +0,0 @@ -class CreateGiftCardTypes < ActiveRecord::Migration[7.2] - def change - create_table :gift_card_types do |t| - t.string :label - t.string :numbering - t.string :contact - t.string :prod_id - t.string :isbn - t.string :gl_acct - t.string :department_number - - t.timestamps - end - end -end diff --git a/db/migrate/20241126111253_create_issuances.rb b/db/migrate/20241126111253_create_issuances.rb index 476973a..3b23725 100644 --- a/db/migrate/20241126111253_create_issuances.rb +++ b/db/migrate/20241126111253_create_issuances.rb @@ -4,14 +4,9 @@ def change t.string :status t.integer :creator_id t.integer :issuer_id - t.decimal :card_amount + t.integer :batch_id t.integer :quantity - t.datetime :begin_use_date - t.datetime :end_use_date - t.datetime :expiration_date - t.integer :gift_card_type_id t.text :allocated_certificates - t.string :numbering t.datetime :issued_at t.timestamps diff --git a/db/schema.rb b/db/schema.rb index 219d1d1..7153ae9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_12_13_203609) do +ActiveRecord::Schema[7.2].define(version: 2025_01_07_060614) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "pgcrypto" @@ -48,21 +48,30 @@ t.datetime "updated_at", null: false end - create_table "gift_card_types", force: :cascade do |t| - t.string "label" - t.string "numbering" + create_table "batches", force: :cascade do |t| + t.string "description" t.string "contact" - t.string "prod_id" + t.string "gift_card_type" + t.decimal "price", precision: 8, scale: 2 + t.integer "registrations_available" + t.datetime "begin_use_date" + t.datetime "end_use_date" + t.datetime "expiration_date" + t.string "associated_product" t.string "isbn" - t.string "gl_acct" - t.string "department_number" + t.string "gl_code" + t.string "dept" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.index ["price"], name: "index_batches_on_price" + t.index ["registrations_available"], name: "index_batches_on_registrations_available" end create_table "gift_cards", force: :cascade do |t| t.integer "issuance_id" - t.integer "gift_card_type_id" + t.integer "batch_id" + t.decimal "price", precision: 8, scale: 2 + t.string "gift_card_type" t.string "certificate" t.datetime "expiration_date" t.integer "registrations_available" @@ -71,24 +80,24 @@ t.string "gl_code" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.string "isbn" + t.index ["batch_id"], name: "index_gift_cards_on_batch_id" + t.index ["issuance_id"], name: "index_gift_cards_on_issuance_id" + t.index ["price"], name: "index_gift_cards_on_price" + t.index ["registrations_available"], name: "index_gift_cards_on_registrations_available" end create_table "issuances", force: :cascade do |t| t.string "status" t.integer "creator_id" t.integer "issuer_id" - t.decimal "card_amount" + t.integer "batch_id" t.integer "quantity" - t.datetime "begin_use_date" - t.datetime "end_use_date" - t.datetime "expiration_date" - t.integer "gift_card_type_id" t.text "allocated_certificates" - t.string "numbering" t.datetime "issued_at" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.index ["batch_id"], name: "index_issuances_on_batch_id" + t.index ["quantity"], name: "index_issuances_on_quantity" end create_table "people", force: :cascade do |t| diff --git a/import.rb b/import.rb index 6348aa5..1c6cb2f 100644 --- a/import.rb +++ b/import.rb @@ -1,145 +1,179 @@ -#=> GiftCardType(id: integer, label: string, numbering: string, contact: string, prod_id: string, isbn: string, gl_acct: string, department_number: string, created_at: datetime, updated_at: datetime) +#=> Batch(id: integer, description: string, regex: string, contact: string, associated_product: string, isbn: string, gl_code: string, dept: string, created_at: datetime, updated_at: datetime) -GiftCardType.delete_all +Batch.class_eval do + attr_accessor :regex +end + +Batch.delete_all +regexes = {} -t = GiftCardType.where(label: "Regular Rate (Green) Paid Gift Card").first_or_initialize -t.numbering = "3500xxxxx0" -t.prod_id = "CER21842" +t = Batch.where(description: "Initial - Regular Rate (Green) Paid Gift Card").first_or_initialize +t.gift_card_type = GiftCard::TYPE_PAID_FULL_PRICE +t.regex = "3500xxxxx0" +regexes[t.description] = t.regex +t.associated_product = "CER21842" t.isbn = "978-1602005761" -t.save! +t.save(validate: false) -t = GiftCardType.where(label: "Half Price (Blue) Paid Gift Card").first_or_initialize -t.numbering = "1750xxxxx0" -t.prod_id = "CER21841" +t = Batch.where(description: "Initial - Half Price (Blue) Paid Gift Card").first_or_initialize +t.gift_card_type = GiftCard::TYPE_PAID_HALF_PRICE +t.regex = "1750xxxxx0" +regexes[t.description] = t.regex +t.associated_product = "CER21841" t.isbn = "978-1602006676" -t.save! - -t = GiftCardType.where(label: "Marketing Department Card").first_or_initialize -t.numbering = "4240xxxxx0" -t.gl_acct = "63301" -t.department_number = "4240-15999" +t.save(validate: false) + +t = Batch.where(description: "Initial - Marketing Department Card").first_or_initialize +t.gift_card_type = GiftCard::TYPE_DEPT +t.regex = "4240xxxxx0" +regexes[t.description] = t.regex +t.gl_code = "63301" +t.dept = "4240-15999" t.contact = "Taylor" -t.save! - -t = GiftCardType.where(label: "Donor Dept. Department Card").first_or_initialize -t.numbering = "3210xxxxx0" -t.gl_acct = "63301" -t.department_number = "3210-38100" +t.save(validate: false) + +t = Batch.where(description: "Initial - Donor Dept. Department Card").first_or_initialize +t.gift_card_type = GiftCard::TYPE_DEPT +t.regex = "3210xxxxx0" +regexes[t.description] = t.regex +t.gl_code = "63301" +t.dept = "3210-38100" t.contact = "Quinton/Gene" -t.save! - -t = GiftCardType.where(label: "Partners Department Card").first_or_initialize -t.numbering = "3215xxxxx0" -t.gl_acct = "63301" -t.department_number = "3215-38116" +t.save(validate: false) + +t = Batch.where(description: "Initial - Partners Department Card").first_or_initialize +t.gift_card_type = GiftCard::TYPE_DEPT +t.regex = "3215xxxxx0" +regexes[t.description] = t.regex +t.gl_code = "63301" +t.dept = "3215-38116" t.contact = "Quinton/Gene" -t.save! - -t = GiftCardType.where(label: "President Department Card").first_or_initialize -t.numbering = "1100xxxxx0" -t.gl_acct = "63301" -t.department_number = "1100-50050" +t.save(validate: false) + +t = Batch.where(description: "Initial - President Department Card").first_or_initialize +t.gift_card_type = GiftCard::TYPE_DEPT +t.regex = "1100xxxxx0" +regexes[t.description] = t.regex +t.gl_code = "63301" +t.dept = "1100-50050" t.contact = "Brian Borger" -t.save! - -t = GiftCardType.where(label: "Field Core Department Card").first_or_initialize -t.numbering = "4710xxxxx0" -t.gl_acct = "63301" -t.department_number = "4710-15999" +t.save(validate: false) + +t = Batch.where(description: "Initial - Field Core Department Card").first_or_initialize +t.gift_card_type = GiftCard::TYPE_DEPT +t.regex = "4710xxxxx0" +regexes[t.description] = t.regex +t.gl_code = "63301" +t.dept = "4710-15999" t.contact = "Brandon" -t.save! - -t = GiftCardType.where(label: "Innovative Events Department Card").first_or_initialize -t.numbering = "4228xxxxx0" -t.gl_acct = "63301" -t.department_number = "4228-14400" +t.save(validate: false) + +t = Batch.where(description: "Initial - Innovative Events Department Card").first_or_initialize +t.gift_card_type = GiftCard::TYPE_DEPT +t.regex = "4228xxxxx0" +regexes[t.description] = t.regex +t.gl_code = "63301" +t.dept = "4228-14400" t.contact = "Tim Bell/Tanya" -t.save! - -t = GiftCardType.where(label: "Speaker Dept. Card").first_or_initialize -t.numbering = "1470xxxxx0" -t.gl_acct = "63301" -t.department_number = "1470-34998" +t.save(validate: false) + +t = Batch.where(description: "Initial - Speaker Dept. Card").first_or_initialize +t.gift_card_type = GiftCard::TYPE_DEPT +t.regex = "1470xxxxx0" +regexes[t.description] = t.regex +t.gl_code = "63301" +t.dept = "1470-34998" t.contact = "Jennifer Abbott" -t.save! - -t = GiftCardType.where(label: "Corporate Department Card").first_or_initialize -t.numbering = "4241xxxxx0" -t.gl_acct = "63301" -t.department_number = "4241-45100" +t.save(validate: false) + +t = Batch.where(description: "Initial - Corporate Department Card").first_or_initialize +t.gift_card_type = GiftCard::TYPE_DEPT +t.regex = "4241xxxxx0" +regexes[t.description] = t.regex +t.gl_code = "63301" +t.dept = "4241-45100" t.contact = "Glen Flagerstrom" -t.save! - -t = GiftCardType.where(label: "Station Relations Card").first_or_initialize -t.numbering = "1420xxxxx0" -t.gl_acct = "63301" -t.department_number = "1420-34001" +t.save(validate: false) + +t = Batch.where(description: "Initial - Station Relations Card").first_or_initialize +t.gift_card_type = GiftCard::TYPE_DEPT +t.regex = "1420xxxxx0" +regexes[t.description] = t.regex +t.gl_code = "63301" +t.dept = "1420-34001" t.contact = "Maddison Villafane" -t.save! +t.save(validate: false) -t = GiftCardType.where(label: "Unknown").first_or_initialize -t.numbering = "xxxxx" -t.contact = "This is for initial import cards with a certificate id that doesn't match any gift card numbering" -t.save! +t = Batch.where(description: "Initial - Unknown").first_or_initialize +t.gift_card_type = GiftCard::TYPE_PAID_OTHER +t.regex = "xxxxx" +regexes[t.description] = t.regex +t.contact = "This is for initial import cards with a certificate id that doesn't match any gift card regex" +t.save(validate: false) -#=> GiftCardType(id: integer, label: string, numbering: string, contact: string, prod_id: string, isbn: string, gl_acct: string, department_number: string, created_at: datetime, updated_at: datetime) +#=> Batch(id: integer, description: string, regex: string, contact: string, associated_product: string, isbn: string, gl_code: string, dept: string, created_at: datetime, updated_at: datetime) #=> GiftCard(id: integer, certificate: integer, expiration_date: datetime, registrations_available: integer, associated_product: string, certificate_value: decimal, gl_code: string, created_at: datetime, updated_at: datetime, issuance_id: integer) total = `wc -l "FL_EventCertificate_202411261112.csv"`.split(" ").first.to_i -unknown_gct = GiftCardType.last +unknown_batch = Batch.last system = Person.where(first_name: "Initial", last_name: "Import").first_or_create GiftCard.delete_all Issuance.delete_all -initial_issuances = GiftCardType.all.collect do |gct| - issuance = Issuance.where(creator_id: system, issuer_id: system, gift_card_type: gct).first_or_create - issuance.card_amount = 0 +initial_issuances = Batch.all.collect do |batch| + issuance = Issuance.where(creator_id: system, issuer_id: system, batch: batch).first_or_create issuance.quantity = 0 issuance.save! issuance.update_column(:status, "issued") issuance = Issuance.find(issuance.id) - [ gct, issuance ] + [ batch, issuance ] end.to_h -all_types = GiftCardType.all -batch = [] +all_batches = Batch.all +gift_cards = [] + +regexes.each_pair do |k, v| + regexes[k] = v.gsub(/x+/) { |xs| "(#{xs.gsub("x", "\\d")})" } +end i = 0 CSV.foreach("FL_EventCertificate_202411261112.csv", headers: true) do |row| + puts("[#{i += 1}/#{total}, #{(i / total.to_f * 100).round(2)}%]") # ex: # # - gct = all_types.detect{ |gtt| gtt.numbering_regex.match(row["certificateId"]) } || unknown_gct - issuance = initial_issuances[gct] + #byebug + batch = all_batches.detect{ |batch| /#{regexes[batch.description]}/.match(row["certificateId"]) } || unknown_batch + issuance = initial_issuances[batch] #gc = GiftCard.where(certificate_id: row["certificateId"]).first_or_initialize gc = GiftCard.new gc.certificate = row["certificateId"] gc.issuance = issuance - gc.gift_card_type = gct + gc.batch = batch gc.expiration_date = DateTime.parse(row["expirationDate"]) gc.registrations_available = row["numberRegistrations"].to_i - gc.certificate_value = row["certificateValue"].to_d + gc.price = row["certificateValue"].to_d gc.gl_code = row["glCode"] gc.created_at = DateTime.parse(row["addDate"]) if row["created_at"] gc.updated_at = DateTime.parse(row["modifiedDate"]) if row["modifiedDate"] gc.associated_product = row["associatedProduct"] - batch << gc + gift_cards << gc - if batch.length >= 1000 - GiftCard.import(batch) - batch = [] + if gift_cards.length >= 10000 + GiftCard.import(gift_cards) + gift_cards = [] end end -GiftCard.import(batch) +GiftCard.import(gift_cards) Issuance.where(quantity: [nil, 0]).each do |i| i.update(quantity: i.gift_cards.count) diff --git a/lib/has_numbering.rb b/lib/has_numbering.rb deleted file mode 100644 index 1538fc4..0000000 --- a/lib/has_numbering.rb +++ /dev/null @@ -1,10 +0,0 @@ -module HasNumbering - def numbering_regex_str - # add brackets around the x's so that it can be extracted, and use \d instead of x - numbering.gsub(/x+/) { |xs| "(#{xs.gsub("x", "\\d")})" } - end - - def numbering_regex - @numbering_regex ||= /#{numbering_regex_str}/ - end -end From eb45bd73f9cce05610f747c5fe5a4129d8e5979d Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Tue, 7 Jan 2025 01:49:42 -0500 Subject: [PATCH 63/73] add some indexes --- db/migrate/20250107060614_add_indexes.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 db/migrate/20250107060614_add_indexes.rb diff --git a/db/migrate/20250107060614_add_indexes.rb b/db/migrate/20250107060614_add_indexes.rb new file mode 100644 index 0000000..1d67210 --- /dev/null +++ b/db/migrate/20250107060614_add_indexes.rb @@ -0,0 +1,14 @@ +class AddIndexes < ActiveRecord::Migration[7.2] + def change + add_index :gift_cards, :issuance_id + add_index :gift_cards, :batch_id + add_index :gift_cards, :registrations_available + add_index :gift_cards, :price + + add_index :issuances, :batch_id + add_index :issuances, :quantity + + add_index :batches, :registrations_available + add_index :batches, :price + end +end From c35244f48df5d435f0f76b5958783fa8c1ac2759 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Tue, 7 Jan 2025 01:50:07 -0500 Subject: [PATCH 64/73] try to rip out sidekiq it's causing build failures and we don't really need it --- Gemfile | 8 ------ Gemfile.lock | 41 ------------------------------ config/application.rb | 3 --- config/initializers/sidekiq.rb | 46 ---------------------------------- config/redis.yml | 5 ---- 5 files changed, 103 deletions(-) delete mode 100644 config/initializers/sidekiq.rb diff --git a/Gemfile b/Gemfile index 320e967..a6dc14a 100644 --- a/Gemfile +++ b/Gemfile @@ -10,14 +10,6 @@ gem "sassc-rails" gem "pg" -gem "sidekiq", "~> 6.5" -source "https://gems.contribsys.com/" do - gem "sidekiq-pro" -end -gem "sidekiq-cron" -gem "sidekiq-failures" -gem "sidekiq-unique-jobs" - # Use the Puma web server [https://github.com/puma/puma] gem "puma", ">= 5.0" # Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails] diff --git a/Gemfile.lock b/Gemfile.lock index 9f08b98..88a03eb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,3 @@ -GEM - remote: https://gems.contribsys.com/ - specs: - sidekiq-pro (5.5.8) - sidekiq (~> 6.0, >= 6.5.6) - GEM remote: https://rubygems.org/ specs: @@ -118,9 +112,6 @@ GEM msgpack (~> 1.2) brakeman (6.2.2) racc - brpoplpush-redis_script (0.1.3) - concurrent-ruby (~> 1.0, >= 1.0.5) - redis (>= 1.0, < 6) builder (3.3.0) byebug (11.1.3) capybara (3.40.0) @@ -136,9 +127,6 @@ GEM concurrent-ruby (1.3.4) connection_pool (2.4.1) crass (1.0.6) - cronex (0.15.0) - tzinfo - unicode (>= 0.4.4.5) csv (3.3.0) datadog-ci (0.8.3) msgpack @@ -161,8 +149,6 @@ GEM railties (>= 6.1) drb (2.2.1) erubi (1.13.0) - et-orbi (1.2.11) - tzinfo ffi (1.17.0-aarch64-linux-gnu) ffi (1.17.0-aarch64-linux-musl) ffi (1.17.0-arm-linux-gnu) @@ -176,9 +162,6 @@ GEM formtastic (5.0.0) actionpack (>= 6.0.0) formtastic_i18n (0.7.0) - fugit (1.11.1) - et-orbi (~> 1, >= 1.2.11) - raabro (~> 1.4) globalid (1.2.1) activesupport (>= 6.1) has_scope (0.8.2) @@ -299,7 +282,6 @@ GEM public_suffix (6.0.1) puma (6.5.0) nio4r (~> 2.0) - raabro (1.4.0) racc (1.8.1) rack (2.2.10) rack-session (1.0.2) @@ -411,23 +393,6 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - sidekiq (6.5.12) - connection_pool (>= 2.2.5, < 3) - rack (~> 2.0) - redis (>= 4.5.0, < 5) - sidekiq-cron (2.0.1) - cronex (>= 0.13.0) - fugit (~> 1.8, >= 1.11.1) - globalid (>= 1.0.1) - sidekiq (>= 6.5.0) - sidekiq-failures (1.0.4) - sidekiq (>= 4.0.0) - sidekiq-unique-jobs (7.1.33) - brpoplpush-redis_script (> 0.1.1, <= 2.0.0) - concurrent-ruby (~> 1.0, >= 1.0.5) - redis (< 5.0) - sidekiq (>= 5.0, < 7.0) - thor (>= 0.20, < 3.0) slop (3.6.0) sprockets (4.2.1) concurrent-ruby (~> 1.0) @@ -456,7 +421,6 @@ GEM timeout (0.4.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode (0.4.4.5) unicode-display_width (2.6.0) useragent (0.16.10) web-console (4.2.1) @@ -519,11 +483,6 @@ DEPENDENCIES rubocop-rails-omakase sassc-rails selenium-webdriver - sidekiq (~> 6.5) - sidekiq-cron - sidekiq-failures - sidekiq-pro! - sidekiq-unique-jobs sprockets-rails standard stimulus-rails diff --git a/config/application.rb b/config/application.rb index 3b31bc9..a0768dc 100644 --- a/config/application.rb +++ b/config/application.rb @@ -29,9 +29,6 @@ class Application < Rails::Application # config.time_zone = "Central Time (US & Canada)" # config.eager_load_paths << Rails.root.join("extras") - # Use sidekiq for active_job - config.active_job.queue_adapter = :sidekiq - # Send all logs to stdout, which docker reads and sends to datadog. config.logger = Log::Logger.new($stdout) unless Rails.env.test? # we don't need a logger in test env end diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb deleted file mode 100644 index 2a13409..0000000 --- a/config/initializers/sidekiq.rb +++ /dev/null @@ -1,46 +0,0 @@ -require "datadog/statsd" -require "redis" - -redis_conf = YAML.safe_load(ERB.new(File.read(Rails.root.join("config", "redis.yml"))).result, permitted_classes: [Symbol], aliases: true)["sidekiq"] - -redis_settings = {url: Redis.new(redis_conf).id} - -SidekiqUniqueJobs.configure do |config| - # don't use SidekiqUniqueJobs in test env because it will cause head-scratching - # https://github.com/mhenrixon/sidekiq-unique-jobs#uniqueness - # https://github.com/mperham/sidekiq/wiki/Ent-Unique-Jobs#enable (not our gem but Sidekiq Enterprise suggested the same thing) - config.enabled = !Rails.env.test? -end - -Sidekiq.configure_client do |config| - config.redis = redis_settings - - config.client_middleware do |chain| - chain.add SidekiqUniqueJobs::Middleware::Client - end -end - -Sidekiq::Client.reliable_push! - -Sidekiq.configure_server do |config| - config.super_fetch! - config.reliable_scheduler! - config.redis = redis_settings - config.failures_default_mode = :exhausted - - config.client_middleware do |chain| - chain.add SidekiqUniqueJobs::Middleware::Client - end - - config.server_middleware do |chain| - chain.add SidekiqUniqueJobs::Middleware::Server - end - - SidekiqUniqueJobs::Server.configure(config) -end - -if ENV["AWS_EXECUTION_ENV"].present? - Sidekiq::Pro.dogstatsd = -> { Datadog::Statsd.new socket_path: "/var/run/datadog/dsd.socket" } -end - -Sidekiq.default_worker_options = {"backtrace" => true} diff --git a/config/redis.yml b/config/redis.yml index 8204ebe..ef3f4b8 100644 --- a/config/redis.yml +++ b/config/redis.yml @@ -1,11 +1,6 @@ default: &DEFAULT :db: <%= ENV.fetch('STORAGE_REDIS_DB_INDEX') %> -sidekiq: - <<: *DEFAULT - :host: <%= ENV.fetch('STORAGE_REDIS_HOST') %> - :port: <%= ENV.fetch('STORAGE_REDIS_PORT', 6379) %> - session: &SESSION <<: *DEFAULT :host: <%= ENV.fetch('SESSION_REDIS_HOST') %> From 0ac2f6ec5ef6a78629719fd5aafc3d2b9a621468 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Fri, 10 Jan 2025 14:47:22 -0500 Subject: [PATCH 65/73] make issuing work again --- app/admin/batches.rb | 53 +++++++++++++++---- app/admin/gift_cards.rb | 1 + app/admin/issuances.rb | 21 ++------ app/models/batch.rb | 12 +++-- app/models/gift_card.rb | 7 +-- app/models/issuance.rb | 32 ++++++----- .../20241126092131_create_gift_cards.rb | 1 - db/schema.rb | 1 + 8 files changed, 81 insertions(+), 47 deletions(-) diff --git a/app/admin/batches.rb b/app/admin/batches.rb index e161c0c..0c50f73 100644 --- a/app/admin/batches.rb +++ b/app/admin/batches.rb @@ -1,20 +1,53 @@ ActiveAdmin.register Batch do + actions :all, except: :destroy # See permitted parameters documentation: # https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters # # Uncomment all parameters which should be permitted for assignment # - permit_params :description, :gift_card_type, :price, :registrations_available, :begin_use_date, :end_use_date, :expiration_date, + permit_params :description, :contact, :gift_card_type, :price, :registrations_available, :begin_use_date, :end_use_date, :expiration_date, :associated_product, :isbn, :gl_code, :dept - # - # or - # - # permit_params do - # permitted = [:label, :numbering, :contact, :prod_id, :isbn, :gl_acct, :department_number] - # permitted << :other if params[:action] == 'create' && current_user.admin? - # permitted - # end - + index do + column :id + column :description + column :contact + column :gift_card_type do |record| + GiftCard::TYPE_DESCRIPTIONS[record.gift_card_type] + end + column :price, as: :currency + column "Reg'n Avail", :registrations_available + column :begin_use_date + column :end_use_date + column "Expn Date", :expiration_date + column "Product", :associated_product + column :isbn + column :gl_code + column :dept + end + + form do |f| + f.semantic_errors + + inputs do + input :description + input :contact + input :gift_card_type, as: :select, collection: GiftCard::TYPE_DESCRIPTIONS.invert + input :price + input :registrations_available + input :begin_use_date, as: :date_time_picker + input :end_use_date, as: :date_time_picker + input :expiration_date, as: :date_time_picker + input :associated_product + input :isbn + + if GiftCard::PAID_TYPES.include?(f.object.gift_card_type) + input :gl_code + input :dept + end + end + + f.actions + end end diff --git a/app/admin/gift_cards.rb b/app/admin/gift_cards.rb index 4ead4e7..af8fab5 100644 --- a/app/admin/gift_cards.rb +++ b/app/admin/gift_cards.rb @@ -21,6 +21,7 @@ selectable_column id_column column :gift_card_type + column :batch column :issuance column :certificate number_column "Value", :certificate_value, as: :currency, unit: "$", sortable: :certificate_value diff --git a/app/admin/issuances.rb b/app/admin/issuances.rb index 753bd16..1a4d974 100644 --- a/app/admin/issuances.rb +++ b/app/admin/issuances.rb @@ -6,12 +6,12 @@ # # Uncomment all parameters which should be permitted for assignment # - permit_params :status, :gift_card_amount, :card_amount, :quantity, :begin_use_date, :end_use_date, :expiration_date, :gift_card_type_id + permit_params :batch_id, :quantity # # or # # permit_params do - # permitted = [:status, :initiator_id, :card_amount, :quantity, :begin_use_date, :end_use_date, :expiration_date] + # permitted = [:status, :initiator_id, :price, :quantity] # permitted << :other if params[:action] == 'create' && current_user.admin? # permitted # end @@ -20,11 +20,7 @@ filter :issuer filter :gift_card_type filter :status - filter :card_amount filter :quantitiy - filter :begin_use_date - filter :end_use_date - filter :expiration_date filter :allocated_certificates filter :numbering filter :issued_at @@ -52,21 +48,14 @@ total = issuance.gift_cards.count raw("#{number_with_delimiter(used)} / #{number_with_delimiter(issuance.gift_cards.count)}
(#{(used / total.to_f * 100).round(1)}%)") end - column :begin_use_date - column :end_use_date - column :expiration_date end - form do |f| + form do |f| f.semantic_errors - inputs do - input :gift_card_type - input :card_amount + inputs do + input :batch, collection: Batch.order(created_at: :desc).collect{ |batch| [batch.to_s, batch.id] } input :quantity - input :begin_use_date, as: :date_time_picker - input :end_use_date, as: :date_time_picker - input :expiration_date, as: :date_time_picker actions do unless issuance.issued? diff --git a/app/models/batch.rb b/app/models/batch.rb index e3e7b84..ddcdae2 100644 --- a/app/models/batch.rb +++ b/app/models/batch.rb @@ -1,11 +1,15 @@ class Batch < ApplicationRecord - validates_presence_of :description, :gift_card_type, :price, :registrations_available, :begin_use_date, :end_use_date, :expiration_date, - :associated_product, :isbn - validates :gl_code, :dept, presence: true, if: -> { gift_card_type == GiftCard::TYPE_PAID_FULL_PRICE } + validates_presence_of :description, :gift_card_type, :price, :registrations_available, :begin_use_date, :end_use_date, :expiration_date, + :associated_product, :isbn + validates_presence_of :gl_code, :dept, presence: true, if: -> { GiftCard::PAID_TYPES.include?(gift_card_type) } + + def new + self.registrations_available ||= 2 + end def to_s - description + description end def self.ransackable_attributes(auth_object = nil) diff --git a/app/models/gift_card.rb b/app/models/gift_card.rb index c577e0f..45a3d64 100644 --- a/app/models/gift_card.rb +++ b/app/models/gift_card.rb @@ -3,12 +3,13 @@ class GiftCard < ApplicationRecord TYPE_PAID_FULL_PRICE = "paid_full_price" TYPE_PAID_OTHER = "paid_other" TYPE_DEPT = "type_dept" + PAID_TYPES = [TYPE_PAID_HALF_PRICE, TYPE_PAID_FULL_PRICE, TYPE_PAID_OTHER] TYPE_DESCRIPTIONS = { - TYPE_PAID_HALF_PRICE => "Paid, Half Type", - TYPE_PAID_FULL_PRICE => "Paid, Full Type", + TYPE_PAID_HALF_PRICE => "Paid, Half", + TYPE_PAID_FULL_PRICE => "Paid, Full", TYPE_PAID_OTHER => "Paid, Other", - TYPE_DEPT => "Paid, Department" + TYPE_DEPT => "Department" } belongs_to :issuance diff --git a/app/models/issuance.rb b/app/models/issuance.rb index eee4809..5ba2b79 100644 --- a/app/models/issuance.rb +++ b/app/models/issuance.rb @@ -9,8 +9,8 @@ class Issuance < ApplicationRecord has_many :gift_cards validates :quantity, numericality: { only_integer: true } - delegate :price, to: :batch - delegate :gift_card_type, to: :batch + delegate :price, to: :batch, allow_nil: true + delegate :gift_card_type, to: :batch, allow_nil: true aasm column: :status do state :configuring, initial: true @@ -31,6 +31,10 @@ class Issuance < ApplicationRecord preview! end + before_save do + set_allocated_certificates if previewing? + end + def to_s if previewing? "Gift Card Issuance (Preview)" @@ -42,14 +46,15 @@ def to_s def create_gift_cards allocated_certificates.split(CERTIFICATE_DISPLAY_SEPARATOR).each do |certificate| gift_card = gift_cards.where(certificate: certificate).first_or_initialize - gift_card.registrations_available = 2 + gift_card.registrations_available = batch.registrations_available gift_card.price = price - gift_card.expiration_date = expiration_date - gift_card.gift_card_type = gift_card_type + gift_card.expiration_date = batch.expiration_date + gift_card.gift_card_type = batch.gift_card_type gift_card.issuance = self - gift_card.isbn = gift_card_type.isbn - gift_card.associated_product = gift_card_type.prod_id - gift_card.gl_code = [gift_card_type.gl_acct, gift_card_type.department_number.to_s.gsub("-", ".")].compact.join(".") + gift_card.batch = batch + gift_card.isbn = batch.isbn + gift_card.associated_product = batch.associated_product + gift_card.gl_code = batch.gl_code gift_card.save! end end @@ -59,7 +64,7 @@ def set_allocated_certificates allocated_certificates = [] quantity.times do - certificate = "#{price.to_s.rjust(3, "0")}#{next_number.to_s.rjust(5, "0")}0" + certificate = "#{price}#{next_number.to_s.rjust(5, "0")}0" allocated_certificates << certificate next_number += 1 end @@ -69,21 +74,22 @@ def set_allocated_certificates # largest number in certificates represented in x's in format, for all certificates matching format def largest_existing_number_in_certificate - numbering_regex_str = /#{batch.price}(.*)0/ + numbering_regex = /^#{batch.price}(\d\d\d\d\d)0$/ + numbering_regex_str = "^#{batch.price}(\\d\\d\\d\\d\\d)0" # look for all numbers that match numbering existing_matching_certificates = GiftCard.all.where("certificate ~* ?", numbering_regex_str).pluck(:certificate) # pulling all allocated certificate ids instead of a regex isn't ideal, but there shouldn't be many, if any, times there # are previewed gift cards issuances while another one is being previewed - existing_matching_certificates += Issuance.previewing.pluck(:allocated_certificates).collect do |allocated_certificates| - allocated_certificates.split(CERTIFICATE_DISPLAY_SEPARATOR).find_all { |certificate| certificate =~ numbering_regex } + existing_matching_certificates += Issuance.previewing.where.not(id: self.id).pluck(:allocated_certificates).collect do |allocated_certificates| + allocated_certificates.to_s.split(CERTIFICATE_DISPLAY_SEPARATOR).find_all { |certificate| certificate =~ numbering_regex } end.flatten existing_matching_certificates.collect{ |certificate| certificate =~ numbering_regex $1.to_i - }.max || 1 + }.max || 0 end def self.ransackable_attributes(auth_object = nil) diff --git a/db/migrate/20241126092131_create_gift_cards.rb b/db/migrate/20241126092131_create_gift_cards.rb index 797d1ba..ae87ce3 100644 --- a/db/migrate/20241126092131_create_gift_cards.rb +++ b/db/migrate/20241126092131_create_gift_cards.rb @@ -9,7 +9,6 @@ def change t.datetime :expiration_date t.integer :registrations_available t.string :associated_product - t.decimal :certificate_value t.string :gl_code t.timestamps diff --git a/db/schema.rb b/db/schema.rb index 7153ae9..da982c9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -80,6 +80,7 @@ t.string "gl_code" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "isbn" t.index ["batch_id"], name: "index_gift_cards_on_batch_id" t.index ["issuance_id"], name: "index_gift_cards_on_issuance_id" t.index ["price"], name: "index_gift_cards_on_price" From 19fb41a0cf7f6801e234ea13f3a77f8bff66d17d Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Sun, 12 Jan 2025 23:52:22 -0500 Subject: [PATCH 66/73] implement okta login --- Gemfile | 7 +- Gemfile.lock | 62 ++++ app/controllers/application_controller.rb | 18 +- app/controllers/login_controller.rb | 2 + app/controllers/monitors_controller.rb | 2 +- app/controllers/sessions_controller.rb | 16 + app/models/authentication.rb | 9 - app/models/issuance.rb | 4 +- app/models/person.rb | 13 - app/models/user.rb | 38 +++ app/views/layouts/application.html.erb | 23 -- app/views/login/error.html.erb | 10 + app/views/login/new.html.erb | 15 + config/application.rb | 6 +- config/initializers/active_admin.rb | 6 +- config/initializers/activeadmin_addons.rb | 4 +- config/initializers/devise.rb | 283 ++++++++++++++++++ .../devise_sign_out_monkeypatch.rb | 14 + config/initializers/omniauth.rb | 14 + config/routes.rb | 11 +- db/migrate/20241126092151_create_people.rb | 10 - db/migrate/20241126092151_create_users.rb | 15 + .../20241126092204_create_authentications.rb | 13 - db/schema.rb | 21 +- 24 files changed, 514 insertions(+), 102 deletions(-) create mode 100644 app/controllers/login_controller.rb create mode 100644 app/controllers/sessions_controller.rb delete mode 100644 app/models/authentication.rb delete mode 100644 app/models/person.rb create mode 100644 app/models/user.rb delete mode 100644 app/views/layouts/application.html.erb create mode 100644 app/views/login/error.html.erb create mode 100644 app/views/login/new.html.erb create mode 100644 config/initializers/devise.rb create mode 100644 config/initializers/devise_sign_out_monkeypatch.rb create mode 100644 config/initializers/omniauth.rb delete mode 100644 db/migrate/20241126092151_create_people.rb create mode 100644 db/migrate/20241126092151_create_users.rb delete mode 100644 db/migrate/20241126092204_create_authentications.rb diff --git a/Gemfile b/Gemfile index a6dc14a..8c0735c 100644 --- a/Gemfile +++ b/Gemfile @@ -22,6 +22,11 @@ gem "jbuilder" gem "redis", "~> 4.0" gem "redis-actionpack" +# login-related +gem "devise" +gem "omniauth-oktaoauth", github: "CruGlobal/omniauth-oktaoauth" +gem "omniauth-rails_csrf_protection" + # Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis] # gem "kredis" @@ -77,4 +82,4 @@ gem "ddtrace", "~> 1.4" gem "ougai", "~> 1.7" gem "amazing_print" - +gem "strip_attributes" diff --git a/Gemfile.lock b/Gemfile.lock index 88a03eb..a1507b8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,11 @@ +GIT + remote: https://github.com/CruGlobal/omniauth-oktaoauth.git + revision: 02b679206abdba13d25157b99351128c76e8b36c + specs: + omniauth-oktaoauth (0.1.6) + omniauth (>= 1.5) + omniauth-oauth2 (>= 1.4.0, < 2.0) + GEM remote: https://rubygems.org/ specs: @@ -103,6 +111,7 @@ GEM ruby2_keywords (>= 0.0.2) ast (2.4.2) base64 (0.2.0) + bcrypt (3.1.20) benchmark (0.4.0) bigdecimal (3.1.8) bindex (0.8.1) @@ -142,6 +151,12 @@ GEM irb (~> 1.10) reline (>= 0.3.8) debug_inspector (1.2.0) + devise (4.9.4) + bcrypt (~> 3.0) + orm_adapter (~> 0.1) + railties (>= 4.1.0) + responders + warden (~> 1.2.3) dogstatsd-ruby (5.6.3) dotenv (3.1.4) dotenv-rails (3.1.4) @@ -149,6 +164,12 @@ GEM railties (>= 6.1) drb (2.2.1) erubi (1.13.0) + faraday (2.12.2) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-net_http (3.4.0) + net-http (>= 0.5.0) ffi (1.17.0-aarch64-linux-gnu) ffi (1.17.0-aarch64-linux-musl) ffi (1.17.0-arm-linux-gnu) @@ -167,6 +188,7 @@ GEM has_scope (0.8.2) actionpack (>= 5.2) activesupport (>= 5.2) + hashie (5.0.0) i18n (1.14.6) concurrent-ruby (~> 1.0) importmap-rails (2.0.3) @@ -190,6 +212,8 @@ GEM railties (>= 4.2.0) thor (>= 0.14, < 2.0) json (2.8.2) + jwt (2.10.1) + base64 kaminari (1.2.2) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.2) @@ -232,6 +256,10 @@ GEM mini_mime (1.1.5) minitest (5.25.4) msgpack (1.7.5) + multi_xml (0.7.1) + bigdecimal (~> 3.1) + net-http (0.6.0) + uri net-imap (0.5.1) date net-protocol @@ -254,9 +282,27 @@ GEM racc (~> 1.4) nokogiri (1.16.7-x86_64-linux) racc (~> 1.4) + oauth2 (2.0.9) + faraday (>= 0.17.3, < 3.0) + jwt (>= 1.0, < 3.0) + multi_xml (~> 0.5) + rack (>= 1.2, < 4) + snaky_hash (~> 2.0) + version_gem (~> 1.1) oj (3.16.7) bigdecimal (>= 3.0) ostruct (>= 0.2) + omniauth (2.1.2) + hashie (>= 3.4.6) + rack (>= 2.2.3) + rack-protection + omniauth-oauth2 (1.8.0) + oauth2 (>= 1.4, < 3) + omniauth (~> 2.0) + omniauth-rails_csrf_protection (1.0.2) + actionpack (>= 4.2) + omniauth (~> 2.0) + orm_adapter (0.5.0) ostruct (0.6.1) ougai (1.9.1) oj (~> 3.10) @@ -284,6 +330,9 @@ GEM nio4r (~> 2.0) racc (1.8.1) rack (2.2.10) + rack-protection (3.2.0) + base64 (>= 0.1.0) + rack (~> 2.2, >= 2.2.4) rack-session (1.0.2) rack (< 3) rack-test (2.1.0) @@ -394,6 +443,9 @@ GEM rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) slop (3.6.0) + snaky_hash (2.0.1) + hashie + version_gem (~> 1.1, >= 1.1.1) sprockets (4.2.1) concurrent-ruby (~> 1.0) rack (>= 2.2.4, < 4) @@ -416,13 +468,19 @@ GEM stimulus-rails (1.3.4) railties (>= 6.0.0) stringio (3.1.2) + strip_attributes (1.14.1) + activemodel (>= 3.0, < 9.0) thor (1.3.2) tilt (2.4.0) timeout (0.4.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.6.0) + uri (1.0.2) useragent (0.16.10) + version_gem (1.1.4) + warden (1.2.9) + rack (>= 2.0.9) web-console (4.2.1) actionview (>= 6.0.0) activemodel (>= 6.0.0) @@ -467,10 +525,13 @@ DEPENDENCIES capybara ddtrace (~> 1.4) debug + devise dogstatsd-ruby (~> 5.3) dotenv-rails importmap-rails jbuilder + omniauth-oktaoauth! + omniauth-rails_csrf_protection ougai (~> 1.7) pg pry-byebug @@ -486,6 +547,7 @@ DEPENDENCIES sprockets-rails standard stimulus-rails + strip_attributes tzinfo-data web-console diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 1235efc..89d30ec 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,13 +1,15 @@ class ApplicationController < ActionController::Base - # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. - allow_browser versions: :modern - - def current_admin_user - # TODO replace with okta user - Person.first + def authenticate_active_admin_user! + redirect_to("/login/new") && return unless current_user end - def destroy_admin_user_session_path - "TODO" + def after_sign_out_path_for(_resource_or_scope) + id_token = session[:id_token] + session.clear + if id_token.present? + "#{ENV.fetch("OKTA_ISSUER")}/v1/logout?id_token_hint=#{id_token}&post_logout_redirect_uri=#{request.base_url}" + else + "/" + end end end diff --git a/app/controllers/login_controller.rb b/app/controllers/login_controller.rb new file mode 100644 index 0000000..78311c2 --- /dev/null +++ b/app/controllers/login_controller.rb @@ -0,0 +1,2 @@ +class LoginController < ApplicationController +end diff --git a/app/controllers/monitors_controller.rb b/app/controllers/monitors_controller.rb index fa1cf6b..724758f 100644 --- a/app/controllers/monitors_controller.rb +++ b/app/controllers/monitors_controller.rb @@ -2,7 +2,7 @@ class MonitorsController < ApplicationController #skip_before_action :require_login, raise: false def lb - Person.first + User.first render plain: "OK" end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000..2cc53d0 --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class SessionsController < Devise::OmniauthCallbacksController + def oktaoauth + @user = User.find_or_create_from_auth_hash(auth_hash) + sign_in @user + session[:id_token] = request.env.dig("omniauth.auth", "extra", "id_token") + redirect_to root_path + end + + protected + + def auth_hash + request.env["omniauth.auth"] + end +end diff --git a/app/models/authentication.rb b/app/models/authentication.rb deleted file mode 100644 index 4b77c46..0000000 --- a/app/models/authentication.rb +++ /dev/null @@ -1,9 +0,0 @@ -class Authentication < ApplicationRecord - def self.ransackable_attributes(auth_object = nil) - [] - end - - def self.ransackable_associations(auth_object = nil) - [] - end -end diff --git a/app/models/issuance.rb b/app/models/issuance.rb index eee4809..8abf9a4 100644 --- a/app/models/issuance.rb +++ b/app/models/issuance.rb @@ -3,8 +3,8 @@ class Issuance < ApplicationRecord include AASM - belongs_to :creator, class_name: "Person" - belongs_to :issuer, class_name: "Person", optional: true + belongs_to :creator, class_name: "User" + belongs_to :issuer, class_name: "User", optional: true belongs_to :batch has_many :gift_cards diff --git a/app/models/person.rb b/app/models/person.rb deleted file mode 100644 index fa0c41d..0000000 --- a/app/models/person.rb +++ /dev/null @@ -1,13 +0,0 @@ -class Person < ApplicationRecord - def self.ransackable_attributes(auth_object = nil) - [:first_name, :last_name] - end - - def self.ransackable_associations(auth_object = nil) - [] - end - - def full_name - "#{first_name} #{last_name}" - end -end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..e59ab49 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,38 @@ +class User < ApplicationRecord + devise :omniauthable, omniauth_providers: [:oktaoauth] + + strip_attributes only: %i[username first_name last_name email] + + def self.ransackable_attributes(auth_object = nil) + [:first_name, :last_name] + end + + def self.ransackable_associations(auth_object = nil) + [] + end + + def self.find_or_create_from_auth_hash(auth_hash) + existing = find_by(sso_guid: auth_hash.extra.raw_info.ssoguid) + return existing.apply_auth_hash(auth_hash) if existing + + pending = find_by("lower(username) = ?", auth_hash.extra.raw_info.preferred_username.downcase) + return pending.apply_auth_hash(auth_hash) if pending + + new.apply_auth_hash(auth_hash) + end + + def apply_auth_hash(auth_hash) + self.sso_guid = auth_hash.extra.raw_info.ssoguid + self.username = auth_hash.extra.raw_info.preferred_username + self.first_name = auth_hash.info.first_name + self.last_name = auth_hash.info.last_name + self.email = auth_hash.info.email + save! + self + end + + def name + [first_name, last_name].join(" ") + end + alias_method :full_name, :name +end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb deleted file mode 100644 index 13f3e77..0000000 --- a/app/views/layouts/application.html.erb +++ /dev/null @@ -1,23 +0,0 @@ - - - - <%= content_for(:title) || "Familylife Gift Cards" %> - - - <%= csrf_meta_tags %> - <%= csp_meta_tag %> - - <%= yield :head %> - - - - - - <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> - <%= javascript_importmap_tags %> - - - - <%= yield %> - - diff --git a/app/views/login/error.html.erb b/app/views/login/error.html.erb new file mode 100644 index 0000000..c6495b4 --- /dev/null +++ b/app/views/login/error.html.erb @@ -0,0 +1,10 @@ + + + + + + +Login error. + + + diff --git a/app/views/login/new.html.erb b/app/views/login/new.html.erb new file mode 100644 index 0000000..772ff57 --- /dev/null +++ b/app/views/login/new.html.erb @@ -0,0 +1,15 @@ + + + + + + +<%= form_tag "/users/auth/oktaoauth", id: "auth_form" do %> +<% end %> + + + + + diff --git a/config/application.rb b/config/application.rb index a0768dc..d8e3641 100644 --- a/config/application.rb +++ b/config/application.rb @@ -31,5 +31,9 @@ class Application < Rails::Application # Send all logs to stdout, which docker reads and sends to datadog. config.logger = Log::Logger.new($stdout) unless Rails.env.test? # we don't need a logger in test env - end + + config.after_initialize do + load "lib/fix_logout.rb" + end + end end diff --git a/config/initializers/active_admin.rb b/config/initializers/active_admin.rb index 85c4cc7..c6ae19f 100644 --- a/config/initializers/active_admin.rb +++ b/config/initializers/active_admin.rb @@ -71,7 +71,7 @@ # # This setting changes the method which Active Admin calls # within the application controller. - # config.authentication_method = :authenticate_admin_user! + config.authentication_method = :authenticate_active_admin_user! # == User Authorization # @@ -108,7 +108,7 @@ # # This setting changes the method which Active Admin calls # (within the application controller) to return the currently logged in user. - config.current_user_method = :current_admin_user + config.current_user_method = :current_user # == Logging Out # @@ -120,7 +120,7 @@ # will call the method to return the path. # # Default: - # config.logout_link_path = :destroy_admin_user_session_path + config.logout_link_path = :destroy_user_session_path # This setting changes the http method used when rendering the # link. For example :get, :delete, :put, etc.. diff --git a/config/initializers/activeadmin_addons.rb b/config/initializers/activeadmin_addons.rb index ac9ea44..1fdef71 100644 --- a/config/initializers/activeadmin_addons.rb +++ b/config/initializers/activeadmin_addons.rb @@ -1,9 +1,9 @@ ActiveadminAddons.setup do |config| # Change to "default" if you want to use ActiveAdmin's default select control. - # config.default_select = "select2" + config.default_select = "default" # Set default options for DateTimePickerInput. The options you can provide are the same as in - # xdan's datetimepicker library (https://github.com/xdan/datetimepicker/tree/2.5.4). Yo need to + # xdan's datetimepicker library (https://github.com/xdan/datetimepicker/tree/2.5.4). You need to # pass a ruby hash, avoid camelCase keys. For example: use min_date instead of minDate key. # config.datetime_picker_default_options = {} diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb new file mode 100644 index 0000000..26ef9f0 --- /dev/null +++ b/config/initializers/devise.rb @@ -0,0 +1,283 @@ +# frozen_string_literal: true + +# Use this hook to configure devise mailer, warden hooks and so forth. +# Many of these configuration options can be set straight in your model. +Devise.setup do |config| + # 4.9 changes + config.responder.error_status = :unprocessable_entity + config.responder.redirect_status = :see_other + + # The secret key used by Devise. Devise uses this key to generate + # random tokens. Changing this key will render invalid all existing + # confirmation, reset password and unlock tokens in the database. + # Devise will use the `secret_key_base` as its `secret_key` + # by default. You can change it below and use your own secret key. + # config.secret_key = '6291273c0e44927ef374ef57782dcc12f1eca66b1e0b587394e4a67b82b0e5eef27a057fb6648d722179ff909' + + # ==> Mailer Configuration + # Configure the e-mail address which will be shown in Devise::Mailer, + # note that it will be overwritten if you use your own mailer class + # with default "from" parameter. + config.mailer_sender = "please-change-me-at-config-initializers-devise@example.com" + + # Configure the class responsible to send e-mails. + # config.mailer = 'Devise::Mailer' + + # Configure the parent class responsible to send e-mails. + # config.parent_mailer = 'ActionMailer::Base' + + # ==> ORM configuration + # Load and configure the ORM. Supports :active_record (default) and + # :mongoid (bson_ext recommended) by default. Other ORMs may be + # available as additional gems. + require "devise/orm/active_record" + + # ==> Configuration for any authentication mechanism + # Configure which keys are used when authenticating a user. The default is + # just :email. You can configure it to use [:username, :subdomain], so for + # authenticating a user, both parameters are required. Remember that those + # parameters are used only when authenticating and not when retrieving from + # session. If you need permissions, you should implement that in a before filter. + # You can also supply a hash where the value is a boolean determining whether + # or not authentication should be aborted when the value is not present. + # config.authentication_keys = [:email] + + # Configure parameters from the request object used for authentication. Each entry + # given should be a request method and it will automatically be passed to the + # find_for_authentication method and considered in your model lookup. For instance, + # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. + # The same considerations mentioned for authentication_keys also apply to request_keys. + # config.request_keys = [] + + # Configure which authentication keys should be case-insensitive. + # These keys will be downcased upon creating or modifying a user and when used + # to authenticate or find a user. Default is :email. + config.case_insensitive_keys = [:email] + + # Configure which authentication keys should have whitespace stripped. + # These keys will have whitespace before and after removed upon creating or + # modifying a user and when used to authenticate or find a user. Default is :email. + config.strip_whitespace_keys = [:email] + + # Tell if authentication through request.params is enabled. True by default. + # It can be set to an array that will enable params authentication only for the + # given strategies, for example, `config.params_authenticatable = [:database]` will + # enable it only for database (email + password) authentication. + # config.params_authenticatable = true + + # Tell if authentication through HTTP Auth is enabled. False by default. + # It can be set to an array that will enable http authentication only for the + # given strategies, for example, `config.http_authenticatable = [:database]` will + # enable it only for database authentication. The supported strategies are: + # :database = Support basic authentication with authentication key + password + # config.http_authenticatable = false + + # If 401 status code should be returned for AJAX requests. True by default. + # config.http_authenticatable_on_xhr = true + + # The realm used in Http Basic Authentication. 'Application' by default. + # config.http_authentication_realm = 'Application' + + # It will change confirmation, password recovery and other workflows + # to behave the same regardless if the e-mail provided was right or wrong. + # Does not affect registerable. + # config.paranoid = true + + # By default Devise will store the user in session. You can skip storage for + # particular strategies by setting this option. + # Notice that if you are skipping storage for all authentication paths, you + # may want to disable generating routes to Devise's sessions controller by + # passing skip: :sessions to `devise_for` in your config/routes.rb + config.skip_session_storage = [:http_auth] + + # By default, Devise cleans up the CSRF token on authentication to + # avoid CSRF token fixation attacks. This means that, when using AJAX + # requests for sign in and sign up, you need to get a new CSRF token + # from the server. You can disable this option at your own risk. + # config.clean_up_csrf_token_on_authentication = true + + # When false, Devise will not attempt to reload routes on eager load. + # This can reduce the time taken to boot the app but if your application + # requires the Devise mappings to be loaded during boot time the application + # won't boot properly. + # config.reload_routes = true + + # ==> Configuration for :database_authenticatable + # For bcrypt, this is the cost for hashing the password and defaults to 11. If + # using other algorithms, it sets how many times you want the password to be hashed. + # + # Limiting the stretches to just one in testing will increase the performance of + # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use + # a value less than 10 in other environments. Note that, for bcrypt (the default + # algorithm), the cost increases exponentially with the number of stretches (e.g. + # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). + config.stretches = Rails.env.test? ? 1 : 11 + + # Set up a pepper to generate the hashed password. + # config.pepper = '24d945e9e6ce6d721082b49ff13f608dfe4de1f8a1c46cc976d3101fdf330e32836df1c817ac4aa5610f214e6' + + # Send a notification to the original email when the user's email is changed. + # config.send_email_changed_notification = false + + # Send a notification email when the user's password is changed. + # config.send_password_change_notification = false + + # ==> Configuration for :confirmable + # A period that the user is allowed to access the website even without + # confirming their account. For instance, if set to 2.days, the user will be + # able to access the website for two days without confirming their account, + # access will be blocked just in the third day. Default is 0.days, meaning + # the user cannot access the website without confirming their account. + # config.allow_unconfirmed_access_for = 2.days + + # A period that the user is allowed to confirm their account before their + # token becomes invalid. For example, if set to 3.days, the user can confirm + # their account within 3 days after the mail was sent, but on the fourth day + # their account can't be confirmed with the token any more. + # Default is nil, meaning there is no restriction on how long a user can take + # before confirming their account. + # config.confirm_within = 3.days + + # If true, requires any email changes to be confirmed (exactly the same way as + # initial account confirmation) to be applied. Requires additional unconfirmed_email + # db field (see migrations). Until confirmed, new email is stored in + # unconfirmed_email column, and copied to email column on successful confirmation. + config.reconfirmable = true + + # Defines which key will be used when confirming an account + # config.confirmation_keys = [:email] + + # ==> Configuration for :rememberable + # The time the user will be remembered without asking for credentials again. + # config.remember_for = 2.weeks + + # Invalidates all the remember me tokens when the user signs out. + config.expire_all_remember_me_on_sign_out = true + + # If true, extends the user's remember period when remembered via cookie. + # config.extend_remember_period = false + + # Options to be passed to the created cookie. For instance, you can set + # secure: true in order to force SSL only cookies. + # config.rememberable_options = {} + + # ==> Configuration for :validatable + # Range for password length. + config.password_length = 6..128 + + # Email regex used to validate email formats. It simply asserts that + # one (and only one) @ exists in the given string. This is mainly + # to give user feedback and not to assert the e-mail validity. + config.email_regexp = /\A[^@\s]+@[^@\s]+\z/ + + # ==> Configuration for :timeoutable + # The time you want to timeout the user session without activity. After this + # time the user will be asked for credentials again. Default is 30 minutes. + # config.timeout_in = 30.minutes + + # ==> Configuration for :lockable + # Defines which strategy will be used to lock an account. + # :failed_attempts = Locks an account after a number of failed attempts to sign in. + # :none = No lock strategy. You should handle locking by yourself. + # config.lock_strategy = :failed_attempts + + # Defines which key will be used when locking and unlocking an account + # config.unlock_keys = [:email] + + # Defines which strategy will be used to unlock an account. + # :email = Sends an unlock link to the user email + # :time = Re-enables login after a certain amount of time (see :unlock_in below) + # :both = Enables both strategies + # :none = No unlock strategy. You should handle unlocking by yourself. + # config.unlock_strategy = :both + + # Number of authentication tries before locking an account if lock_strategy + # is failed attempts. + # config.maximum_attempts = 20 + + # Time interval to unlock the account if :time is enabled as unlock_strategy. + # config.unlock_in = 1.hour + + # Warn on the last attempt before the account is locked. + # config.last_attempt_warning = true + + # ==> Configuration for :recoverable + # + # Defines which key will be used when recovering the password for an account + # config.reset_password_keys = [:email] + + # Time interval you can reset your password with a reset password key. + # Don't put a too small interval or your users won't have the time to + # change their passwords. + config.reset_password_within = 6.hours + + # When set to false, does not sign a user in automatically after their password is + # reset. Defaults to true, so a user is signed in automatically after a reset. + # config.sign_in_after_reset_password = true + + # ==> Configuration for :encryptable + # Allow you to use another hashing or encryption algorithm besides bcrypt (default). + # You can use :sha1, :sha512 or algorithms from others authentication tools as + # :clearance_sha1, :authlogic_sha512 (then you should set stretches above to 20 + # for default behavior) and :restful_authentication_sha1 (then you should set + # stretches to 10, and copy REST_AUTH_SITE_KEY to pepper). + # + # Require the `devise-encryptable` gem when using anything other than bcrypt + # config.encryptor = :sha512 + + # ==> Scopes configuration + # Turn scoped views on. Before rendering "sessions/new", it will first check for + # "users/sessions/new". It's turned off by default because it's slower if you + # are using only default views. + # config.scoped_views = false + + # Configure the default scope given to Warden. By default it's the first + # devise role declared in your routes (usually :user). + # config.default_scope = :user + + # Set this configuration to false if you want /users/sign_out to sign out + # only the current scope. By default, Devise signs out all scopes. + config.sign_out_all_scopes = false + + # ==> Navigation configuration + # Lists the formats that should be treated as navigational. Formats like + # :html, should redirect to the sign in page when the user does not have + # access, but formats like :xml or :json, should return 401. + # + # If you have any extra navigational formats, like :iphone or :mobile, you + # should add them to the navigational formats lists. + # + # The "*/*" below is required to match Internet Explorer requests. + # config.navigational_formats = ['*/*', :html] + + # The default HTTP method used to sign out a resource. Default is :delete. + config.sign_out_via = :delete + + # ==> OmniAuth + # Add a new OmniAuth provider. Check the wiki for more information on setting + # up on your models and hooks. + # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' + + # ==> Warden configuration + # If you want to use other strategies, that are not supported by Devise, or + # change the failure app, you can configure them inside the config.warden block. + # + # config.warden do |manager| + # manager.intercept_401 = false + # manager.default_strategies(scope: :user).unshift :some_external_strategy + # end + + # ==> Mountable engine configurations + # When using Devise inside an engine, let's call it `MyEngine`, and this engine + # is mountable, there are some extra configurations to be taken into account. + # The following options are available, assuming the engine is mounted as: + # + # mount MyEngine, at: '/my_engine' + # + # The router that invoked `devise_for`, in the example above, would be: + # config.router_name = :my_engine + # + # When using OmniAuth, Devise cannot automatically set OmniAuth path, + # so you need to do it manually. For the users scope, it would be: + # config.omniauth_path_prefix = '/my_engine/users/auth' +end diff --git a/config/initializers/devise_sign_out_monkeypatch.rb b/config/initializers/devise_sign_out_monkeypatch.rb new file mode 100644 index 0000000..4c87267 --- /dev/null +++ b/config/initializers/devise_sign_out_monkeypatch.rb @@ -0,0 +1,14 @@ +# This monkey patch modifies the redirect to allow it to redirect to a different host on log out. + +Rails.application.config.to_prepare do + class Devise::SessionsController < DeviseController # rubocop:disable Lint/ConstantDefinitionInBlock + def respond_to_on_destroy + # We actually need to hardcode this as Rails default responder doesn't + # support returning empty response on GET request + respond_to do |format| + format.all { head :no_content } + format.any(*navigational_formats) { redirect_to after_sign_out_path_for(resource_name), status: Devise.responder.redirect_status, allow_other_host: true } + end + end + end +end diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb new file mode 100644 index 0000000..af88dfb --- /dev/null +++ b/config/initializers/omniauth.rb @@ -0,0 +1,14 @@ +Rails.application.config.middleware.use OmniAuth::Builder do + provider :oktaoauth, ENV["OKTA_CLIENT_ID"], ENV["OKTA_CLIENT_SECRET"], { + client_options: { + site: ENV["OKTA_ISSUER"], + authorize_url: "#{ENV["OKTA_ISSUER"]}/v1/authorize", + token_url: "#{ENV["OKTA_ISSUER"]}/v1/token", + post_logout_redirect_uri: %|https://#{ENV["SITE_URL"]}/login/logged_out| + }, + issuer: ENV["OKTA_ISSUER"], + redirect_uri: ENV["OKTA_REDIRECT_URI"], + auth_server_id: ENV["OKTA_AUTH_SERVER_ID"], + scope: "openid profile email" + } +end diff --git a/config/routes.rb b/config/routes.rb index eb4e250..d9c211a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,4 @@ Rails.application.routes.draw do - ActiveAdmin.routes(self) # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html # Reveal health status that returns 200 if the app boots with no exceptions, otherwise 500. @@ -12,6 +11,14 @@ end end + # okta login related routes + devise_for :users, class_name: "User", controllers: {omniauth_callbacks: "sessions"} + ActiveAdmin.routes(self) + devise_scope :user do + get "users/sign_out", to: "devise/sessions#destroy", as: :destroy_user_session + end + get "/login/new", to: "login#new" + # Defines the root path route ("/") - get "/", to: redirect("/admin") + get "/", to: redirect("/admin"), as: :root end diff --git a/db/migrate/20241126092151_create_people.rb b/db/migrate/20241126092151_create_people.rb deleted file mode 100644 index 6fa6505..0000000 --- a/db/migrate/20241126092151_create_people.rb +++ /dev/null @@ -1,10 +0,0 @@ -class CreatePeople < ActiveRecord::Migration[7.2] - def change - create_table :people do |t| - t.string :first_name - t.string :last_name - - t.timestamps - end - end -end diff --git a/db/migrate/20241126092151_create_users.rb b/db/migrate/20241126092151_create_users.rb new file mode 100644 index 0000000..20898e9 --- /dev/null +++ b/db/migrate/20241126092151_create_users.rb @@ -0,0 +1,15 @@ +class CreateUsers < ActiveRecord::Migration[7.2] + def change + create_table :users do |t| + t.uuid :sso_guid, null: false + t.string :username + t.string :first_name + t.string :last_name + t.string :email + t.boolean :has_access, default: false + t.timestamps null: false + end + + add_index :users, :email, unique: true + end +end diff --git a/db/migrate/20241126092204_create_authentications.rb b/db/migrate/20241126092204_create_authentications.rb deleted file mode 100644 index ad0a4c0..0000000 --- a/db/migrate/20241126092204_create_authentications.rb +++ /dev/null @@ -1,13 +0,0 @@ -class CreateAuthentications < ActiveRecord::Migration[7.2] - def change - create_table :authentications do |t| - t.integer :person_id - t.string :provider - t.string :uid - t.string :token - t.string :username - - t.timestamps - end - end -end diff --git a/db/schema.rb b/db/schema.rb index 7153ae9..21ebf20 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -12,10 +12,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_01_07_060614) do # These are extensions that must be enabled in order to support this database - enable_extension "pg_stat_statements" - enable_extension "pgcrypto" enable_extension "plpgsql" - enable_extension "uuid-ossp" create_table "active_admin_comments", force: :cascade do |t| t.string "namespace" @@ -38,16 +35,6 @@ t.datetime "updated_at", null: false end - create_table "authentications", force: :cascade do |t| - t.integer "person_id" - t.string "provider" - t.string "uid" - t.string "token" - t.string "username" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - create_table "batches", force: :cascade do |t| t.string "description" t.string "contact" @@ -80,6 +67,7 @@ t.string "gl_code" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "isbn" t.index ["batch_id"], name: "index_gift_cards_on_batch_id" t.index ["issuance_id"], name: "index_gift_cards_on_issuance_id" t.index ["price"], name: "index_gift_cards_on_price" @@ -100,10 +88,15 @@ t.index ["quantity"], name: "index_issuances_on_quantity" end - create_table "people", force: :cascade do |t| + create_table "users", force: :cascade do |t| + t.uuid "sso_guid", null: false + t.string "username" t.string "first_name" t.string "last_name" + t.string "email" + t.boolean "has_access", default: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.index ["email"], name: "index_users_on_email", unique: true end end From e3afcb0ca5d1e2775af3881baa33764a736f0d96 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Sun, 12 Jan 2025 23:59:01 -0500 Subject: [PATCH 67/73] use initializers/devise_sign_out_monkeypatch.rb instead of my logout fix --- config/application.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/config/application.rb b/config/application.rb index d8e3641..2267c62 100644 --- a/config/application.rb +++ b/config/application.rb @@ -31,9 +31,5 @@ class Application < Rails::Application # Send all logs to stdout, which docker reads and sends to datadog. config.logger = Log::Logger.new($stdout) unless Rails.env.test? # we don't need a logger in test env - - config.after_initialize do - load "lib/fix_logout.rb" - end end end From 5d111a237be9e906ca8fbf0d8bf95ab382ec2b46 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 13 Jan 2025 01:27:22 -0500 Subject: [PATCH 68/73] switch to string for sso_guid it was causing problems saving sso_guid values on my local db it should function fine --- db/migrate/20241126092151_create_users.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20241126092151_create_users.rb b/db/migrate/20241126092151_create_users.rb index 20898e9..9f5d41e 100644 --- a/db/migrate/20241126092151_create_users.rb +++ b/db/migrate/20241126092151_create_users.rb @@ -1,7 +1,7 @@ class CreateUsers < ActiveRecord::Migration[7.2] def change create_table :users do |t| - t.uuid :sso_guid, null: false + t.string :sso_guid, null: false t.string :username t.string :first_name t.string :last_name From fb045ba71d563c8d40f0a82f096235195d443b00 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 13 Jan 2025 01:29:26 -0500 Subject: [PATCH 69/73] switch to User --- import.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/import.rb b/import.rb index 1c6cb2f..522bf7f 100644 --- a/import.rb +++ b/import.rb @@ -1,5 +1,8 @@ #=> Batch(id: integer, description: string, regex: string, contact: string, associated_product: string, isbn: string, gl_code: string, dept: string, created_at: datetime, updated_at: datetime) +# User(id: integer, sso_guid: uuid, username: string, first_name: string, last_name: string, email: string, has_access: boolean, created_at: datetime, updated_at: datetime) +system = User.where(sso_guid: "initial", first_name: "Initial", last_name: "Import").first_or_create + Batch.class_eval do attr_accessor :regex end @@ -118,8 +121,6 @@ total = `wc -l "FL_EventCertificate_202411261112.csv"`.split(" ").first.to_i unknown_batch = Batch.last -system = Person.where(first_name: "Initial", last_name: "Import").first_or_create - GiftCard.delete_all Issuance.delete_all From ddbb12c6b5f82ddc44ccf546e1c170cb092fb19a Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 13 Jan 2025 01:30:08 -0500 Subject: [PATCH 70/73] update schema --- db/schema.rb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 21ebf20..618daeb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -12,7 +12,10 @@ ActiveRecord::Schema[7.2].define(version: 2025_01_07_060614) do # These are extensions that must be enabled in order to support this database + enable_extension "pg_stat_statements" + enable_extension "pgcrypto" enable_extension "plpgsql" + enable_extension "uuid-ossp" create_table "active_admin_comments", force: :cascade do |t| t.string "namespace" @@ -35,6 +38,16 @@ t.datetime "updated_at", null: false end + create_table "authentications", force: :cascade do |t| + t.integer "person_id" + t.string "provider" + t.string "uid" + t.string "token" + t.string "username" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "batches", force: :cascade do |t| t.string "description" t.string "contact" @@ -63,7 +76,6 @@ t.datetime "expiration_date" t.integer "registrations_available" t.string "associated_product" - t.decimal "certificate_value" t.string "gl_code" t.datetime "created_at", null: false t.datetime "updated_at", null: false @@ -89,7 +101,7 @@ end create_table "users", force: :cascade do |t| - t.uuid "sso_guid", null: false + t.string "sso_guid", null: false t.string "username" t.string "first_name" t.string "last_name" From 95a100d77a26459f24a4506d0fc0d9fa83bbd8d9 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 13 Jan 2025 17:03:04 -0500 Subject: [PATCH 71/73] update brakeman --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index a1507b8..1b14f26 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -119,7 +119,7 @@ GEM debug_inspector (>= 1.2.0) bootsnap (1.18.4) msgpack (~> 1.2) - brakeman (6.2.2) + brakeman (7.0.0) racc builder (3.3.0) byebug (11.1.3) From 21a7b5d2fcfc346b067134019162efb30ee8eb10 Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 13 Jan 2025 17:38:40 -0500 Subject: [PATCH 72/73] fix import add date --- import.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/import.rb b/import.rb index 522bf7f..f2c5228 100644 --- a/import.rb +++ b/import.rb @@ -162,7 +162,7 @@ gc.registrations_available = row["numberRegistrations"].to_i gc.price = row["certificateValue"].to_d gc.gl_code = row["glCode"] - gc.created_at = DateTime.parse(row["addDate"]) if row["created_at"] + gc.created_at = DateTime.parse(row["addDate"]) if row["addDate"] gc.updated_at = DateTime.parse(row["modifiedDate"]) if row["modifiedDate"] gc.associated_product = row["associatedProduct"] From 66e7d144a9ff5b1fbb3d7041d49d949a52c825cc Mon Sep 17 00:00:00 2001 From: Andrew Roth Date: Mon, 13 Jan 2025 18:04:49 -0500 Subject: [PATCH 73/73] standardrb fixes --- app/admin/api_keys.rb | 2 -- app/admin/batches.rb | 8 ++--- app/admin/dashboard.rb | 1 + app/admin/gift_cards.rb | 15 ++++---- app/admin/issuances.rb | 34 ++++++++----------- .../api/v1/application_controller.rb | 8 ++--- .../api/v1/gift_cards_controller.rb | 3 +- app/controllers/monitors_controller.rb | 2 +- app/models/api_key.rb | 2 +- app/models/batch.rb | 5 ++- app/models/custom_authorization.rb | 1 - app/models/gift_card.rb | 8 ++--- app/models/issuance.rb | 10 +++--- app/models/user.rb | 6 ++-- config/application.rb | 4 +-- config/environments/production.rb | 4 +-- config/initializers/active_admin.rb | 2 +- config/initializers/devise.rb | 6 ++-- config/initializers/omniauth.rb | 2 +- config/initializers/session_store.rb | 4 +-- config/routes.rb | 4 +-- .../20241126092131_create_gift_cards.rb | 2 +- ...1126094659_create_active_admin_comments.rb | 4 +-- db/migrate/20241126110902_create_batches.rb | 2 +- import.rb | 11 +++--- lib/log/logger/formatter.rb | 2 +- lib/log/logger/formatter_readable.rb | 2 +- 27 files changed, 72 insertions(+), 82 deletions(-) diff --git a/app/admin/api_keys.rb b/app/admin/api_keys.rb index 05ba062..505845a 100644 --- a/app/admin/api_keys.rb +++ b/app/admin/api_keys.rb @@ -1,5 +1,4 @@ ActiveAdmin.register ApiKey do - # See permitted parameters documentation: # https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters # @@ -16,5 +15,4 @@ # permitted << :other if params[:action] == 'create' && current_user.admin? # permitted # end - end diff --git a/app/admin/batches.rb b/app/admin/batches.rb index 0c50f73..7c88d2c 100644 --- a/app/admin/batches.rb +++ b/app/admin/batches.rb @@ -6,8 +6,8 @@ # # Uncomment all parameters which should be permitted for assignment # - permit_params :description, :contact, :gift_card_type, :price, :registrations_available, :begin_use_date, :end_use_date, :expiration_date, - :associated_product, :isbn, :gl_code, :dept + permit_params :description, :contact, :gift_card_type, :price, :registrations_available, :begin_use_date, :end_use_date, :expiration_date, + :associated_product, :isbn, :gl_code, :dept index do column :id @@ -43,8 +43,8 @@ input :isbn if GiftCard::PAID_TYPES.include?(f.object.gift_card_type) - input :gl_code - input :dept + input :gl_code + input :dept end end diff --git a/app/admin/dashboard.rb b/app/admin/dashboard.rb index 3824c62..d743303 100644 --- a/app/admin/dashboard.rb +++ b/app/admin/dashboard.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + ActiveAdmin.register_page "Dashboard" do menu priority: 1, label: proc { I18n.t("active_admin.dashboard") } diff --git a/app/admin/gift_cards.rb b/app/admin/gift_cards.rb index af8fab5..31fb49b 100644 --- a/app/admin/gift_cards.rb +++ b/app/admin/gift_cards.rb @@ -1,5 +1,4 @@ ActiveAdmin.register GiftCard do - # See permitted parameters documentation: # https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters # @@ -14,8 +13,8 @@ # permitted << :other if params[:action] == 'create' && current_user.admin? # permitted # end - - actions :all, :except => [:new] + + actions :all, except: [ :new ] index do selectable_column @@ -25,14 +24,14 @@ column :issuance column :certificate number_column "Value", :certificate_value, as: :currency, unit: "$", sortable: :certificate_value - #column "Value", :certificate_value do |gift_card| + # column "Value", :certificate_value do |gift_card| # number_to_currency(gift_card.certificate_value) - #end + # end column :expiration_date column "Reg'ns Avail" do |gift_card| - gift_card.registrations_available - end - column "Assoc'd Prod", :associated_product, sortable: :associated_product do |gift_card| + gift_card.registrations_available + end + column "Assoc'd Prod", :associated_product, sortable: :associated_product do |gift_card| gift_card.associated_product end column :gl_code diff --git a/app/admin/issuances.rb b/app/admin/issuances.rb index 1a4d974..22cd231 100644 --- a/app/admin/issuances.rb +++ b/app/admin/issuances.rb @@ -1,5 +1,5 @@ ActiveAdmin.register Issuance do - #actions :all, except: [:edit, :destroy] + # actions :all, except: [:edit, :destroy] # See permitted parameters documentation: # https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters @@ -15,7 +15,7 @@ # permitted << :other if params[:action] == 'create' && current_user.admin? # permitted # end - + filter :creator filter :issuer filter :gift_card_type @@ -54,42 +54,38 @@ f.semantic_errors inputs do - input :batch, collection: Batch.order(created_at: :desc).collect{ |batch| [batch.to_s, batch.id] } + input :batch, collection: Batch.order(created_at: :desc).collect { |batch| [ batch.to_s, batch.id ] } input :quantity actions do unless issuance.issued? f.action :submit, as: :button, label: "Save and Preview Issuance" end - #f.action :cancel, as: :link, label: 'Cancel', class: 'cancel-link' - cancel_link -=begin - if %w(new create).include?(params[:action]) - submit_tag(:create, "Preview Issuance") - elsif params[:action] == "edit" && issuance.preview? - submit_tag(:update, "Preview Issuance") - end + # f.action :cancel, as: :link, label: 'Cancel', class: 'cancel-link' cancel_link -=end + # if %w(new create).include?(params[:action]) + # submit_tag(:create, "Preview Issuance") + # elsif params[:action] == "edit" && issuance.preview? + # submit_tag(:update, "Preview Issuance") + # end + # cancel_link end end end -=begin - action_item :destroy, only: :show, if: -> { resource.preview? } do - link_to 'Delete Issuance', issue_admin_issuance_path(issuance), method: :destroy, data: { confirm: 'Are you sure?' } - end -=end + # action_item :destroy, only: :show, if: -> { resource.preview? } do + # link_to 'Delete Issuance', issue_admin_issuance_path(issuance), method: :destroy, data: { confirm: 'Are you sure?' } + # end action_item :issue, only: :show, if: -> { resource.previewing? } do - link_to 'Issue Gift Cards', issue_admin_issuance_path(issuance), method: :put, data: { confirm: 'Are you sure?' } + link_to "Issue Gift Cards", issue_admin_issuance_path(issuance), method: :put, data: { confirm: "Are you sure?" } end member_action :issue, method: :put do resource.issue! resource.update(issuer_id: current_admin_user.id, issued_at: Time.now) redirect_to admin_issuance_path(resource), notice: "Gift cards issued!" - end + end before_create do |issuance| issuance.creator = current_admin_user diff --git a/app/controllers/api/v1/application_controller.rb b/app/controllers/api/v1/application_controller.rb index 8168cb9..aa227ff 100644 --- a/app/controllers/api/v1/application_controller.rb +++ b/app/controllers/api/v1/application_controller.rb @@ -4,15 +4,15 @@ class Api::V1::ApplicationController < ApplicationController respond_to :json skip_forgery_protection - #skip_before_action :require_login, raise: false - #skip_before_action :check_url + # skip_before_action :require_login, raise: false + # skip_before_action :check_url protected def restrict_access api_key = ApiKey.find_by(access_token: oauth_access_token) unless api_key - render json: {error: "You either didn't pass in an access token, or the token you did pass in was wrong."}, + render json: { error: "You either didn't pass in an access token, or the token you did pass in was wrong." }, status: :unauthorized, callback: params[:callback] return false @@ -26,7 +26,7 @@ def current_user end def oauth_access_token - @oauth_access_token ||= (params[:access_token] || oauth_access_token_from_header) + @oauth_access_token ||= params[:access_token] || oauth_access_token_from_header end # grabs access_token from header if one is present diff --git a/app/controllers/api/v1/gift_cards_controller.rb b/app/controllers/api/v1/gift_cards_controller.rb index f36a07e..ee0d2a5 100644 --- a/app/controllers/api/v1/gift_cards_controller.rb +++ b/app/controllers/api/v1/gift_cards_controller.rb @@ -1,6 +1,6 @@ class Api::V1::GiftCardsController < Api::V1::ApplicationController def show - gift_card = GiftCard.find_by(certificate: params[:id]) + gift_card = GiftCard.find_by(certificate: params[:id]) render json: gift_card end @@ -21,4 +21,3 @@ def update render json: gift_card end end - diff --git a/app/controllers/monitors_controller.rb b/app/controllers/monitors_controller.rb index 724758f..477370d 100644 --- a/app/controllers/monitors_controller.rb +++ b/app/controllers/monitors_controller.rb @@ -1,5 +1,5 @@ class MonitorsController < ApplicationController - #skip_before_action :require_login, raise: false + # skip_before_action :require_login, raise: false def lb User.first diff --git a/app/models/api_key.rb b/app/models/api_key.rb index 8f995de..cde4a09 100644 --- a/app/models/api_key.rb +++ b/app/models/api_key.rb @@ -3,7 +3,7 @@ class ApiKey < ApplicationRecord after_initialize :generate_access_token def self.ransackable_attributes(auth_object = nil) - ["access_token", "created_at", "id", "id_value", "updated_at", "user"] + [ "access_token", "created_at", "id", "id_value", "updated_at", "user" ] end private diff --git a/app/models/batch.rb b/app/models/batch.rb index ddcdae2..e99e6cd 100644 --- a/app/models/batch.rb +++ b/app/models/batch.rb @@ -1,5 +1,4 @@ class Batch < ApplicationRecord - validates_presence_of :description, :gift_card_type, :price, :registrations_available, :begin_use_date, :end_use_date, :expiration_date, :associated_product, :isbn validates_presence_of :gl_code, :dept, presence: true, if: -> { GiftCard::PAID_TYPES.include?(gift_card_type) } @@ -13,10 +12,10 @@ def to_s end def self.ransackable_attributes(auth_object = nil) - %w(description gift_card_type price registrations_available begin_use_date end_use_date expiration_date associated_product isbn gl_code dept contact) + %w[description gift_card_type price registrations_available begin_use_date end_use_date expiration_date associated_product isbn gl_code dept contact] end def self.ransackable_associations(auth_object = nil) - %w(issuances) + %w[issuances] end end diff --git a/app/models/custom_authorization.rb b/app/models/custom_authorization.rb index 59dfde9..9adfdc4 100644 --- a/app/models/custom_authorization.rb +++ b/app/models/custom_authorization.rb @@ -10,4 +10,3 @@ def authorized?(action, subject = nil) end end end - diff --git a/app/models/gift_card.rb b/app/models/gift_card.rb index 45a3d64..3e27ed7 100644 --- a/app/models/gift_card.rb +++ b/app/models/gift_card.rb @@ -3,7 +3,7 @@ class GiftCard < ApplicationRecord TYPE_PAID_FULL_PRICE = "paid_full_price" TYPE_PAID_OTHER = "paid_other" TYPE_DEPT = "type_dept" - PAID_TYPES = [TYPE_PAID_HALF_PRICE, TYPE_PAID_FULL_PRICE, TYPE_PAID_OTHER] + PAID_TYPES = [ TYPE_PAID_HALF_PRICE, TYPE_PAID_FULL_PRICE, TYPE_PAID_OTHER ] TYPE_DESCRIPTIONS = { TYPE_PAID_HALF_PRICE => "Paid, Half", @@ -18,11 +18,11 @@ class GiftCard < ApplicationRecord validates :certificate, uniqueness: true def self.ransackable_attributes(auth_object = nil) - %w(certificate expiration_date registrations_available associated_product certificate_value gl_code created_at updated_at - issuance_id gift_card_type_id isbn department_number batch batch_id price gift_card_type) + %w[certificate expiration_date registrations_available associated_product certificate_value gl_code created_at updated_at + issuance_id gift_card_type_id isbn department_number batch batch_id price gift_card_type] end def self.ransackable_associations(auth_object = nil) - %w(batch batch_id issuance issuance_id gift_card_type_id) + %w[batch batch_id issuance issuance_id gift_card_type_id] end end diff --git a/app/models/issuance.rb b/app/models/issuance.rb index d467228..9abcb5b 100644 --- a/app/models/issuance.rb +++ b/app/models/issuance.rb @@ -45,7 +45,7 @@ def to_s def create_gift_cards allocated_certificates.split(CERTIFICATE_DISPLAY_SEPARATOR).each do |certificate| - gift_card = gift_cards.where(certificate: certificate).first_or_initialize + gift_card = gift_cards.where(certificate: certificate).first_or_initialize gift_card.registrations_available = batch.registrations_available gift_card.price = price gift_card.expiration_date = batch.expiration_date @@ -82,21 +82,21 @@ def largest_existing_number_in_certificate # pulling all allocated certificate ids instead of a regex isn't ideal, but there shouldn't be many, if any, times there # are previewed gift cards issuances while another one is being previewed - existing_matching_certificates += Issuance.previewing.where.not(id: self.id).pluck(:allocated_certificates).collect do |allocated_certificates| + existing_matching_certificates += Issuance.previewing.where.not(id: id).pluck(:allocated_certificates).collect do |allocated_certificates| allocated_certificates.to_s.split(CERTIFICATE_DISPLAY_SEPARATOR).find_all { |certificate| certificate =~ numbering_regex } end.flatten - existing_matching_certificates.collect{ |certificate| + existing_matching_certificates.collect { |certificate| certificate =~ numbering_regex $1.to_i }.max || 0 end def self.ransackable_attributes(auth_object = nil) - %w(status created_at updated_at creator_id issuer_id quantity allocated_certificates numbering gift_cards_id) + %w[status created_at updated_at creator_id issuer_id quantity allocated_certificates numbering gift_cards_id] end def self.ransackable_associations(auth_object = nil) - %w(creator issuer gift_cards) + %w[creator issuer gift_cards] end end diff --git a/app/models/user.rb b/app/models/user.rb index e59ab49..7b5fd9e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,10 +1,10 @@ class User < ApplicationRecord - devise :omniauthable, omniauth_providers: [:oktaoauth] + devise :omniauthable, omniauth_providers: [ :oktaoauth ] strip_attributes only: %i[username first_name last_name email] def self.ransackable_attributes(auth_object = nil) - [:first_name, :last_name] + [ :first_name, :last_name ] end def self.ransackable_associations(auth_object = nil) @@ -32,7 +32,7 @@ def apply_auth_hash(auth_hash) end def name - [first_name, last_name].join(" ") + [ first_name, last_name ].join(" ") end alias_method :full_name, :name end diff --git a/config/application.rb b/config/application.rb index 2267c62..57f4f88 100644 --- a/config/application.rb +++ b/config/application.rb @@ -6,7 +6,7 @@ # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) -require_relative '../lib/log/logger' +require_relative "../lib/log/logger" module FamilylifeGiftCards class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. @@ -17,7 +17,7 @@ class Application < Rails::Application # Common ones are `templates`, `generators`, or `middleware`, for example. config.autoload_lib(ignore: %w[assets tasks]) - redis_conf = YAML.safe_load(ERB.new(File.read(Rails.root.join("config", "redis.yml"))).result, permitted_classes: [Symbol], aliases: true)["cache"] + redis_conf = YAML.safe_load(ERB.new(File.read(Rails.root.join("config", "redis.yml"))).result, permitted_classes: [ Symbol ], aliases: true)["cache"] redis_conf[:url] = "redis://" + redis_conf[:host] + "/" + redis_conf[:db].to_s config.cache_store = :redis_cache_store, redis_conf diff --git a/config/environments/production.rb b/config/environments/production.rb index abc6343..044fef3 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -56,7 +56,7 @@ # .then { |logger| ActiveSupport::TaggedLogging.new(logger) } # Prepend all log lines with the following tags. - config.log_tags = [:request_id] + config.log_tags = [ :request_id ] # "info" includes generic and useful information about system operation, but avoids logging too much # information to avoid inadvertent exposure of personally identifiable information (PII). If you @@ -82,6 +82,6 @@ # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` # ] # Skip DNS rebinding protection for the default health check endpoint. - config.host_authorization = {exclude: ->(request) { request.path == "/monitors/lb" }} + config.host_authorization = { exclude: ->(request) { request.path == "/monitors/lb" } } config.hosts << ENV.fetch("SITE_HOST") end diff --git a/config/initializers/active_admin.rb b/config/initializers/active_admin.rb index c6ae19f..cca286a 100644 --- a/config/initializers/active_admin.rb +++ b/config/initializers/active_admin.rb @@ -174,7 +174,7 @@ # You can exclude possibly sensitive model attributes from being displayed, # added to forms, or exported by default by ActiveAdmin # - config.filter_attributes = [:encrypted_password, :password, :password_confirmation] + config.filter_attributes = [ :encrypted_password, :password, :password_confirmation ] # == Localize Date/Time Format # diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 26ef9f0..d6a1629 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -52,12 +52,12 @@ # Configure which authentication keys should be case-insensitive. # These keys will be downcased upon creating or modifying a user and when used # to authenticate or find a user. Default is :email. - config.case_insensitive_keys = [:email] + config.case_insensitive_keys = [ :email ] # Configure which authentication keys should have whitespace stripped. # These keys will have whitespace before and after removed upon creating or # modifying a user and when used to authenticate or find a user. Default is :email. - config.strip_whitespace_keys = [:email] + config.strip_whitespace_keys = [ :email ] # Tell if authentication through request.params is enabled. True by default. # It can be set to an array that will enable params authentication only for the @@ -88,7 +88,7 @@ # Notice that if you are skipping storage for all authentication paths, you # may want to disable generating routes to Devise's sessions controller by # passing skip: :sessions to `devise_for` in your config/routes.rb - config.skip_session_storage = [:http_auth] + config.skip_session_storage = [ :http_auth ] # By default, Devise cleans up the CSRF token on authentication to # avoid CSRF token fixation attacks. This means that, when using AJAX diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index af88dfb..e1e459d 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -4,7 +4,7 @@ site: ENV["OKTA_ISSUER"], authorize_url: "#{ENV["OKTA_ISSUER"]}/v1/authorize", token_url: "#{ENV["OKTA_ISSUER"]}/v1/token", - post_logout_redirect_uri: %|https://#{ENV["SITE_URL"]}/login/logged_out| + post_logout_redirect_uri: %(https://#{ENV["SITE_URL"]}/login/logged_out) }, issuer: ENV["OKTA_ISSUER"], redirect_uri: ENV["OKTA_REDIRECT_URI"], diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index 9af0b6b..d3cddd0 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -2,6 +2,6 @@ require "redis" -redis_conf = YAML.safe_load(ERB.new(File.read(Rails.root.join("config", "redis.yml"))).result, permitted_classes: [Symbol], aliases: true)["session"] +redis_conf = YAML.safe_load(ERB.new(File.read(Rails.root.join("config", "redis.yml"))).result, permitted_classes: [ Symbol ], aliases: true)["session"] -Rails.application.config.session_store :redis_store, servers: [redis_conf], expire_after: 30.days +Rails.application.config.session_store :redis_store, servers: [ redis_conf ], expire_after: 30.days diff --git a/config/routes.rb b/config/routes.rb index d9c211a..3438cc6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,12 +7,12 @@ namespace :api do namespace :v1 do - resources :gift_cards, only: [:show, :update] + resources :gift_cards, only: [ :show, :update ] end end # okta login related routes - devise_for :users, class_name: "User", controllers: {omniauth_callbacks: "sessions"} + devise_for :users, class_name: "User", controllers: { omniauth_callbacks: "sessions" } ActiveAdmin.routes(self) devise_scope :user do get "users/sign_out", to: "devise/sessions#destroy", as: :destroy_user_session diff --git a/db/migrate/20241126092131_create_gift_cards.rb b/db/migrate/20241126092131_create_gift_cards.rb index ae87ce3..d69b969 100644 --- a/db/migrate/20241126092131_create_gift_cards.rb +++ b/db/migrate/20241126092131_create_gift_cards.rb @@ -3,7 +3,7 @@ def change create_table :gift_cards do |t| t.integer :issuance_id t.integer :batch_id - t.decimal :price, :precision => 8, :scale => 2 + t.decimal :price, precision: 8, scale: 2 t.string :gift_card_type t.string :certificate t.datetime :expiration_date diff --git a/db/migrate/20241126094659_create_active_admin_comments.rb b/db/migrate/20241126094659_create_active_admin_comments.rb index 73e3e00..af6b49f 100644 --- a/db/migrate/20241126094659_create_active_admin_comments.rb +++ b/db/migrate/20241126094659_create_active_admin_comments.rb @@ -2,12 +2,12 @@ class CreateActiveAdminComments < ActiveRecord::Migration[7.2] def self.up create_table :active_admin_comments do |t| t.string :namespace - t.text :body + t.text :body t.references :resource, polymorphic: true t.references :author, polymorphic: true t.timestamps end - add_index :active_admin_comments, [:namespace] + add_index :active_admin_comments, [ :namespace ] end def self.down diff --git a/db/migrate/20241126110902_create_batches.rb b/db/migrate/20241126110902_create_batches.rb index 28a3787..2b89817 100644 --- a/db/migrate/20241126110902_create_batches.rb +++ b/db/migrate/20241126110902_create_batches.rb @@ -4,7 +4,7 @@ def change t.string :description t.string :contact t.string :gift_card_type - t.decimal :price, :precision => 8, :scale => 2 + t.decimal :price, precision: 8, scale: 2 t.integer :registrations_available t.datetime :begin_use_date diff --git a/import.rb b/import.rb index f2c5228..17ef2cf 100644 --- a/import.rb +++ b/import.rb @@ -144,16 +144,15 @@ i = 0 CSV.foreach("FL_EventCertificate_202411261112.csv", headers: true) do |row| - puts("[#{i += 1}/#{total}, #{(i / total.to_f * 100).round(2)}%]") # ex: # # - #byebug - batch = all_batches.detect{ |batch| /#{regexes[batch.description]}/.match(row["certificateId"]) } || unknown_batch + # byebug + batch = all_batches.detect { |batch| /#{regexes[batch.description]}/.match(row["certificateId"]) } || unknown_batch issuance = initial_issuances[batch] - #gc = GiftCard.where(certificate_id: row["certificateId"]).first_or_initialize + # gc = GiftCard.where(certificate_id: row["certificateId"]).first_or_initialize gc = GiftCard.new gc.certificate = row["certificateId"] gc.issuance = issuance @@ -165,7 +164,7 @@ gc.created_at = DateTime.parse(row["addDate"]) if row["addDate"] gc.updated_at = DateTime.parse(row["modifiedDate"]) if row["modifiedDate"] gc.associated_product = row["associatedProduct"] - + gift_cards << gc if gift_cards.length >= 10000 @@ -176,6 +175,6 @@ GiftCard.import(gift_cards) -Issuance.where(quantity: [nil, 0]).each do |i| +Issuance.where(quantity: [ nil, 0 ]).each do |i| i.update(quantity: i.gift_cards.count) end diff --git a/lib/log/logger/formatter.rb b/lib/log/logger/formatter.rb index a78fb05..3c9511a 100644 --- a/lib/log/logger/formatter.rb +++ b/lib/log/logger/formatter.rb @@ -8,7 +8,7 @@ def initialize(*args) def _call(severity, time, progname, data) request = data.delete(:request) if request - data[:network] = {client: {ip: request.ip}} + data[:network] = { client: { ip: request.ip } } data[:amzn_trace_id] = request.headers["X-Amzn-Trace-Id"] data[:request_id] = request.uuid end diff --git a/lib/log/logger/formatter_readable.rb b/lib/log/logger/formatter_readable.rb index b2b204f..4d96dea 100644 --- a/lib/log/logger/formatter_readable.rb +++ b/lib/log/logger/formatter_readable.rb @@ -7,7 +7,7 @@ def initialize(*args) def _call(severity, time, progname, data) data.delete(:request) - super(severity, time, progname, data) + super end end end