diff --git a/Gemfile b/Gemfile index f2501e6..0a2f834 100644 --- a/Gemfile +++ b/Gemfile @@ -42,7 +42,7 @@ gem "tzinfo-data", platforms: %i[ windows jruby ] gem "bootsnap", require: false # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] -# gem "image_processing", "~> 1.2" +gem "image_processing", "~> 1.2" group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem @@ -66,3 +66,5 @@ group :test do gem "capybara" gem "selenium-webdriver" end + +gem "docraptor" diff --git a/Gemfile.lock b/Gemfile.lock index 238c3c2..ad78f39 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -98,13 +98,21 @@ GEM debug (1.8.0) irb (>= 1.5.0) reline (>= 0.3.1) + docraptor (3.1.0) + typhoeus (~> 1.0, >= 1.0.1) drb (2.1.1) ruby2_keywords erubi (1.12.0) + ethon (0.16.0) + ffi (>= 1.15.0) + ffi (1.16.3) globalid (1.2.1) activesupport (>= 6.1) i18n (1.14.1) concurrent-ruby (~> 1.0) + image_processing (1.12.2) + mini_magick (>= 4.9.5, < 5) + ruby-vips (>= 2.0.17, < 3) importmap-rails (1.2.1) actionpack (>= 6.0.0) railties (>= 6.0.0) @@ -125,6 +133,7 @@ GEM net-smtp marcel (1.0.2) matrix (0.4.2) + mini_magick (4.12.0) mini_mime (1.1.5) minitest (5.20.0) msgpack (1.7.2) @@ -200,6 +209,8 @@ GEM reline (0.3.9) io-console (~> 0.5) rexml (3.2.6) + ruby-vips (2.2.0) + ffi (~> 1.12) ruby2_keywords (0.0.5) rubyzip (2.3.2) selenium-webdriver (4.14.0) @@ -222,6 +233,8 @@ GEM actionpack (>= 6.0.0) activejob (>= 6.0.0) railties (>= 6.0.0) + typhoeus (1.4.0) + ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) web-console (4.2.1) @@ -247,6 +260,8 @@ DEPENDENCIES bootsnap capybara debug + docraptor + image_processing (~> 1.2) importmap-rails jbuilder pg (~> 1.1) diff --git a/app/controllers/invoices_controller.rb b/app/controllers/invoices_controller.rb index f5ff0c5..ff8b37d 100644 --- a/app/controllers/invoices_controller.rb +++ b/app/controllers/invoices_controller.rb @@ -16,6 +16,7 @@ def create respond_to do |format| if @invoice.save + InvoicePdfJob.perform_now(@invoice) format.html { redirect_to invoice_url(@invoice), notice: "Invoice was successfully created." } else format.html { render :new, status: :unprocessable_entity } diff --git a/app/jobs/invoice_pdf_job.rb b/app/jobs/invoice_pdf_job.rb new file mode 100644 index 0000000..92f2dcd --- /dev/null +++ b/app/jobs/invoice_pdf_job.rb @@ -0,0 +1,55 @@ +DocRaptor.configure do |config| + config.username = "YOUR_API_KEY_HERE" + # config.debugging = true +end + +class InvoicePdfJob < ApplicationJob + queue_as :default + + def perform(invoice) + docraptor = DocRaptor::DocApi.new + + document_content = ApplicationController.render( + template: "invoices/show", + layout: "layouts/pdf", + assigns: { invoice: invoice } + ) + + begin + response = docraptor.create_doc( + test: true, # test documents are free but watermarked + document_type: "pdf", + document_content: document_content, + # document_content: "Hello World!", + # document_url: "https://docraptor.com/examples/invoice.html", + # javascript: true, + # prince_options: { + # media: "print", # @media 'screen' or 'print' CSS + # baseurl: "https://yoursite.com", # the base URL for any relative URLs + # } + ) + + # hosted_document_url = docraptor.create_hosted_doc( + # test: true, # test documents are free but watermarked + # document_type: "pdf", + # document_content: document_content, + # ) + # invoice.update(docraptor_document_url: hosted_document_url.download_url) + + # ActiveStorage + invoice.pdf_document.attach(io: StringIO.new(response), filename: "invoice#{invoice.id}.pdf", content_type: "application/pdf") + # ActionMailer + InvoiceMailer.created(invoice).deliver_later if invoice.pdf_document.attached? + + # store it locally + File.write("docraptor-hello.pdf", response, mode: "wb") + puts "Successfully created docraptor-hello.pdf!" + rescue DocRaptor::ApiError => error + puts "#{error.class}: #{error.message}" + puts error.code + puts error.response_body + puts error.backtrace[0..3].join("\n") + end + # Do something later + end +end diff --git a/app/mailers/invoice_mailer.rb b/app/mailers/invoice_mailer.rb new file mode 100644 index 0000000..f57817e --- /dev/null +++ b/app/mailers/invoice_mailer.rb @@ -0,0 +1,9 @@ +class InvoiceMailer < ApplicationMailer + def created(invoice) + @invoice = invoice + @greeting = "Hi" + + attachments["invoice.pdf"] = @invoice.pdf_document.download if @invoice.pdf_document.attached? + mail to: invoice.email, subject: "Invoice #{@invoice.id}" + end +end diff --git a/app/models/invoice.rb b/app/models/invoice.rb index 6ec0ad6..9a96138 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -2,6 +2,7 @@ class Invoice < ApplicationRecord validates :email, :product, :price, :quantity, :total, presence: true before_validation :calculate_total + has_one_attached :pdf_document private diff --git a/app/views/invoice_mailer/created.html.erb b/app/views/invoice_mailer/created.html.erb new file mode 100644 index 0000000..84a6925 --- /dev/null +++ b/app/views/invoice_mailer/created.html.erb @@ -0,0 +1,5 @@ +

Invoice#created

+ +

+ <%= @greeting %>, find me in app/views/invoice_mailer/created.html.erb +

diff --git a/app/views/invoices/show.html.erb b/app/views/invoices/show.html.erb index 2a0e60d..bdfd01a 100644 --- a/app/views/invoices/show.html.erb +++ b/app/views/invoices/show.html.erb @@ -1 +1,11 @@ <%= render @invoice %> + +<% if @invoice.pdf_document.attached? %> +<% if @invoice.pdf_document.representable? %> + <%= image_tag @invoice.pdf_document.representation(resize_to_limit: [200, 200]) %> +<% end %> +<%= @invoice.pdf_document.blob.filename %> +<%= number_to_human_size @invoice.pdf_document.blob.byte_size %> +<%= link_to 'Open in new tab', rails_blob_path(@invoice.pdf_document, disposition: "inline"), target: :_blank %> +<%= link_to 'Download', rails_blob_path(@invoice.pdf_document, disposition: "attachment") %> +<% end %> \ No newline at end of file diff --git a/app/views/layouts/pdf.html.erb b/app/views/layouts/pdf.html.erb new file mode 100644 index 0000000..5a5d637 --- /dev/null +++ b/app/views/layouts/pdf.html.erb @@ -0,0 +1,16 @@ + + + + DocraptorHtmlToPdf + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%#= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%#= javascript_importmap_tags %> + + + + <%= yield %> + + diff --git a/db/migrate/20231202165640_create_active_storage_tables.active_storage.rb b/db/migrate/20231202165640_create_active_storage_tables.active_storage.rb new file mode 100644 index 0000000..e4706aa --- /dev/null +++ b/db/migrate/20231202165640_create_active_storage_tables.active_storage.rb @@ -0,0 +1,57 @@ +# This migration comes from active_storage (originally 20170806125915) +class CreateActiveStorageTables < ActiveRecord::Migration[7.0] + def change + # Use Active Record's configured type for primary and foreign keys + primary_key_type, foreign_key_type = primary_and_foreign_key_types + + create_table :active_storage_blobs, id: primary_key_type do |t| + t.string :key, null: false + t.string :filename, null: false + t.string :content_type + t.text :metadata + t.string :service_name, null: false + t.bigint :byte_size, null: false + t.string :checksum + + if connection.supports_datetime_with_precision? + t.datetime :created_at, precision: 6, null: false + else + t.datetime :created_at, null: false + end + + t.index [ :key ], unique: true + end + + create_table :active_storage_attachments, id: primary_key_type do |t| + t.string :name, null: false + t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type + t.references :blob, null: false, type: foreign_key_type + + if connection.supports_datetime_with_precision? + t.datetime :created_at, precision: 6, null: false + else + t.datetime :created_at, null: false + end + + t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + + create_table :active_storage_variant_records, id: primary_key_type do |t| + t.belongs_to :blob, null: false, index: false, type: foreign_key_type + t.string :variation_digest, null: false + + t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + end + + private + def primary_and_foreign_key_types + config = Rails.configuration.generators + setting = config.options[config.orm][:primary_key_type] + primary_key_type = setting || :primary_key + foreign_key_type = setting || :bigint + [primary_key_type, foreign_key_type] + end +end diff --git a/db/migrate/20231202171526_add_docraptor_document_url_to_invoices.rb b/db/migrate/20231202171526_add_docraptor_document_url_to_invoices.rb new file mode 100644 index 0000000..1672083 --- /dev/null +++ b/db/migrate/20231202171526_add_docraptor_document_url_to_invoices.rb @@ -0,0 +1,5 @@ +class AddDocraptorDocumentUrlToInvoices < ActiveRecord::Migration[7.1] + def change + add_column :invoices, :docraptor_document_url, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index 3693b36..a665bc9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,10 +10,38 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2023_11_16_064754) do +ActiveRecord::Schema[7.1].define(version: 2023_12_02_171526) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "active_storage_attachments", force: :cascade do |t| + t.string "name", null: false + t.string "record_type", null: false + t.bigint "record_id", null: false + t.bigint "blob_id", null: false + t.datetime "created_at", null: false + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true + end + + create_table "active_storage_blobs", force: :cascade do |t| + t.string "key", null: false + t.string "filename", null: false + t.string "content_type" + t.text "metadata" + t.string "service_name", null: false + t.bigint "byte_size", null: false + t.string "checksum" + t.datetime "created_at", null: false + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true + end + + create_table "active_storage_variant_records", force: :cascade do |t| + t.bigint "blob_id", null: false + t.string "variation_digest", null: false + t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true + end + create_table "invoices", force: :cascade do |t| t.string "email" t.string "product" @@ -22,6 +50,9 @@ t.integer "total" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.text "docraptor_document_url" end + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" + add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" end diff --git a/test/mailers/previews/invoice_mailer_preview.rb b/test/mailers/previews/invoice_mailer_preview.rb new file mode 100644 index 0000000..6b1e408 --- /dev/null +++ b/test/mailers/previews/invoice_mailer_preview.rb @@ -0,0 +1,9 @@ +# Preview all emails at http://localhost:3000/rails/mailers/invoice_mailer +class InvoiceMailerPreview < ActionMailer::Preview + + # Preview this email at http://localhost:3000/rails/mailers/invoice_mailer/created + def created + InvoiceMailer.created(Invoice.last) + end + +end