v0.13 Support for import and attachment of images/attachments to posts

This commit is contained in:
David Sainty 2024-06-22 15:13:41 +10:00
parent 9825f919b9
commit 4816641be3

View File

@ -1,5 +1,5 @@
# gossamer threads migration-import code # gossamer threads migration-import code
# v0.12 # v0.13
require 'mysql2' require 'mysql2'
require 'open-uri' require 'open-uri'
@ -10,6 +10,7 @@ require 'sqlite3'
require 'digest' require 'digest'
require 'fileutils' require 'fileutils'
require 'csv' require 'csv'
require 'time'
require File.expand_path("../../../config/environment", __FILE__) require File.expand_path("../../../config/environment", __FILE__)
require_relative 'base' require_relative 'base'
@ -182,12 +183,12 @@ class GossamerForumsImporter < ImportScripts::Base
email email
end end
# Helper method to download an image from a URL # Helper method to download an attachment / image from a URL
def download_image(url) def download_attachment(url)
begin begin
URI.open(url).read URI.open(url).read
rescue OpenURI::HTTPError => e rescue OpenURI::HTTPError => e
puts "Failed to download image from #{url}: #{e.message}" puts "Failed to download attachment from #{url}: #{e.message}"
nil nil
rescue URI::InvalidURIError => e rescue URI::InvalidURIError => e
puts "Failed to handle invalid URL/URI for #{url}: #{e.message}" puts "Failed to handle invalid URL/URI for #{url}: #{e.message}"
@ -195,7 +196,8 @@ class GossamerForumsImporter < ImportScripts::Base
end end
end end
def upload_image(user, file, filename, gossamer_url) # Helper method to upload an attachment / image to Discourse
def upload_attachment(user, file, filename, gossamer_url)
begin begin
upload = Upload.create!( upload = Upload.create!(
user_id: user.id, user_id: user.id,
@ -213,14 +215,42 @@ class GossamerForumsImporter < ImportScripts::Base
# Move the file to the correct location # Move the file to the correct location
# FileUtils.mv(file.path, upload.path) # FileUtils.mv(file.path, upload.path)
upload.save! upload.save!
upload upload
rescue => e rescue => e
puts "Failed to upload image #{filename} for user #{user.username}: #{e.message}" puts "Failed to upload attachment #{filename} for user #{user.username}: #{e.message}"
nil nil
end end
end end
# Helper method to handle post attachments
def handle_post_attachments(gossamer_post_id, post, user_id)
execute_query("SELECT * FROM gforum_PostAttachment WHERE post_id_fk = #{post_id}").each do |att_row|
attachment_url = "https://forum.slowtwitch.com/forum/?do=post_attachment;postatt_id=#{att_row['postatt_id']}"
attachment_data = download_attachment(attachment_url)
next unless attachment_data
mime_type = att_row['postatt_content']
temp_file = Tempfile.new(['attachment', File.extname(att_row['postatt_filename'])])
temp_file.binmode
temp_file.write(attachment_data)
temp_file.rewind
upload = upload_attachment(user_id, temp_file, att_row['postatt_filename'], attachment_url)
next unless upload
upload_url = upload.url
if mime_type.start_with?('image/')
post.raw += "\n![#{att_row['postatt_filename']}](#{upload_url})"
else
post.raw += "\n[#{att_row['postatt_filename']}](#{upload_url})"
end
post.save!
temp_file.close
temp_file.unlink
end
end
# def download_file(url) # def download_file(url)
# require 'open-uri' # require 'open-uri'
@ -372,7 +402,7 @@ class GossamerForumsImporter < ImportScripts::Base
next unless ['image/jpeg', 'image/png'].include?(file['File_MimeType']) next unless ['image/jpeg', 'image/png'].include?(file['File_MimeType'])
puts "#B" puts "#B"
image_data = download_image(file_url) image_data = download_attachment(file_url)
next if image_data.nil? next if image_data.nil?
puts "#C" puts "#C"
@ -383,7 +413,7 @@ class GossamerForumsImporter < ImportScripts::Base
if images_imported == 0 if images_imported == 0
puts "#D" puts "#D"
upload = upload_image(user, temp_file, file['File_Name'], file_url) upload = upload_attachment(user, temp_file, file['File_Name'], file_url)
next if upload.nil? next if upload.nil?
user.user_avatar = UserAvatar.create!(user_id: user.id, custom_upload_id: upload.id) user.user_avatar = UserAvatar.create!(user_id: user.id, custom_upload_id: upload.id)
@ -454,8 +484,8 @@ class GossamerForumsImporter < ImportScripts::Base
end end
# Import topics and posts from Gossamer Forums to Discourse # Import topics and posts from Gossamer Forums to Discourse
def import_topics_and_posts def import_topics_and_posts_with_attachments
puts "Importing topics and posts..." puts "Importing topics and posts with attachments..."
# Execute the query to get all posts ordered by post_id # Execute the query to get all posts ordered by post_id
execute_query("SELECT * FROM gforum_Post ORDER BY post_id").each do |row| execute_query("SELECT * FROM gforum_Post ORDER BY post_id").each do |row|
@ -463,7 +493,6 @@ def import_topics_and_posts
# discourse_user_id = @user_id_map[row['user_id_fk']] # discourse_user_id = @user_id_map[row['user_id_fk']]
discourse_user_id = fetch_user_id_mapping(row['user_id_fk']) discourse_user_id = fetch_user_id_mapping(row['user_id_fk'])
discourse_category_id = fetch_category_id_mapping(row['forum_id_fk']) discourse_category_id = fetch_category_id_mapping(row['forum_id_fk'])
puts "discourse_user_id #{discourse_user_id} discourse_category_id #{discourse_category_id}" puts "discourse_user_id #{discourse_user_id} discourse_category_id #{discourse_category_id}"
next unless discourse_user_id && discourse_category_id next unless discourse_user_id && discourse_category_id
@ -490,7 +519,12 @@ def import_topics_and_posts
# Create the initial post in the topic # Create the initial post in the topic
puts "CREATE POST topic.id #{topic.id} discourse_user_id #{discourse_user_id}" puts "CREATE POST topic.id #{topic.id} discourse_user_id #{discourse_user_id}"
# Ensure the raw post stirng contents itself is acceptable to Discourse
sanitized_post_message = row['post_message']&.tr("\0", '') || "" sanitized_post_message = row['post_message']&.tr("\0", '') || ""
# Remove the [signature] label from appearing at the end of the messages after import
sanitized_post_message.sub(/\n?\[signature\]\n?\z/, '')
post = Post.create!( post = Post.create!(
topic_id: topic.id, topic_id: topic.id,
user_id: discourse_user_id, user_id: discourse_user_id,
@ -503,6 +537,9 @@ def import_topics_and_posts
post.custom_fields['original_gossamer_id'] = row['post_id'] post.custom_fields['original_gossamer_id'] = row['post_id']
post.save! post.save!
# Handle attachments for the post
handle_post_attachments(row['post_id'], post, discourse_user_id)
# Create URL mappings # Create URL mappings
# old_url = "https://old/forum/#{row['forum_name']}/topics/#{row['post_id']}" # old_url = "https://old/forum/#{row['forum_name']}/topics/#{row['post_id']}"
new_url = "https://new/t/#{topic.slug}/#{topic.id}" new_url = "https://new/t/#{topic.slug}/#{topic.id}"
@ -528,7 +565,12 @@ def import_topics_and_posts
# Create the post in the existing topic # Create the post in the existing topic
begin begin
puts "#4" puts "#4"
# Ensure the raw post string contents itself is acceptable to Discourse
sanitized_post_message = row['post_message']&.tr("\0", '') || "" sanitized_post_message = row['post_message']&.tr("\0", '') || ""
# Remove the [signature] label from appearing at the end of the messages after import
sanitized_post_message.sub(/\n?\[signature\]\n?\z/, '')
post = Post.create!( post = Post.create!(
topic_id: topic_id, topic_id: topic_id,
user_id: discourse_user_id, user_id: discourse_user_id,
@ -541,6 +583,9 @@ def import_topics_and_posts
) )
post.custom_fields['original_gossamer_id'] = row['post_id'] post.custom_fields['original_gossamer_id'] = row['post_id']
post.save! post.save!
# Handle attachments for the post
handle_post_attachments(row['post_id'], post, discourse_user_id)
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e
puts "Error importing post with post_id #{row['post_id']}: #{e.message}" puts "Error importing post with post_id #{row['post_id']}: #{e.message}"
end end
@ -582,7 +627,8 @@ def import_personal_messages
# Create a private message topic in Discourse # Create a private message topic in Discourse
topic = Topic.create!( topic = Topic.create!(
title: row['msg_subject'], # title: row['msg_subject'],
title: title,
user_id: from_user_id, user_id: from_user_id,
archetype: Archetype.private_message, archetype: Archetype.private_message,
created_at: Time.at(row['msg_time']), created_at: Time.at(row['msg_time']),
@ -605,27 +651,13 @@ def import_personal_messages
# Add recipient user to the private message topic # Add recipient user to the private message topic
topic.topic_allowed_users.create!(user_id: to_user_id) topic.topic_allowed_users.create!(user_id: to_user_id)
# handle_post_attachments(row['msg_id'], post, from_user_id)
end end
end end
end end
# Import attachments for a post
def import_post_attachments(post_message, post_id)
# Fetch attachments related to the post
attachments = execute_query("SELECT * FROM gforum_PostAttachment WHERE post_id_fk = #{post_id}")
attachments.each do |attachment|
# Append attachment links to the post message
file_url = "https://forum.slowtwitch.com/images/posts/attachments/#{attachment['ID'] % 10}/#{attachment['ID']}-#{attachment['File_Name']}"
post_message += "\n\n![#{attachment['File_Name']}](#{file_url})"
end
1# post_message
end
# Main method to perform the import # Main method to perform the import
def perform_import def perform_import
@ -641,14 +673,12 @@ end
export_username_mapping_to_csv("gossamer-migration-username-mapping#{timestamp}") export_username_mapping_to_csv("gossamer-migration-username-mapping#{timestamp}")
import_categories import_categories
import_topics_and_posts import_topics_and_posts_with_attachments
export_url_mapping_to_csv("gossamer-migration-url-mapping#{timestamp}") export_url_mapping_to_csv("gossamer-migration-url-mapping#{timestamp}")
create_nginx_rewrite_rules("gossamer-redirects.conf") create_nginx_rewrite_rules("gossamer-redirects.conf")
import_personal_messages import_personal_messages
# import_attachments
puts "Gossamer Forums import complete! #{timestamp}" puts "Gossamer Forums import complete! #{timestamp}"
end end
end end