remove mailing system (#763)

This commit is contained in:
Echo 2026-01-03 09:25:47 -05:00 committed by GitHub
parent 5f45d9f7af
commit c62cb0f7da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 37 additions and 480 deletions

View file

@ -1,28 +0,0 @@
module My
class MailingAddressesController < ApplicationController
before_action :ensure_current_user
def show
@user = current_user
# Generate OTC if it doesn't exist
if params[:from_fillout]
sleep 1 # unfortunate hack to make sure the job runs after airtable gets the data
FetchMailingAddressJob.perform_now(@user.id)
else
@user.update_column(:mailing_address_otc, SecureRandom.hex(8))
end
end
def edit
current_user.update_column(:mailing_address_otc, SecureRandom.hex(8))
redirect_to "https://forms.hackclub.com/t/mo6hitqC6Vus?otc=#{current_user.mailing_address_otc}", allow_other_host: true
end
private
def ensure_current_user
redirect_to root_path, alert: "You must be logged in to view this page" unless current_user
end
end
end

View file

@ -1,17 +0,0 @@
module My
class MailroomController < ApplicationController
before_action :ensure_current_user
def index
@user = current_user
@physical_mails = @user.physical_mails.order(created_at: :desc)
@has_mailing_address = @user.mailing_address.present?
end
private
def ensure_current_user
redirect_to root_path, alert: "You must be logged in to view this page" unless current_user
end
end
end

View file

@ -1,12 +0,0 @@
class AttemptToDeliverPhysicalMailJob < ApplicationJob
queue_as :literally_whenever
include HasEnqueueControl
enqueue_limit 1
def perform
PhysicalMail.pending_delivery.find_each do |mail|
mail.deliver!
end
end
end

View file

@ -1,44 +0,0 @@
class CheckStreakPhysicalMailJob < ApplicationJob
queue_as :literally_whenever
include GoodJob::ActiveJobExtensions::Concurrency
good_job_control_concurrency_with(
total_limit: 1,
key: -> { "check_streak_physical_mail_job" },
drop: true
)
def perform
streaks = Heartbeat.daily_streaks_for_users(users_with_recent_heartbeats)
over_7_day_streaks = streaks.select { |_, streak| streak > 7 }.keys
existing_user_ids = PhysicalMail.going_out
.where(mission_type: :first_time_7_streak)
.where(user_id: over_7_day_streaks)
.pluck(:user_id)
.to_set
over_7_day_streaks.each do |user_id|
next if existing_user_ids.include?(user_id)
user = User.find(user_id)
# Create the physical mail record
PhysicalMail.create!(
user: user,
mission_type: :first_time_7_streak,
status: :pending
)
end
end
private
def users_with_recent_heartbeats
Heartbeat.where(time: 1.hour.ago..Time.current)
.distinct
.pluck(:user_id)
end
end

View file

@ -1,31 +0,0 @@
class FetchMailingAddressJob < ApplicationJob
queue_as :default
Table = Norairrecord.table(ENV["ADDRESS_AIRTABLE_PAT"], "appEC3w8nxAvCwAjL", "tbldZMvkUWGkkteQu")
def perform(user_id)
user = User.find(user_id)
return unless user.mailing_address_otc.present?
# Search Airtable for the address with this OTC
records = Table.all(filter: "{OTC} = '#{user.mailing_address_otc}'")
return if records.empty?
address_data = records.first.fields
# Create or update the mailing address
mailing_address = user.mailing_address || user.build_mailing_address
mailing_address.update!(
first_name: address_data["first_name"],
last_name: address_data["last_name"],
line_1: address_data["line_1"],
line_2: address_data["line_2"],
city: address_data["city"],
state: address_data["state"],
zip_code: address_data["zip_code"],
country: address_data["country"]
)
records.first.destroy
end
end

View file

@ -1,27 +0,0 @@
class MailingAddress < ApplicationRecord
has_paper_trail
belongs_to :user
encrypts :first_name, deterministic: true
encrypts :last_name, deterministic: true
encrypts :zip_code, deterministic: true
encrypts :line_1, deterministic: true
encrypts :line_2, deterministic: true
encrypts :city, deterministic: true
encrypts :state, deterministic: true
encrypts :country, deterministic: true
after_save :update_user_country_code
private
def update_user_country_code
return unless country.present?
# Find the country by name and get its ISO code
country_obj = ISO3166::Country.find_country_by_any_name(country)
return unless country_obj
user.update_column(:country_code, country_obj.alpha2)
end
end

View file

@ -1,123 +0,0 @@
class PhysicalMail < ApplicationRecord
belongs_to :user
include PublicActivity::Model
# tracked only: [ :update ], owner: :user, params: {
# mission_type: ->(controller, model) { model.mission_type },
# humanized_mission_type: ->(controller, model) { model.humanized_mission_type }
# }
after_create :create_streak_activity, if: :first_time_7_streak?
after_update :create_sent_activity, if: :became_sent?
scope :going_out, -> { where(status: :pending).or(where(status: :sent)) }
enum :status, {
pending: 0,
sent: 1,
failed: 2
}
enum :mission_type, {
admin_mail: 0,
first_time_7_streak: 1
}
def self.instant_mission_types
%w[first_time_7_streak]
end
scope :pending_delivery, -> {
where(status: :pending)
.joins(:user)
.joins("INNER JOIN mailing_addresses ON mailing_addresses.user_id = users.id")
}
def link_to_theseus
return nil if theseus_id.nil?
"https://hack.club/#{theseus_id}"
end
def humanized_mission_type
return "Your first 7-day streak" if first_time_7_streak?
mission_type.titleize
end
def deliver!
return if status == :sent || theseus_id.present?
slug = "hackatime-#{mission_type.to_s.gsub("_", "-")}"
endpoint = if self.class.instant_mission_types.include?(mission_type)
"https://mail.hackclub.com/api/v1/letter_queues/instant/#{slug}-instant"
else
"https://mail.hackclub.com/api/v1/letter_queues/#{slug}"
end
flavors = FlavorText.compliment
flavors.concat(FlavorText.rare_compliment) if rand(10) == 0
return nil unless user.mailing_address.present?
# authorization: Bearer <token>
response = HTTP.auth("Bearer #{ENV["MAIL_HACKCLUB_TOKEN"]}").post(endpoint, json: {
recipient_email: user.email_addresses.first.email,
address: {
first_name: user.mailing_address.first_name,
last_name: user.mailing_address.last_name,
line_1: user.mailing_address.line_1,
line_2: user.mailing_address.line_2,
city: user.mailing_address.city,
state: user.mailing_address.state,
postal_code: user.mailing_address.zip_code,
country: user.mailing_address.country
},
rubber_stamps: flavors.sample,
idempotency_key: "physical_mail_#{id}",
metadata: {
attributes: attributes
}
})
if response.status.success?
data = JSON.parse(response.body.to_s)
puts "Successfully delivered physical mail: #{data["id"]}"
update(status: :sent, theseus_id: data["id"])
else
update(status: :failed)
raise "Failed to deliver physical mail: #{response.body}"
end
rescue OpenSSL::SSL::SSLError => e
Rails.logger.warn "SSL error during mail delivery (request likely succeeded): #{e.message}"
update(status: :sent)
rescue => e
update(status: :failed)
raise e
end
private
def user_address
user.mailing_address
end
def became_sent?
saved_change_to_status? && status == "sent" && status_before_last_save == "pending"
end
def create_streak_activity
create_activity :first_streak_achieved, owner: user, params: {
mission_type: mission_type,
humanized_mission_type: humanized_mission_type
}
end
def create_sent_activity
create_activity :mail_sent, owner: user, params: {
mission_type: mission_type,
humanized_mission_type: humanized_mission_type
}
end
end

View file

@ -94,8 +94,7 @@ class User < ApplicationRecord
has_many :email_verification_requests, dependent: :destroy
has_many :sign_in_tokens, dependent: :destroy
has_many :project_repo_mappings
has_one :mailing_address, dependent: :destroy
has_many :physical_mails
has_many :hackatime_heartbeats,
foreign_key: :user_id,

View file

@ -44,7 +44,7 @@ class AnonymizeUserService
username: "deleted_user_#{user.id}",
uses_slack_status: false,
country_code: nil,
mailing_address_otc: nil,
deprecated_name: nil
)
end
@ -56,7 +56,7 @@ class AnonymizeUserService
user.email_verification_requests.destroy_all
user.wakatime_mirrors.destroy_all
user.project_repo_mappings.destroy_all
user.mailing_address&.destroy
user.heartbeats.destroy_all
user.access_grants.destroy_all

View file

@ -1,56 +0,0 @@
<% content_for :title do %>
My Mailing Address
<% end %>
<div class="min-h-screen bg-darker text-white">
<div class="max-w-6xl mx-auto px-6 py-8">
<header class="mb-8">
<h1 class="text-3xl font-bold text-primary mb-2">My Mailing Address</h1>
<p class="text-secondary">Manage your shipping information for physical mail</p>
</header>
<% if @user.mailing_address.present? %>
<section class="bg-dark rounded-lg p-6 mb-8">
<div class="flex items-center gap-3 mb-4">
<div class="w-8 h-8 bg-green rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
</div>
<h2 class="text-xl font-bold text-green">Address on File</h2>
</div>
<div class="bg-darkless rounded-lg p-6 border-l-4 border-primary">
<div class="space-y-2 text-lg">
<p class="font-semibold text-white">
<%= h(@user.mailing_address.first_name) %> <%= h(@user.mailing_address.last_name) %>
</p>
<p class="text-secondary"><%= h(@user.mailing_address.line_1) %></p>
<% if @user.mailing_address.line_2.present? %>
<p class="text-secondary"><%= h(@user.mailing_address.line_2) %></p>
<% end %>
<p class="text-secondary">
<%= h(@user.mailing_address.city) %>, <%= h(@user.mailing_address.state) %> <%= h(@user.mailing_address.zip_code) %>
</p>
<p class="text-secondary font-semibold"><%= h(@user.mailing_address.country) %></p>
</div>
</div>
</section>
<% else %>
<section class="bg-orange bg-opacity-10 border border-orange rounded-lg p-6 mb-8">
<div class="flex items-center gap-3 mb-3">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/83/Emergency_Light.gif" alt="Emergency Light" class="w-8 h-8">
<h2 class="text-xl font-bold text-dark">No Address Added!</h2>
</div>
<p class="text-lg text-dark">You need to add your mailing address to receive physical mail and rewards.</p>
</section>
<% end %>
<div class="flex justify-center">
<%= link_to @user.mailing_address.present? ? "Update this address" : "Add your address",
edit_my_mailing_address_path,
class: "bg-primary hover:bg-red text-white px-8 py-3 rounded-lg text-lg font-semibold transition-colors inline-flex items-center gap-2" %>
</div>
</div>
</div>

View file

@ -1,87 +0,0 @@
<% content_for :title do %>
Mailroom
<% end %>
<div class="min-h-screen bg-darker text-white">
<div class="max-w-6xl mx-auto px-6 py-8">
<header class="mb-8">
<h1 class="text-4xl font-bold text-primary mb-2">📮 The Mailroom</h1>
<p class="text-secondary text-lg">Track your physical mail and rewards</p>
</header>
<% if @has_mailing_address %>
<section class="bg-green/50 border border-green rounded-lg p-6 mb-8">
<div class="flex items-center gap-3 mb-3">
<div class="w-8 h-8 bg-green rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
</div>
<h2 class="text-xl font-bold text-green">Address Verified</h2>
</div>
<p class="text-lg mb-4">
Your mailing address is set up and ready to receive mail!
</p>
<%# link_to my_mailing_address_path, class: "text-cyan hover:text-blue underline font-semibold" do %>
<!-- View my mailing address-->
<%# end %>
</section>
<% else %>
<section class="bg-orange bg-opacity-10 border border-orange rounded-lg p-6 mb-8">
<div class="flex items-center gap-3 mb-3">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/83/Emergency_Light.gif" alt="Emergency Light" class="w-8 h-8">
<h2 class="text-xl font-bold text-dark">No Address Added!</h2>
</div>
<p class="text-lg text-dark">You need to add your mailing address to receive physical mail and rewards.</p>
<%= link_to "Add my address", edit_my_mailing_address_path,
class: "bg-primary hover:bg-red text-white px-6 py-3 rounded-lg font-semibold transition-colors inline-block" %>
</section>
<% end %>
<section class="bg-dark rounded-lg p-6">
<h2 class="text-2xl font-bold mb-6">📦 My Mail</h2>
<% if @physical_mails.any? %>
<div class="space-y-6">
<% @physical_mails.each do |mail| %>
<% theseus_link = mail.link_to_theseus %>
<div class="bg-darkless rounded-lg p-6 border border-darkless hover:border-primary transition-colors relative group">
<% if theseus_link %>
<a href="<%= theseus_link %>" target="_blank" class="absolute inset-0 z-10"></a>
<% end %>
<div class="relative z-20">
<div class="flex items-start justify-between mb-3">
<h3 class="text-xl font-bold text-white">
<%= mail.humanized_mission_type %>
<% if theseus_link %>
<span class="text-cyan ml-2 group-hover:translate-x-1 transition-transform inline-block">↗</span>
<% end %>
</h3>
<div class="px-3 py-1 bg-primary bg-opacity-20 text-white rounded-full text-sm font-semibold">
<%= mail.status.titleize %>
</div>
</div>
<div class="flex flex-col sm:flex-row sm:items-center gap-2 text-secondary mb-3">
<span>📅 Created: <%= mail.created_at.strftime("%B %d, %Y") %></span>
</div>
<% if theseus_link %>
<div class="pt-3 border-t border-darkless">
<a href="<%= theseus_link %>" target="_blank" class="text-cyan hover:text-blue underline font-semibold relative z-30">
View on mail.hackclub.com
</a>
</div>
<% end %>
</div>
</div>
<% end %>
</div>
<% else %>
<div class="text-center py-12">
<div class="text-6xl mb-4">📪</div>
<h3 class="text-xl font-bold text-secondary mb-2">No mail yet</h3>
<p class="text-secondary">Keep coding to earn rewards and get mail!</p>
</div>
<% end %>
</section>
</div>
</div>

View file

@ -69,11 +69,6 @@
Extensions
<% end %>
</div>
<div>
<%# link_to my_mailroom_path, class: "block px-2 py-1 rounded-lg transition #{current_page?(my_mailroom_path) ? 'bg-primary/50 text-primary' : 'hover:bg-[#23272a]'}", data: { action: "click->nav#clickLink" } do %>
<!-- Mailroom-->
<%# end %>
</div>
<div>
<%= link_to my_settings_path, class: "block px-2 py-1 rounded-lg transition #{current_page?(my_settings_path) ? 'bg-primary/50 text-primary' : 'hover:bg-[#23272a]'}", data: { action: "click->nav#clickLink" } do %>
Settings

View file

@ -110,14 +110,7 @@ Rails.application.configure do
class: "Cache::HeartbeatCountsJob",
kwargs: { force_reload: true }
},
check_streak_physical_mail: {
cron: "0 * * * *", # Run before AttemptToDeliverPhysicalMailJob
class: "CheckStreakPhysicalMailJob"
},
attempt_to_deliver_physical_mail: {
cron: "5 * * * *", # Run after physical mail is created
class: "AttemptToDeliverPhysicalMailJob"
},
geocode_users_without_country: {
cron: "7 * * * *",
class: "GeocodeUsersWithoutCountryJob"

View file

@ -29,8 +29,6 @@ Rails.application.routes.draw do
end
end
end
# get "/my/mailing_address", to: "my/mailing_address#show", as: :my_mailing_address
end
constraints AdminLevelConstraint.new(:superadmin, :admin, :viewer) do

View file

@ -0,0 +1,32 @@
class DropMailingSystem < ActiveRecord::Migration[8.1]
def up
drop_table :physical_mails, if_exists: true
drop_table :mailing_addresses, if_exists: true
remove_column :users, :mailing_address_otc, if_exists: true
end
def down
create_table :mailing_addresses do |t|
t.references :user, null: false, foreign_key: true
t.string :first_name
t.string :last_name
t.string :line_1
t.string :line_2
t.string :city
t.string :state
t.string :zip_code
t.string :country
t.timestamps
end
create_table :physical_mails do |t|
t.references :user, null: false, foreign_key: true
t.integer :status, default: 0
t.integer :mission_type
t.string :theseus_id
t.timestamps
end
add_column :users, :mailing_address_otc, :string
end
end

30
db/schema.rb generated
View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2025_12_30_202839) do
ActiveRecord::Schema[8.1].define(version: 2026_01_03_141942) do
create_schema "pganalyze"
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
@ -331,21 +331,6 @@ ActiveRecord::Schema[8.1].define(version: 2025_12_30_202839) do
t.index ["start_date"], name: "index_leaderboards_on_start_date", where: "(deleted_at IS NULL)"
end
create_table "mailing_addresses", force: :cascade do |t|
t.string "city", null: false
t.string "country", null: false
t.datetime "created_at", null: false
t.string "first_name", null: false
t.string "last_name", null: false
t.string "line_1", null: false
t.string "line_2"
t.string "state", null: false
t.datetime "updated_at", null: false
t.bigint "user_id", null: false
t.string "zip_code", null: false
t.index ["user_id"], name: "index_mailing_addresses_on_user_id"
end
create_table "oauth_access_grants", force: :cascade do |t|
t.bigint "application_id", null: false
t.datetime "created_at", null: false
@ -388,16 +373,6 @@ ActiveRecord::Schema[8.1].define(version: 2025_12_30_202839) do
t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true
end
create_table "physical_mails", force: :cascade do |t|
t.datetime "created_at", null: false
t.integer "mission_type", null: false
t.integer "status", default: 0, null: false
t.string "theseus_id"
t.datetime "updated_at", null: false
t.bigint "user_id", null: false
t.index ["user_id"], name: "index_physical_mails_on_user_id"
end
create_table "project_labels", force: :cascade do |t|
t.datetime "created_at", null: false
t.string "label"
@ -547,7 +522,6 @@ ActiveRecord::Schema[8.1].define(version: 2025_12_30_202839) do
t.string "hca_access_token"
t.string "hca_id"
t.string "hca_scopes", default: [], array: true
t.string "mailing_address_otc"
t.text "slack_access_token"
t.string "slack_avatar_url"
t.string "slack_scopes", default: [], array: true
@ -600,12 +574,10 @@ ActiveRecord::Schema[8.1].define(version: 2025_12_30_202839) do
add_foreign_key "heartbeats", "users"
add_foreign_key "leaderboard_entries", "leaderboards"
add_foreign_key "leaderboard_entries", "users"
add_foreign_key "mailing_addresses", "users"
add_foreign_key "oauth_access_grants", "oauth_applications", column: "application_id"
add_foreign_key "oauth_access_grants", "users", column: "resource_owner_id"
add_foreign_key "oauth_access_tokens", "oauth_applications", column: "application_id"
add_foreign_key "oauth_access_tokens", "users", column: "resource_owner_id"
add_foreign_key "physical_mails", "users"
add_foreign_key "project_repo_mappings", "repositories"
add_foreign_key "project_repo_mappings", "users"
add_foreign_key "repo_host_events", "users"

View file

@ -1,7 +0,0 @@
require "test_helper"
class MailingAddressTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end