@@ -5,18 +5,19 @@ class OpenGraphImageGenerator
5
5
6
6
def initialize ( user )
7
7
@user = user
8
+ @spaces_service = DigitalOceanSpacesService . new
8
9
end
9
10
10
11
def generate
11
- template_path = Rails . root . join ( 'app' , 'assets' , 'images' , 'og_template.png' )
12
- output_dir = Rails . root . join ( 'public' , 'uploads' , 'og_images' )
13
- FileUtils . mkdir_p ( output_dir ) unless File . directory? ( output_dir )
14
- output_path = output_dir . join ( "#{ @user . username } _og.png" )
15
-
16
12
begin
13
+ # Create a temporary working directory
14
+ temp_dir = Dir . mktmpdir
15
+ output_path = File . join ( temp_dir , "#{ @user . username } _og.png" )
16
+
17
+ template_path = Rails . root . join ( 'app' , 'assets' , 'images' , 'og_template.png' )
17
18
image = MiniMagick ::Image . open ( template_path )
18
19
19
- # Determine whether to use fallback avatar or download the provided one
20
+ # Download and process avatar
20
21
if @user . avatar . blank? || !valid_image_url? ( @user . avatar_url )
21
22
avatar = MiniMagick ::Image . open ( Rails . root . join ( 'public' , 'avatars' , 'default_avatar.jpg' ) )
22
23
else
@@ -43,17 +44,11 @@ def generate
43
44
# Spacing between elements
44
45
spacing = 10
45
46
46
- # Estimate text heights (approximated as 1.2 times point size)
47
- name_text_height = name_pointsize * 1.2
48
- username_text_height = username_pointsize * 1.2
49
- tag_text_height = tag_pointsize * 1.2 if tag_text . present?
50
-
51
47
# Total content height calculation
52
48
total_height = ( AVATAR_SIZE + 2 * BORDER_SIZE ) + spacing +
53
- name_text_height + spacing +
54
- username_text_height
55
-
56
- total_height += spacing + tag_text_height if tag_text . present?
49
+ name_pointsize * 1.2 + spacing +
50
+ username_pointsize * 1.2
51
+ total_height += spacing + tag_pointsize * 1.2 if tag_text . present?
57
52
58
53
# Calculate starting y-position to center content vertically
59
54
template_height = image . height
@@ -64,29 +59,29 @@ def generate
64
59
65
60
# Add avatar to the image, centered horizontally
66
61
image = image . composite ( avatar ) do |c |
67
- c . gravity 'North' # Align from the top
62
+ c . gravity 'North'
68
63
c . geometry "+0+#{ current_y } "
69
64
end
70
65
71
66
current_y += ( AVATAR_SIZE + 2 * BORDER_SIZE ) + spacing
72
67
73
68
# Add text to the image
74
69
image . combine_options do |c |
75
- c . gravity 'North' # Align from the top
76
- c . font 'Arial' # Use a common system font
70
+ c . gravity 'North'
71
+ c . font '/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf'
77
72
78
73
# Add full name
79
74
c . fill '#BEF264'
80
75
c . pointsize name_pointsize . to_s
81
76
c . draw "text 0,#{ current_y } '#{ escape_text ( full_name ) } '"
82
77
83
- current_y += name_text_height + spacing
78
+ current_y += name_pointsize * 1.2 + spacing
84
79
85
80
# Add username
86
81
c . pointsize username_pointsize . to_s
87
82
c . draw "text 0,#{ current_y } '#{ escape_text ( username ) } '"
88
83
89
- current_y += username_text_height + spacing
84
+ current_y += username_pointsize * 1.2 + spacing
90
85
91
86
# Add tags if present
92
87
if tag_text . present?
@@ -96,67 +91,60 @@ def generate
96
91
end
97
92
end
98
93
99
- # Save the generated image
94
+ # Save the image to temp directory
100
95
image . write ( output_path )
101
- output_path
96
+
97
+ # Upload to DigitalOcean Spaces
98
+ og_image_key = "og_images/#{ @user . username } _og.png"
99
+ spaces_url = @spaces_service . upload_file_from_path ( og_image_key , output_path )
100
+
101
+ # Update user's og_image_url
102
+ @user . update_column ( :og_image_url , spaces_url ) if spaces_url
103
+
104
+ spaces_url
102
105
rescue StandardError => e
103
106
Rails . logger . error ( "Failed to generate OG image for user #{ @user . id } : #{ e . message } " )
104
- nil # Return nil to indicate failure without raising an exception
107
+ nil
108
+ ensure
109
+ FileUtils . remove_entry ( temp_dir ) if temp_dir && File . exist? ( temp_dir )
105
110
end
106
111
end
107
112
113
+ private
114
+
108
115
def valid_image_url? ( url )
109
116
return true if url . start_with? ( 'https://linkarooie.syd1.digitaloceanspaces.com/' )
110
117
return false if url . blank?
111
118
112
- begin
113
- uri = URI . parse ( url )
114
- return false unless uri . is_a? ( URI ::HTTP ) || uri . is_a? ( URI ::HTTPS )
115
- return false if uri . host . nil?
116
-
117
- response = fetch_head ( uri )
118
- return response . is_a? ( Net ::HTTPSuccess ) && response [ 'Content-Type' ] . to_s . start_with? ( 'image/' )
119
- rescue URI ::InvalidURIError , SocketError , Errno ::ECONNREFUSED , Net ::OpenTimeout , OpenSSL ::SSL ::SSLError => e
120
- Rails . logger . error ( "Invalid or unreachable URL: #{ url } . Error: #{ e . message } ." )
121
- false
119
+ uri = URI . parse ( url )
120
+ response = Net ::HTTP . start ( uri . host , uri . port , use_ssl : uri . scheme == 'https' ) do |http |
121
+ http . head ( uri . path )
122
122
end
123
+
124
+ response . is_a? ( Net ::HTTPSuccess ) && response [ 'Content-Type' ] . to_s . start_with? ( 'image/' )
125
+ rescue StandardError => e
126
+ Rails . logger . error ( "Error validating image URL: #{ e . message } " )
127
+ false
123
128
end
124
129
125
130
def download_image ( url )
126
- return MiniMagick ::Image . open ( url ) if url . start_with? ( 'https://linkarooie.syd1.digitaloceanspaces.com/' )
127
-
128
- uri = URI . parse ( url )
129
- response = Net ::HTTP . get_response ( uri )
130
-
131
- if response . is_a? ( Net ::HTTPSuccess )
132
- content_type = response [ 'Content-Type' ]
133
-
134
- if content_type . to_s . start_with? ( 'image/' )
131
+ if url . start_with? ( 'https://linkarooie.syd1.digitaloceanspaces.com/' )
132
+ MiniMagick ::Image . open ( url )
133
+ else
134
+ response = Net ::HTTP . get_response ( URI . parse ( url ) )
135
+
136
+ if response . is_a? ( Net ::HTTPSuccess ) && response [ 'Content-Type' ] . to_s . start_with? ( 'image/' )
135
137
MiniMagick ::Image . read ( response . body )
136
138
else
137
- handle_invalid_image ( "URL does not point to an image: #{ url } . Content-Type: #{ content_type } ." )
139
+ MiniMagick :: Image . open ( Rails . root . join ( 'public' , 'avatars' , 'default_avatar.jpg' ) )
138
140
end
139
- else
140
- handle_invalid_image ( "Failed to download image from URL: #{ url } . HTTP Error: #{ response . code } #{ response . message } ." )
141
141
end
142
142
rescue StandardError => e
143
- handle_invalid_image ( "Failed to download image from URL: #{ url } . Error: #{ e . message } ." )
144
- end
145
-
146
- private
147
-
148
- def fetch_head ( uri )
149
- Net ::HTTP . start ( uri . host , uri . port , use_ssl : uri . scheme == 'https' , open_timeout : 5 , read_timeout : 5 ) do |http |
150
- http . request_head ( uri . path )
151
- end
152
- end
153
-
154
- def handle_invalid_image ( error_message )
155
- Rails . logger . error ( error_message )
143
+ Rails . logger . error ( "Failed to download image: #{ e . message } " )
156
144
MiniMagick ::Image . open ( Rails . root . join ( 'public' , 'avatars' , 'default_avatar.jpg' ) )
157
145
end
158
146
159
147
def escape_text ( text )
160
- text . gsub ( "'" , "\\ \\ '" )
148
+ text . gsub ( "\\ " , " \\ \\ \\ " ) . gsub ( " '", "\\ \\ '" )
161
149
end
162
150
end
0 commit comments