From 2f33d0b7912ceb035d68648bf9130ceffefbee60 Mon Sep 17 00:00:00 2001 From: Brooke Kuhlmann Date: Wed, 12 Aug 2026 08:30:19 -0600 Subject: [PATCH] Added designs importer Necessary to handle the validation and importing of data into the database for a design (screen template). Milestone: minor --- app/aspects/designs/importer.rb | 71 +++++++++++++++++++++++ spec/app/aspects/designs/importer_spec.rb | 50 ++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 app/aspects/designs/importer.rb create mode 100644 spec/app/aspects/designs/importer_spec.rb diff --git a/app/aspects/designs/importer.rb b/app/aspects/designs/importer.rb new file mode 100644 index 00000000..eab9756b --- /dev/null +++ b/app/aspects/designs/importer.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require "core" +require "dry/monads" +require "initable" +require "pipeable" +require "yaml" + +module Terminus + module Aspects + module Designs + # Imports (creates) screen template from zip file. + class Importer + include Deps["aspects.unzipper", :logger, repository: "repositories.screen_template"] + include Initable[ + key_map: { + "configuration.yml" => :configuration, + "index.html.liquid" => :content + }, + problem: Aspects::Errors::Problem + ] + include Dry::Monads[:result] + include Pipeable + + def initialize( + schema: Schemas::Designs::Import, + error_joiner: Aspects::Errors::ResultJoiner, + ** + ) + @schema = schema + @error_joiner = error_joiner + super(**) + end + + def call io + process io + rescue ROM::SQL::UniqueConstraintError => error + Failure problem.duplicate(error.message, nil).detail + end + + private + + attr_reader :schema, :error_joiner + + def process io + pipe( + unzipper.call(io), + fmap { |entries| transform entries }, + validate(schema), + amap { error_joiner.call "Import", it }, + fmap { create it.to_h } + ) + end + + def transform entries + entries.transform_keys!(key_map).then { {**it, **YAML.load(it[:configuration])} } + end + + def create attributes + repository.create(attributes).tap { |screen_template| log screen_template } + end + + def log screen_template + logger.debug do + {tags: [{screen_template_id: screen_template.id}], message: "Imported design."} + end + end + end + end + end +end diff --git a/spec/app/aspects/designs/importer_spec.rb b/spec/app/aspects/designs/importer_spec.rb new file mode 100644 index 00000000..91e1dd31 --- /dev/null +++ b/spec/app/aspects/designs/importer_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "hanami_helper" + +RSpec.describe Terminus::Aspects::Designs::Importer, :db do + subject(:creator) { described_class.new } + + describe "#call" do + let :io do + manifest = {"configuration.yml" => configuration, "index.html.liquid" => "

Test

"} + Terminus::Aspects::Zipper.new.call(manifest).value! + end + + let :configuration do + <<~CONTENT + version: 1.2.3 + name: test + label: Test + CONTENT + end + + it "creates screen template" do + relation = Hanami.app["relations.screen_template"] + expectation = proc { creator.call io } + count = proc { relation.count } + + expect(&expectation).to change(&count).by(1) + end + + it "answers success" do + expect(creator.call(io)).to match(Success(kind_of(Terminus::Structs::ScreenTemplate))) + end + + context "with invalid configuration" do + let(:configuration) { "version: 1.2.3" } + + it "answers failure configuration is missing keys" do + expect(creator.call(io)).to be_failure("Import name is missing and label is missing.") + end + end + + it "answers failure when screen template isn't unique" do + Factory[:screen_template, name: "test"] + + expect(creator.call(io)).to be_failure( + %(Name must be unique. Please use a value other than "test".) + ) + end + end +end