Skip to content

Commit

Permalink
Merge pull request #191 from loftwah/dl/og-image-font-fix
Browse files Browse the repository at this point in the history
fix og image generator
  • Loading branch information
loftwah authored Sep 21, 2024
2 parents 785a294 + 257e15c commit 9829c33
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 73 deletions.
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ FROM base

# Install packages needed for deployment
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libsqlite3-0 libvips imagemagick fonts-liberation sqlite3 libsqlite3-dev && \
apt-get install --no-install-recommends -y curl libsqlite3-0 libvips imagemagick fonts-liberation sqlite3 libsqlite3-dev \
fonts-freefont-ttf fonts-dejavu fontconfig && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Copy built artifacts: gems, application, and node modules
Expand Down
14 changes: 13 additions & 1 deletion app/controllers/users/registrations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ def create
if resource.active_for_authentication?
set_flash_message! :notice, :signed_up
sign_up(resource_name, resource)
UserMailer.welcome_email(resource).deliver_now
begin
UserMailer.welcome_email(resource).deliver_later
rescue => e
Rails.logger.error("Failed to enqueue welcome email for user #{resource.id}: #{e.message}")
end
respond_with resource, location: after_sign_up_path_for(resource)
else
set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
Expand All @@ -29,6 +33,10 @@ def create
set_minimum_password_length
respond_with resource
end
rescue => e
Rails.logger.error("Error during user registration: #{e.message}")
flash[:error] = "An error occurred during registration. Please try again."
redirect_to new_user_registration_path
end

def edit
Expand Down Expand Up @@ -75,6 +83,10 @@ def update
render :edit
end
end
rescue => e
Rails.logger.error("Error during user update: #{e.message}")
flash[:error] = "An error occurred while updating your profile. Please try again."
render :edit
end

private
Expand Down
12 changes: 12 additions & 0 deletions app/jobs/generate_open_graph_image_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class GenerateOpenGraphImageJob < ApplicationJob
queue_as :default

def perform(user_id)
user = User.find_by(id: user_id)
return unless user

OpenGraphImageGenerator.new(user).generate
rescue StandardError => e
Rails.logger.error("Failed to generate OG image for user #{user_id}: #{e.message}")
end
end
6 changes: 5 additions & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class User < ApplicationRecord

before_validation :ensure_username_presence
before_create :set_default_images
after_create :generate_open_graph_image, unless: -> { Rails.env.test? }
after_create :generate_open_graph_image_async, unless: -> { Rails.env.test? }
before_save :process_avatar, if: :will_save_change_to_avatar?
before_save :process_banner, if: :will_save_change_to_banner?

Expand Down Expand Up @@ -62,6 +62,10 @@ def valid_url?(url)

private

def generate_open_graph_image_async
GenerateOpenGraphImageJob.perform_later(self.id)
end

def ensure_username_presence
if username.blank?
self.username = email.present? ? email.split('@').first : "user#{SecureRandom.hex(4)}"
Expand Down
138 changes: 71 additions & 67 deletions app/services/open_graph_image_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ class OpenGraphImageGenerator
# Constants for sizes and paths
AVATAR_SIZE = 400
BORDER_SIZE = 5
FONT = 'Courier' # Use a standard block print font

def initialize(user)
@user = user
Expand All @@ -14,91 +13,96 @@ def generate
FileUtils.mkdir_p(output_dir) unless File.directory?(output_dir)
output_path = output_dir.join("#{@user.username}_og.png")

image = MiniMagick::Image.open(template_path)
begin
image = MiniMagick::Image.open(template_path)

# Determine whether to use fallback avatar or download the provided one
if @user.avatar.blank? || !valid_image_url?(@user.avatar_url)
avatar = MiniMagick::Image.open(Rails.root.join('public', 'avatars', 'default_avatar.jpg'))
else
avatar = download_image(@user.avatar_url)
end
# Determine whether to use fallback avatar or download the provided one
if @user.avatar.blank? || !valid_image_url?(@user.avatar_url)
avatar = MiniMagick::Image.open(Rails.root.join('public', 'avatars', 'default_avatar.jpg'))
else
avatar = download_image(@user.avatar_url)
end

# Resize avatar and add border
avatar.resize "#{AVATAR_SIZE}x#{AVATAR_SIZE}"
avatar.combine_options do |c|
c.bordercolor 'white'
c.border BORDER_SIZE
end
# Resize avatar and add border
avatar.resize "#{AVATAR_SIZE}x#{AVATAR_SIZE}"
avatar.combine_options do |c|
c.bordercolor 'white'
c.border BORDER_SIZE
end

# Prepare text elements
tag_text = @user.parsed_tags.present? ? @user.parsed_tags.join(' | ') : ""
full_name = @user.full_name
username = "@#{@user.username}"
# Prepare text elements
tag_text = @user.parsed_tags.present? ? @user.parsed_tags.join(' | ') : ""
full_name = @user.full_name
username = "@#{@user.username}"

# Define point sizes
name_pointsize = 40
username_pointsize = 28
tag_pointsize = 20
# Define point sizes
name_pointsize = 40
username_pointsize = 28
tag_pointsize = 20

# Spacing between elements
spacing = 10
# Spacing between elements
spacing = 10

# Estimate text heights (approximated as 1.2 times point size)
name_text_height = name_pointsize * 1.2
username_text_height = username_pointsize * 1.2
tag_text_height = tag_pointsize * 1.2 if tag_text.present?
# Estimate text heights (approximated as 1.2 times point size)
name_text_height = name_pointsize * 1.2
username_text_height = username_pointsize * 1.2
tag_text_height = tag_pointsize * 1.2 if tag_text.present?

# Total content height calculation
total_height = (AVATAR_SIZE + 2 * BORDER_SIZE) + spacing +
name_text_height + spacing +
username_text_height
# Total content height calculation
total_height = (AVATAR_SIZE + 2 * BORDER_SIZE) + spacing +
name_text_height + spacing +
username_text_height

total_height += spacing + tag_text_height if tag_text.present?
total_height += spacing + tag_text_height if tag_text.present?

# Calculate starting y-position to center content vertically
template_height = image.height
content_start_y = ((template_height - total_height) / 2).to_i
# Calculate starting y-position to center content vertically
template_height = image.height
content_start_y = ((template_height - total_height) / 2).to_i

# Position elements
current_y = content_start_y
# Position elements
current_y = content_start_y

# Add avatar to the image, centered horizontally
image = image.composite(avatar) do |c|
c.gravity 'North' # Align from the top
c.geometry "+0+#{current_y}"
end
# Add avatar to the image, centered horizontally
image = image.composite(avatar) do |c|
c.gravity 'North' # Align from the top
c.geometry "+0+#{current_y}"
end

current_y += (AVATAR_SIZE + 2 * BORDER_SIZE) + spacing
current_y += (AVATAR_SIZE + 2 * BORDER_SIZE) + spacing

# Add text to the image
image.combine_options do |c|
c.gravity 'North' # Align from the top
c.font FONT # Set the font to 'Courier'
# Add text to the image
image.combine_options do |c|
c.gravity 'North' # Align from the top
c.font 'Arial' # Use a common system font

# Add full name
c.fill '#BEF264'
c.pointsize name_pointsize.to_s
c.draw "text 0,#{current_y} '#{escape_text(full_name)}'"
# Add full name
c.fill '#BEF264'
c.pointsize name_pointsize.to_s
c.draw "text 0,#{current_y} '#{escape_text(full_name)}'"

current_y += name_text_height + spacing
current_y += name_text_height + spacing

# Add username
c.pointsize username_pointsize.to_s
c.draw "text 0,#{current_y} '#{escape_text(username)}'"
# Add username
c.pointsize username_pointsize.to_s
c.draw "text 0,#{current_y} '#{escape_text(username)}'"

current_y += username_text_height + spacing
current_y += username_text_height + spacing

# Add tags if present
if tag_text.present?
c.fill 'white'
c.pointsize tag_pointsize.to_s
c.draw "text 0,#{current_y} '#{escape_text(tag_text)}'"
# Add tags if present
if tag_text.present?
c.fill 'white'
c.pointsize tag_pointsize.to_s
c.draw "text 0,#{current_y} '#{escape_text(tag_text)}'"
end
end
end

# Save the generated image
image.write(output_path)
output_path
# Save the generated image
image.write(output_path)
output_path
rescue StandardError => e
Rails.logger.error("Failed to generate OG image for user #{@user.id}: #{e.message}")
nil # Return nil to indicate failure without raising an exception
end
end

private
Expand Down Expand Up @@ -147,4 +151,4 @@ def download_image(url)
def escape_text(text)
text.gsub("'", "\\\\'")
end
end
end
4 changes: 2 additions & 2 deletions spec/controllers/users/registrations_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@
end
end

it "sends a welcome email after successful registration" do
it "enqueues a welcome email after successful registration" do
expect {
post :create, params: { user: attributes_for(:user, invite_code: "POWEROVERWHELMING") }
}.to change { ActionMailer::Base.deliveries.count }.by(1)
}.to have_enqueued_mail(UserMailer, :welcome_email)
end

it "handles tags correctly" do
Expand Down
5 changes: 4 additions & 1 deletion spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@

config.include FactoryBot::Syntax::Methods

# Add this line to include ActiveJob test helpers
config.include ActiveJob::TestHelper

# Precompile assets before running tests
config.before(:suite) do
Rails.application.load_tasks
Expand All @@ -51,4 +54,4 @@
Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
end

Capybara.javascript_driver = :chrome_headless
Capybara.javascript_driver = :chrome_headless

0 comments on commit 9829c33

Please sign in to comment.