Compare commits
8 commits
main
...
jedmund/fi
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f457ce472 | |||
| 2e569ca5ba | |||
| 16ad974f83 | |||
| 8f2a7fe96e | |||
| 8207b3a09a | |||
| e9d9940d96 | |||
| b04497c049 | |||
| e218f656ac |
14 changed files with 337 additions and 123 deletions
|
|
@ -117,10 +117,41 @@ module Api
|
|||
end
|
||||
|
||||
def preview
|
||||
party = Party.find_by!(shortcode: params[:id])
|
||||
coordinator = PreviewService::Coordinator.new(@party)
|
||||
|
||||
preview_service = PreviewService::Coordinator.new(party)
|
||||
redirect_to preview_service.preview_url
|
||||
if coordinator.generation_in_progress?
|
||||
response.headers['Retry-After'] = '2'
|
||||
default_path = Rails.root.join('public', 'default-previews', "#{@party.element || 'default'}.png")
|
||||
send_file default_path,
|
||||
type: 'image/png',
|
||||
disposition: 'inline'
|
||||
return
|
||||
end
|
||||
|
||||
# Try to get the preview or send default
|
||||
begin
|
||||
if Rails.env.production?
|
||||
# Stream S3 content instead of redirecting
|
||||
s3_object = coordinator.get_s3_object
|
||||
send_data s3_object.body.read,
|
||||
filename: "#{@party.shortcode}.png",
|
||||
type: 'image/png',
|
||||
disposition: 'inline'
|
||||
else
|
||||
# In development, serve from local filesystem
|
||||
send_file coordinator.local_preview_path,
|
||||
type: 'image/png',
|
||||
disposition: 'inline'
|
||||
end
|
||||
rescue Aws::S3::Errors::NoSuchKey
|
||||
# Schedule generation if needed
|
||||
coordinator.schedule_generation unless coordinator.generation_in_progress?
|
||||
|
||||
# Return default preview while generating
|
||||
send_file Rails.root.join('public', 'default-previews', "#{@party.element || 'default'}.png"),
|
||||
type: 'image/png',
|
||||
disposition: 'inline'
|
||||
end
|
||||
end
|
||||
|
||||
def regenerate_preview
|
||||
|
|
|
|||
|
|
@ -44,11 +44,25 @@ module PreviewService
|
|||
end
|
||||
|
||||
def add_text(image, party_name, job_icon: nil, user: nil, **options)
|
||||
party_name = party_name.to_s.strip
|
||||
party_name = 'Untitled' if party_name.empty?
|
||||
|
||||
font_size = options.fetch(:size, '32')
|
||||
font_color = options.fetch(:color, 'white')
|
||||
|
||||
# Load custom font for username, for later use
|
||||
@font_path = Rails.root.join('app', 'assets', 'fonts', 'Gk-Bd.otf').to_s
|
||||
# Try multiple font locations
|
||||
font_locations = [
|
||||
Rails.root.join('app', 'assets', 'fonts', 'Gk-Bd.otf').to_s,
|
||||
Rails.root.join('public', 'assets', 'fonts', 'Gk-Bd.otf').to_s
|
||||
]
|
||||
|
||||
@font_path = font_locations.find { |path| File.exist?(path) }
|
||||
|
||||
unless @font_path
|
||||
Rails.logger.error("Font file not found in any location: #{font_locations.join(', ')}")
|
||||
raise "Font file not found"
|
||||
end
|
||||
|
||||
Rails.logger.info("Using font path: #{@font_path}")
|
||||
unless File.exist?(@font_path)
|
||||
Rails.logger.error("Font file not found at: #{@font_path}")
|
||||
|
|
@ -94,16 +108,18 @@ module PreviewService
|
|||
end
|
||||
|
||||
def draw_party_name(image, party_name, text_metrics, job_icon, font_color, font_size)
|
||||
# Determine x position based on presence of job_icon
|
||||
text_x = job_icon ? PADDING + 64 + 16 : PADDING
|
||||
text_y = PADDING + text_metrics[:height]
|
||||
|
||||
image.combine_options do |c|
|
||||
c.font @font_path
|
||||
c.gravity 'NorthWest'
|
||||
c.fill font_color
|
||||
c.font @font_path
|
||||
c.pointsize font_size
|
||||
c.draw "text #{text_x},#{text_y} '#{party_name}'"
|
||||
# Escape quotes and use pango markup for better text handling
|
||||
c.annotate "0x0+#{text_x}+#{text_y}", party_name.gsub('"', '\"')
|
||||
end
|
||||
|
||||
image
|
||||
end
|
||||
|
||||
|
|
@ -154,7 +170,13 @@ module PreviewService
|
|||
image
|
||||
end
|
||||
|
||||
def measure_text(text, font_size, font: 'Arial')
|
||||
def measure_text(text, font_size, font: @font_path)
|
||||
# Ensure text is not empty and is properly escaped
|
||||
text = text.to_s.strip
|
||||
text = 'Untitled' if text.empty?
|
||||
|
||||
# Escape text for shell command
|
||||
escaped_text = text.gsub(/'/, "'\\\\''")
|
||||
|
||||
# Create a temporary file for the text measurement
|
||||
temp_file = Tempfile.new(['text_measure', '.png'])
|
||||
|
|
@ -167,7 +189,7 @@ module PreviewService
|
|||
'-fill', 'black',
|
||||
'-font', font,
|
||||
'-pointsize', font_size.to_s,
|
||||
"label:#{text}",
|
||||
"label:'#{escaped_text}'", # Quote the text
|
||||
temp_file.path
|
||||
]
|
||||
|
||||
|
|
@ -181,15 +203,15 @@ module PreviewService
|
|||
height: image.height,
|
||||
width: image.width
|
||||
}
|
||||
rescue => e
|
||||
Rails.logger.error "Text measurement error: #{e.message}"
|
||||
# Fallback dimensions
|
||||
{ height: 50, width: 200 }
|
||||
ensure
|
||||
# Close and unlink the temporary file
|
||||
temp_file.close
|
||||
temp_file.unlink
|
||||
end
|
||||
rescue => e
|
||||
Rails.logger.error "Text measurement error: #{e.message}"
|
||||
# Fallback dimensions
|
||||
{ height: 50, width: 200 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ module PreviewService
|
|||
GENERATION_TIMEOUT = 5.minutes
|
||||
LOCAL_STORAGE_PATH = Rails.root.join('storage', 'party-previews')
|
||||
|
||||
# Public Interface - Core Operations
|
||||
|
||||
# Initialize the party preview service
|
||||
#
|
||||
# @param party [Party] The party to generate a preview for
|
||||
|
|
@ -103,6 +105,8 @@ module PreviewService
|
|||
end
|
||||
|
||||
# Deletes the existing preview image for the party
|
||||
#
|
||||
# @return [void]
|
||||
def delete_preview
|
||||
if Rails.env.production?
|
||||
delete_s3_preview
|
||||
|
|
@ -118,6 +122,8 @@ module PreviewService
|
|||
Rails.logger.error("Failed to delete preview for party #{@party.id}: #{e.message}")
|
||||
end
|
||||
|
||||
# State Management - Public
|
||||
|
||||
# Determines if a new preview should be generated
|
||||
#
|
||||
# @return [Boolean] True if a new preview should be generated, false otherwise
|
||||
|
|
@ -150,15 +156,43 @@ module PreviewService
|
|||
false
|
||||
end
|
||||
|
||||
private
|
||||
# Checks if a preview generation is currently in progress
|
||||
#
|
||||
# @return [Boolean] True if a preview is being generated, false otherwise
|
||||
def generation_in_progress?
|
||||
in_progress = Rails.cache.exist?("party_preview_generating_#{@party.id}")
|
||||
Rails.logger.info("Cache key check for generation_in_progress: #{in_progress}")
|
||||
in_progress
|
||||
end
|
||||
|
||||
# Sets up the appropriate storage system based on environment
|
||||
def setup_storage
|
||||
# Always initialize AWS service for potential image fetching
|
||||
@aws_service = AwsService.new
|
||||
# Retrieves the S3 object for the party's preview image
|
||||
#
|
||||
# @return [Aws::S3::Types::GetObjectOutput] S3 object containing the preview image
|
||||
# @raise [Aws::S3::Errors::NoSuchKey] If the preview image doesn't exist in S3
|
||||
# @raise [Aws::S3::Errors::NoSuchBucket] If the configured bucket doesn't exist
|
||||
def get_s3_object
|
||||
@aws_service.s3_client.get_object(
|
||||
bucket: @aws_service.bucket,
|
||||
key: preview_key
|
||||
)
|
||||
end
|
||||
|
||||
# Create local storage paths in development
|
||||
FileUtils.mkdir_p(LOCAL_STORAGE_PATH) unless Dir.exist?(LOCAL_STORAGE_PATH.to_s)
|
||||
# Schedules a background job to generate the preview
|
||||
#
|
||||
# @return [void]
|
||||
def schedule_generation
|
||||
GeneratePartyPreviewJob
|
||||
.set(wait: 30.seconds)
|
||||
.perform_later(@party.id)
|
||||
|
||||
@party.update!(preview_state: :queued)
|
||||
end
|
||||
|
||||
# Returns the full path for storing preview images locally
|
||||
#
|
||||
# @return [Pathname] Full path where the preview image should be stored
|
||||
def local_preview_path
|
||||
LOCAL_STORAGE_PATH.join(preview_filename)
|
||||
end
|
||||
|
||||
# Creates the preview image for the party
|
||||
|
|
@ -225,6 +259,21 @@ module PreviewService
|
|||
image
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Sets up the appropriate storage system based on environment
|
||||
#
|
||||
# @return [void]
|
||||
def setup_storage
|
||||
# Always initialize AWS service for potential image fetching
|
||||
@aws_service = AwsService.new
|
||||
|
||||
# Create local storage paths in development
|
||||
FileUtils.mkdir_p(LOCAL_STORAGE_PATH) unless Dir.exist?(LOCAL_STORAGE_PATH.to_s)
|
||||
end
|
||||
|
||||
# Image Generation Pipeline
|
||||
|
||||
# Adds the job icon to the preview image
|
||||
#
|
||||
# @param image [MiniMagick::Image] The base image
|
||||
|
|
@ -241,6 +290,7 @@ module PreviewService
|
|||
# Organizes and draws weapons on the preview image
|
||||
#
|
||||
# @param image [MiniMagick::Image] The base image
|
||||
# @param grid_layout [Hash] The layout configuration for the grid
|
||||
# @return [MiniMagick::Image] The updated image with weapons
|
||||
def organize_and_draw_weapons(image, grid_layout)
|
||||
mainhand_weapon = @party.weapons.find(&:mainhand)
|
||||
|
|
@ -277,9 +327,12 @@ module PreviewService
|
|||
end
|
||||
end
|
||||
|
||||
# Storage Operations
|
||||
|
||||
# Saves the preview image to the appropriate storage system
|
||||
#
|
||||
# @param image [MiniMagick::Image] The image to save
|
||||
# @return [void]
|
||||
def save_preview(image)
|
||||
if Rails.env.production?
|
||||
upload_to_s3(image)
|
||||
|
|
@ -291,14 +344,14 @@ module PreviewService
|
|||
# Uploads the preview image to S3
|
||||
#
|
||||
# @param image [MiniMagick::Image] The image to upload
|
||||
# @return [void]
|
||||
def upload_to_s3(image)
|
||||
temp_file = Tempfile.new(['preview', '.png'])
|
||||
temp_file = Tempfile.new(%w[preview .png])
|
||||
begin
|
||||
image.write(temp_file.path)
|
||||
|
||||
# Use timestamped filename similar to local storage
|
||||
timestamp = Time.current.strftime('%Y%m%d%H%M%S')
|
||||
key = "#{PREVIEW_FOLDER}/#{@party.shortcode}_#{timestamp}.png"
|
||||
# Use fixed key without timestamp
|
||||
key = "#{PREVIEW_FOLDER}/#{@party.shortcode}.png"
|
||||
|
||||
File.open(temp_file.path, 'rb') do |file|
|
||||
@aws_service.s3_client.put_object(
|
||||
|
|
@ -310,7 +363,6 @@ module PreviewService
|
|||
)
|
||||
end
|
||||
|
||||
# Optionally, store this key on the party record if needed for retrieval
|
||||
@party.update!(preview_s3_key: key)
|
||||
ensure
|
||||
temp_file.close
|
||||
|
|
@ -321,29 +373,18 @@ module PreviewService
|
|||
# Saves the preview image to local storage
|
||||
#
|
||||
# @param image [MiniMagick::Image] The image to save
|
||||
# @return [void]
|
||||
def save_to_local_storage(image)
|
||||
# Remove any existing previews for this party
|
||||
Dir.glob(LOCAL_STORAGE_PATH.join("#{@party.shortcode}_*.png").to_s).each do |file|
|
||||
File.delete(file)
|
||||
end
|
||||
|
||||
# Save new version
|
||||
image.write(local_preview_path)
|
||||
end
|
||||
|
||||
# Generates a timestamped filename for the preview image
|
||||
#
|
||||
# @return [String] Filename in format "shortcode_YYYYMMDDHHMMSS.png"
|
||||
def preview_filename
|
||||
timestamp = Time.current.strftime('%Y%m%d%H%M%S')
|
||||
"#{@party.shortcode}_#{timestamp}.png"
|
||||
end
|
||||
# Path & URL Generation
|
||||
|
||||
# Returns the full path for storing preview images locally
|
||||
# Generates a filename for the preview image
|
||||
#
|
||||
# @return [Pathname] Full path where the preview image should be stored
|
||||
def local_preview_path
|
||||
LOCAL_STORAGE_PATH.join(preview_filename)
|
||||
# @return [String] Filename for the preview image
|
||||
def preview_filename
|
||||
"#{@party.shortcode}.png"
|
||||
end
|
||||
|
||||
# Returns the URL for accessing locally stored preview images
|
||||
|
|
@ -364,6 +405,8 @@ module PreviewService
|
|||
"#{PREVIEW_FOLDER}/#{@party.shortcode}.png"
|
||||
end
|
||||
|
||||
# Preview State Management
|
||||
|
||||
# Checks if a preview image exists for the party
|
||||
#
|
||||
# @return [Boolean] True if a preview exists, false otherwise
|
||||
|
|
@ -371,10 +414,9 @@ module PreviewService
|
|||
return false unless @party.preview_state == 'generated'
|
||||
|
||||
if Rails.env.production?
|
||||
@aws_service.s3_client.head_object(bucket: S3_BUCKET, key: preview_key)
|
||||
true
|
||||
@aws_service.file_exists?(preview_key)
|
||||
else
|
||||
!Dir.glob(LOCAL_STORAGE_PATH.join("#{@party.shortcode}_*.png").to_s).empty?
|
||||
!Dir.glob(LOCAL_STORAGE_PATH.join("#{@party.shortcode}.png").to_s).empty?
|
||||
end
|
||||
rescue Aws::S3::Errors::NotFound
|
||||
false
|
||||
|
|
@ -387,22 +429,15 @@ module PreviewService
|
|||
signer = Aws::S3::Presigner.new(client: @aws_service.s3_client)
|
||||
signer.presigned_url(
|
||||
:get_object,
|
||||
bucket: S3_BUCKET,
|
||||
bucket: @aws_service.bucket,
|
||||
key: preview_key,
|
||||
expires_in: 1.hour
|
||||
expires_in: 1.hour.to_i
|
||||
)
|
||||
end
|
||||
|
||||
# Checks if a preview generation is currently in progress
|
||||
#
|
||||
# @return [Boolean] True if a preview is being generated, false otherwise
|
||||
def generation_in_progress?
|
||||
in_progress = Rails.cache.exist?("party_preview_generating_#{@party.id}")
|
||||
Rails.logger.info("Cache key check for generation_in_progress: #{in_progress}")
|
||||
in_progress
|
||||
end
|
||||
|
||||
# Marks the preview generation as in progress
|
||||
#
|
||||
# @return [void]
|
||||
def set_generation_in_progress
|
||||
Rails.cache.write(
|
||||
"party_preview_generating_#{@party.id}",
|
||||
|
|
@ -412,18 +447,15 @@ module PreviewService
|
|||
end
|
||||
|
||||
# Clears the in-progress flag for preview generation
|
||||
#
|
||||
# @return [void]
|
||||
def clear_generation_in_progress
|
||||
Rails.cache.delete("party_preview_generating_#{@party.id}")
|
||||
end
|
||||
|
||||
# Schedules a background job to generate the preview
|
||||
def schedule_generation
|
||||
GeneratePartyPreviewJob
|
||||
.set(wait: 30.seconds)
|
||||
.perform_later(@party.id)
|
||||
# Job Scheduling
|
||||
|
||||
@party.update!(preview_state: :queued)
|
||||
end
|
||||
# URL Generation
|
||||
|
||||
# Provides a default preview URL based on party attributes
|
||||
#
|
||||
|
|
@ -436,24 +468,33 @@ module PreviewService
|
|||
end
|
||||
end
|
||||
|
||||
# Cleanup Operations
|
||||
|
||||
# Deletes the preview from S3
|
||||
#
|
||||
# @return [void]
|
||||
def delete_s3_preview
|
||||
@aws_service.s3_client.delete_object(
|
||||
bucket: S3_BUCKET,
|
||||
bucket: @aws_service.bucket,
|
||||
key: preview_key
|
||||
)
|
||||
end
|
||||
|
||||
# Deletes local preview files
|
||||
#
|
||||
# @return [void]
|
||||
def delete_local_previews
|
||||
Dir.glob(LOCAL_STORAGE_PATH.join("#{@party.shortcode}_*.png").to_s).each do |file|
|
||||
File.delete(file)
|
||||
end
|
||||
end
|
||||
|
||||
# Error Handling
|
||||
|
||||
# Handles errors during preview generation
|
||||
#
|
||||
# @param error [Exception] The error that occurred
|
||||
# @return [void]
|
||||
def handle_preview_generation_error(error)
|
||||
Rails.logger.error("Preview generation failed for party #{@party.id}")
|
||||
Rails.logger.error("Error: #{error.class} - #{error.message}")
|
||||
|
|
|
|||
|
|
@ -1,42 +1,35 @@
|
|||
require_relative "boot"
|
||||
|
||||
require "rails"
|
||||
# Pick the frameworks you want:
|
||||
require "active_model/railtie"
|
||||
require "active_job/railtie"
|
||||
require "active_record/railtie"
|
||||
require "active_storage/engine"
|
||||
require "action_controller/railtie"
|
||||
# require "action_mailer/railtie"
|
||||
# require "action_mailbox/engine"
|
||||
require "action_text/engine"
|
||||
require "action_view/railtie"
|
||||
require "action_cable/engine"
|
||||
require "rails/test_unit/railtie"
|
||||
|
||||
# Require the gems listed in Gemfile, including any gems
|
||||
# you've limited to :test, :development, or :production.
|
||||
# Include only the Rails frameworks we need
|
||||
require "active_model/railtie" # Basic model functionality
|
||||
require "active_job/railtie" # Background job processing
|
||||
require "active_record/railtie" # Database support
|
||||
require "active_storage/engine" # File upload and storage
|
||||
require "action_controller/railtie" # API controller support
|
||||
require "action_text/engine" # Rich text handling
|
||||
require "action_view/railtie" # View rendering (needed for some API responses)
|
||||
require "rails/test_unit/railtie" # Testing framework
|
||||
|
||||
# Load gems from Gemfile
|
||||
Bundler.require(*Rails.groups)
|
||||
|
||||
module HenseiApi
|
||||
class Application < Rails::Application
|
||||
# Initialize configuration defaults for originally generated Rails version.
|
||||
# Use Rails 7.0 defaults
|
||||
config.load_defaults 7.0
|
||||
|
||||
# Configuration for the application, engines, and railties goes here.
|
||||
#
|
||||
# These settings can be overridden in specific environments using the files
|
||||
# in config/environments, which are processed later.
|
||||
#
|
||||
# config.time_zone = "Central Time (US & Canada)"
|
||||
# config.eager_load_paths << Rails.root.join("extras")
|
||||
|
||||
# Configure autoloading
|
||||
config.autoload_paths << Rails.root.join("lib")
|
||||
config.eager_load_paths << Rails.root.join("lib")
|
||||
|
||||
# Only loads a smaller set of middleware suitable for API only apps.
|
||||
# Middleware like session, flash, cookies can be added back manually.
|
||||
# Skip views, helpers and assets when generating a new resource.
|
||||
# Configure asset handling for API mode
|
||||
config.paths["app/assets"] ||= []
|
||||
config.paths["app/assets"].unshift(Rails.root.join("app", "assets").to_s)
|
||||
config.assets.paths << Rails.root.join("app", "assets", "fonts")
|
||||
|
||||
# API-only application configuration
|
||||
config.api_only = true
|
||||
end
|
||||
end
|
||||
|
|
|
|||
12
config/initializers/assets.rb
Normal file
12
config/initializers/assets.rb
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
Rails.application.config.assets.precompile += %w( .otf )
|
||||
|
||||
# Ensure fonts directory exists in production
|
||||
fonts_dir = Rails.root.join('public', 'assets', 'fonts')
|
||||
FileUtils.mkdir_p(fonts_dir) unless File.directory?(fonts_dir)
|
||||
|
||||
# Copy fonts to public directory in production
|
||||
if Rails.env.production?
|
||||
Dir[Rails.root.join('app', 'assets', 'fonts', '*')].each do |font|
|
||||
FileUtils.cp(font, fonts_dir) if File.file?(font)
|
||||
end
|
||||
end
|
||||
106
lib/tasks/previews.rake
Normal file
106
lib/tasks/previews.rake
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
namespace :previews do
|
||||
desc 'Generate and upload missing preview images'
|
||||
task generate_all: :environment do
|
||||
coordinator_class = PreviewService::Coordinator
|
||||
aws_service = AwsService.new
|
||||
|
||||
# Find all parties without previews
|
||||
parties = Party.where(preview_state: ['pending', 'failed', nil])
|
||||
total = parties.count
|
||||
|
||||
puts "Found #{total} parties needing preview generation"
|
||||
|
||||
parties.find_each.with_index(1) do |party, index|
|
||||
puts "[#{index}/#{total}] Processing party #{party.shortcode}..."
|
||||
|
||||
begin
|
||||
coordinator = coordinator_class.new(party)
|
||||
temp_file = Tempfile.new(['preview', '.png'])
|
||||
|
||||
# Create preview image
|
||||
image = coordinator.create_preview_image
|
||||
image.write(temp_file.path)
|
||||
|
||||
# Upload to S3
|
||||
key = "previews/#{party.shortcode}.png"
|
||||
File.open(temp_file.path, 'rb') do |file|
|
||||
aws_service.s3_client.put_object(
|
||||
bucket: aws_service.bucket,
|
||||
key: key,
|
||||
body: file,
|
||||
content_type: 'image/png',
|
||||
acl: 'private'
|
||||
)
|
||||
end
|
||||
|
||||
# Update party state
|
||||
party.update!(
|
||||
preview_state: :generated,
|
||||
preview_s3_key: key,
|
||||
preview_generated_at: Time.current
|
||||
)
|
||||
|
||||
puts " ✓ Preview generated and uploaded to S3"
|
||||
rescue => e
|
||||
puts " ✗ Error: #{e.message}"
|
||||
ensure
|
||||
temp_file&.close
|
||||
temp_file&.unlink
|
||||
end
|
||||
end
|
||||
|
||||
puts "\nPreview generation complete"
|
||||
end
|
||||
|
||||
desc 'Regenerate all preview images'
|
||||
task regenerate_all: :environment do
|
||||
coordinator_class = PreviewService::Coordinator
|
||||
aws_service = AwsService.new
|
||||
|
||||
parties = Party.all
|
||||
total = parties.count
|
||||
|
||||
puts "Found #{total} parties to regenerate"
|
||||
|
||||
parties.find_each.with_index(1) do |party, index|
|
||||
puts "[#{index}/#{total}] Processing party #{party.shortcode}..."
|
||||
|
||||
begin
|
||||
coordinator = coordinator_class.new(party)
|
||||
temp_file = Tempfile.new(['preview', '.png'])
|
||||
|
||||
# Create preview image
|
||||
image = coordinator.create_preview_image
|
||||
image.write(temp_file.path)
|
||||
|
||||
# Upload to S3
|
||||
key = "previews/#{party.shortcode}.png"
|
||||
File.open(temp_file.path, 'rb') do |file|
|
||||
aws_service.s3_client.put_object(
|
||||
bucket: aws_service.bucket,
|
||||
key: key,
|
||||
body: file,
|
||||
content_type: 'image/png',
|
||||
acl: 'private'
|
||||
)
|
||||
end
|
||||
|
||||
# Update party state
|
||||
party.update!(
|
||||
preview_state: :generated,
|
||||
preview_s3_key: key,
|
||||
preview_generated_at: Time.current
|
||||
)
|
||||
|
||||
puts " ✓ Preview regenerated and uploaded to S3"
|
||||
rescue => e
|
||||
puts " ✗ Error: #{e.message}"
|
||||
ensure
|
||||
temp_file&.close
|
||||
temp_file&.unlink
|
||||
end
|
||||
end
|
||||
|
||||
puts "\nPreview regeneration complete"
|
||||
end
|
||||
end
|
||||
BIN
public/default-previews/1.png
Normal file
BIN
public/default-previews/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 455 KiB |
BIN
public/default-previews/2.png
Normal file
BIN
public/default-previews/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 436 KiB |
BIN
public/default-previews/3.png
Normal file
BIN
public/default-previews/3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 413 KiB |
BIN
public/default-previews/4.png
Normal file
BIN
public/default-previews/4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 472 KiB |
BIN
public/default-previews/5.png
Normal file
BIN
public/default-previews/5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 440 KiB |
BIN
public/default-previews/6.png
Normal file
BIN
public/default-previews/6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 472 KiB |
|
|
@ -22,6 +22,11 @@ dependsOn = ["setup"]
|
|||
|
||||
[phases.build]
|
||||
dependsOn = ["install"]
|
||||
cmds = [
|
||||
"mkdir -p public/assets/fonts",
|
||||
"cp -r app/assets/fonts/* public/assets/fonts/",
|
||||
"bundle exec rake assets:precompile"
|
||||
]
|
||||
|
||||
[start]
|
||||
cmd = "bin/rails server"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
# sig/services/preview_service/coordinator.rbs
|
||||
|
||||
module PreviewService
|
||||
class Coordinator
|
||||
PREVIEW_FOLDER: String
|
||||
|
|
@ -6,69 +8,71 @@ module PreviewService
|
|||
PREVIEW_EXPIRY: ActiveSupport::Duration
|
||||
GENERATION_TIMEOUT: ActiveSupport::Duration
|
||||
LOCAL_STORAGE_PATH: Pathname
|
||||
|
||||
|
||||
@party: Party
|
||||
@image_fetcher: ImageFetcherService
|
||||
@grid_service: Grid
|
||||
@canvas_service: Canvas
|
||||
@aws_service: AwsService
|
||||
|
||||
def initialize: (Party party) -> void
|
||||
def initialize: (party: Party) -> void
|
||||
|
||||
def preview_url: -> String
|
||||
def get_s3_object: () -> Aws::S3::Types::GetObjectOutput
|
||||
|
||||
def generate_preview: -> bool
|
||||
def preview_url: () -> String
|
||||
|
||||
def force_regenerate: -> bool
|
||||
def generate_preview: () -> bool
|
||||
|
||||
def delete_preview: -> void
|
||||
def force_regenerate: () -> bool
|
||||
|
||||
def delete_preview: () -> void
|
||||
|
||||
def should_generate?: () -> bool
|
||||
|
||||
def generation_in_progress?: () -> bool
|
||||
|
||||
def create_preview_image: () -> MiniMagick::Image
|
||||
|
||||
private
|
||||
|
||||
def create_preview_image: -> MiniMagick::Image
|
||||
def setup_storage: () -> void
|
||||
|
||||
def add_job_icon: (MiniMagick::Image image, MiniMagick::Image job_icon) -> MiniMagick::Image
|
||||
def add_job_icon: (image: MiniMagick::Image, job_icon: MiniMagick::Image) -> MiniMagick::Image
|
||||
|
||||
def organize_and_draw_weapons: (MiniMagick::Image image) -> MiniMagick::Image
|
||||
def organize_and_draw_weapons: (image: MiniMagick::Image, grid_layout: Hash[Symbol, untyped]) -> MiniMagick::Image
|
||||
|
||||
def draw_mainhand_weapon: (MiniMagick::Image image, MiniMagick::Image weapon_image) -> MiniMagick::Image
|
||||
def draw_mainhand_weapon: (image: MiniMagick::Image, weapon_image: MiniMagick::Image) -> MiniMagick::Image
|
||||
|
||||
def save_preview: (MiniMagick::Image image) -> void
|
||||
def save_preview: (image: MiniMagick::Image) -> void
|
||||
|
||||
def setup_storage: -> void
|
||||
def upload_to_s3: (image: MiniMagick::Image) -> void
|
||||
|
||||
def upload_to_s3: (MiniMagick::Image image) -> void
|
||||
def save_to_local_storage: (image: MiniMagick::Image) -> void
|
||||
|
||||
def save_to_local_storage: (MiniMagick::Image image) -> void
|
||||
def preview_filename: () -> String
|
||||
|
||||
def preview_filename: -> String
|
||||
def local_preview_path: () -> Pathname
|
||||
|
||||
def local_preview_path: -> Pathname
|
||||
def local_preview_url: () -> String
|
||||
|
||||
def local_preview_url: -> String
|
||||
def preview_key: () -> String
|
||||
|
||||
def preview_key: -> String
|
||||
def preview_exists?: () -> bool
|
||||
|
||||
def preview_exists?: -> bool
|
||||
def generate_s3_url: () -> String
|
||||
|
||||
def generate_s3_url: -> String
|
||||
def set_generation_in_progress: () -> void
|
||||
|
||||
def should_generate?: -> bool
|
||||
def clear_generation_in_progress: () -> void
|
||||
|
||||
def generation_in_progress?: -> bool
|
||||
def schedule_generation: () -> void
|
||||
|
||||
def set_generation_in_progress: -> void
|
||||
def default_preview_url: () -> String
|
||||
|
||||
def clear_generation_in_progress: -> void
|
||||
def delete_s3_preview: () -> void
|
||||
|
||||
def schedule_generation: -> void
|
||||
def delete_local_previews: () -> void
|
||||
|
||||
def default_preview_url: -> String
|
||||
|
||||
def delete_s3_preview: -> void
|
||||
|
||||
def delete_local_previews: -> void
|
||||
|
||||
def handle_preview_generation_error: (Exception error) -> void
|
||||
def handle_preview_generation_error: (error: Exception) -> void
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue