Add physical mail model

This commit is contained in:
Max Wofford 2025-05-14 11:50:50 -04:00
parent 4405b5bdcc
commit 55503499ff
5 changed files with 114 additions and 2 deletions

View file

@ -56,4 +56,6 @@ GITHUB_CLIENT_SECRET=your_github_client_secret_here
SKYLIGHT_AUTHENTICATION=replace_me
IPINFO_API_KEY=replace_me
IPINFO_API_KEY=replace_me
MAIL_HACKCLUB_TOKEN=replace_me

View file

@ -0,0 +1,38 @@
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
over_7_day_streaks.each do |user_id|
next if PhysicalMail.going_out.exists?(user_id: user_id, mission_type: :first_streak)
user = User.find(user_id)
# Create the physical mail record
PhysicalMail.create!(
user: user,
mission_type: :first_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

@ -0,0 +1,49 @@
class PhysicalMail < ApplicationRecord
belongs_to :user
scope :going_out, -> { where(status: :pending).or(where(status: :sent)) }
enum :status, {
pending: 0,
sent: 1,
failed: 2
}
enum :mission_type, {
hackatime_first_time_7_streak: 0
}
def deliver!
slug = "hackatime_#{mission_type.to_s.underscore.gsub("_", "-")}"
flavors = FlavorText.compliment
flavors.concat(FlavorText.rare_compliment) if rand(10) == 0
# authorization: Bearer <token>
response = HTTP.auth("Bearer #{ENV["MAIL_HACKCLUB_TOKEN"]}").post("https://mail.hackclub.com/api/v1/letter_queues/#{slug}", json: {
recipient_email: user.email,
address: user_address,
rubber_stamps: flavors.sample,
idempotency_key: "physical_mail_#{id}",
metadata: {
attributes: attributes
}
})
if response.status.success?
update(status: :sent)
else
update(status: :failed)
raise "Failed to deliver physical mail: #{response.body}"
end
rescue => e
update(status: :failed)
raise e
end
private
def user_address
user.address
end
end

View file

@ -0,0 +1,12 @@
class CreatePhysicalMails < ActiveRecord::Migration[8.0]
def change
create_table :physical_mails do |t|
t.references :user, null: false, foreign_key: true
t.integer :mission_type, null: false
t.integer :status, null: false, default: 0
t.string :theseus_id
t.timestamps
end
end
end

13
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.0].define(version: 2025_05_13_184040) do
ActiveRecord::Schema[8.0].define(version: 2025_05_14_150404) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
@ -253,6 +253,16 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_13_184040) do
t.index ["user_id"], name: "index_mailing_addresses_on_user_id"
end
create_table "physical_mails", force: :cascade do |t|
t.bigint "user_id", null: false
t.integer "mission_type", null: false
t.integer "status", default: 0, null: false
t.string "theseus_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_physical_mails_on_user_id"
end
create_table "project_repo_mappings", force: :cascade do |t|
t.bigint "user_id", null: false
t.string "project_name", null: false
@ -372,6 +382,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_13_184040) do
add_foreign_key "leaderboard_entries", "leaderboards"
add_foreign_key "leaderboard_entries", "users"
add_foreign_key "mailing_addresses", "users"
add_foreign_key "physical_mails", "users"
add_foreign_key "project_repo_mappings", "users"
add_foreign_key "sign_in_tokens", "users"
add_foreign_key "wakatime_mirrors", "users"