Skip to content

Commit

Permalink
Merge pull request #355 from slovensko-digital/GO-66/add_api_for_drafts
Browse files Browse the repository at this point in the history
GO-66 API for Upvs::Drafts
  • Loading branch information
luciajanikova authored Apr 11, 2024
2 parents 5f3cfec + c5fba0d commit 5aa5f92
Show file tree
Hide file tree
Showing 33 changed files with 5,183 additions and 46 deletions.
18 changes: 12 additions & 6 deletions app/components/message_draft_body_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,20 @@
<% end %>
</div>
<% end %>
<% unless @message.not_yet_submitted? %>
<% if @message.invalid? || @message.submit_failed? %>
<div class="flex justify-center items-center flex-grow-0 flex-shrink-0 relative overflow-hidden gap-1.5 px-1.5 py-0.5 rounded-md bg-red-50 border border-red-300">
<% if @message.invalid? %>
<p class="flex-grow-0 flex-shrink-0 text-sm text-left text-red-600">Správa nie je validná</p>
<% elsif @message.submit_failed? %>
<p class="flex-grow-0 flex-shrink-0 text-sm text-left text-red-600">Správu sa nepodarilo odoslať</p>
<% end %>
</div>
<% elsif @message.being_submitted? || @message.submitted? %>
<div class="flex justify-center items-center flex-grow-0 flex-shrink-0 relative overflow-hidden gap-1.5 px-1.5 py-0.5 rounded-md bg-purple-50 border border-purple-300">
<% if @message.being_submitted? %>
<p class="flex-grow-0 flex-shrink-0 text-sm text-left text-purple-600">Správa sa odosiela</p>
<% elsif @message.submitted? %>
<% if @message.submitted? %>
<p class="flex-grow-0 flex-shrink-0 text-sm text-left text-purple-600">Správa bola odoslaná</p>
<% elsif @message.submit_failed? %>
<p class="flex-grow-0 flex-shrink-0 text-sm text-left text-purple-600">Správu sa nepodarilo odoslať</p>
<% elsif @message.being_submitted? %>
<p class="flex-grow-0 flex-shrink-0 text-sm text-left text-purple-600">Správa sa odosiela</p>
<% end %>
</div>
<% end %>
Expand Down
12 changes: 7 additions & 5 deletions app/components/message_draft_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
</div>
</div>
<% end %>
<div class="gap-2 p-6 border-t-0 border-r-0 border-b border-l-0 border-gray-200">
<%= button_to message_draft_path(@message), method: :delete, class: "flex justify-strech items-start self-stretch flex-grow-0 flex-shrink-0 gap-2 p-6 rounded-lg border border-gray-300 px-3.5 py-2.5 hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 rounded-lg", data: { turbo_frame: "_top" }, title: "Zahodiť draft" do %>
<%= render Icons::TrashComponent.new(css_classes: "w-5 h-5 text-red-600") %>
<% end %>
</div>
<% if @message.not_yet_submitted? %>
<div class="gap-2 p-6 border-t-0 border-r-0 border-b border-l-0 border-gray-200">
<%= button_to message_draft_path(@message), method: :delete, class: "flex justify-strech items-start self-stretch flex-grow-0 flex-shrink-0 gap-2 p-6 rounded-lg border border-gray-300 px-3.5 py-2.5 hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 rounded-lg", data: { turbo_frame: "_top" }, title: "Zahodiť draft" do %>
<%= render Icons::TrashComponent.new(css_classes: "w-5 h-5 text-red-600") %>
<% end %>
</div>
<% end %>
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/components/message_thread_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div class="flex flex-col justify-stretch items-stretch gap-2 pt-4 pb-16 sm:px-4" data-test="messages">
<%= render Common::FlashComponent.new %>
<% @thread_messages.each do |message| %>
<% if message.type == 'MessageDraft' %>
<% if message.draft? %>
<%# TODO find a better way for handling focus than `message.id == @thread_last_message_draft_id` %>
<%= render MessageDraftComponent.new(message: message, signable: Current.user.signer?, is_last: message.id == @thread_last_message_draft_id) %>
<% elsif message.collapsed? %>
Expand Down
2 changes: 1 addition & 1 deletion app/components/message_thread_log_item_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<div class="relative flex space-x-3">
<div>
<span class="h-8 w-8 rounded-full bg-gray-200 flex items-center justify-center ring-8 ring-white">
<% if @message.type == 'MessageDraft' %>
<% if @message.draft? %>
<%= render Icons::PencilSquareComponent.new(css_classes: "w-5 h-5 text-gray-600") %>
<% elsif @message.collapsed %>
<%= render Icons::CogEightToothComponent.new(css_classes: "w-5 h-5 text-gray-600") %>
Expand Down
98 changes: 98 additions & 0 deletions app/controllers/api/messages_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,103 @@
class Api::MessagesController < Api::TenantController
before_action :set_en_locale
before_action :load_box, only: :message_drafts
before_action :check_message_type, only: :message_drafts
before_action :check_tags, only: :message_drafts

ALLOWED_MESSAGE_TYPES = ['Upvs::MessageDraft']

def show
@message = @tenant.messages.find(params[:id])
end

def message_drafts
::Message.transaction do
@message = permitted_params[:type].classify.safe_constantize.load_from_params(permitted_params, box: @box)
render_unprocessable_entity(@message.errors.messages.values.join(', ')) and return unless @message.valid?
@message.save

permitted_params.fetch(:objects, []).each do |object_params|
message_object = @message.objects.create(object_params.except(:content, :tags))

object_params.fetch(:tags, []).each do |tag_name|
tag = @tenant.tags.find_by(name: tag_name)
tag.assign_to_message_object(message_object)
tag.assign_to_thread(@message.thread)
end
@message.thread.box.tenant.signed_externally_tag!.assign_to_message_object(message_object) if message_object.is_signed

MessageObjectDatum.create(
message_object: message_object,
blob: Base64.decode64(object_params[:content])
)
end

permitted_params.fetch(:tags, []).each do |tag_name|
tag = @tenant.tags.find_by(name: tag_name)
@message.add_cascading_tag(tag)
end

if @message.valid?(:validate_data)
@message.metadata['status'] = 'created'
@message.save

head :created
else
@message.destroy

render_unprocessable_entity(@message.errors.messages.values.join(', '))
end
end
end

private

def permitted_params
params.permit(
:type,
:uuid,
:title,
metadata: [
:correlation_id,
:reference_id,
:sender_uri,
:recipient_uri,
:sender_business_reference,
:recipient_business_reference,
:posp_id,
:posp_version,
:message_type,
:sktalk_class
],
objects: [
:name,
:is_signed,
:to_be_signed,
:mimetype,
:object_type,
:content,
tags: []
],
tags: []
)
end

def check_message_type
render_bad_request(ActionController::BadRequest.new("Disallowed message type: #{params[:type]}")) unless params[:type].in?(ALLOWED_MESSAGE_TYPES)
end

def check_tags
tag_names = permitted_params.fetch(:tags, []) + permitted_params.fetch(:objects, []).map {|o| o['tags'] }.compact

tag_names.each do |tag_name|
@tenant.tags.find_by!(name: tag_name)
rescue ActiveRecord::RecordNotFound
render_unprocessable_entity("Tag with name #{tag_name} does not exist") and return
end
end

def load_box
@box = @tenant.boxes.find_by(uri: permitted_params[:metadata][:sender_uri])
render_unprocessable_entity('Invalid sender') and return unless @box
end
end
5 changes: 3 additions & 2 deletions app/controllers/api_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
class ApiController < ActionController::API
include Localization
before_action :authenticate_user
around_action :wrap_in_request_logger

Expand Down Expand Up @@ -89,7 +90,7 @@ def render_service_unavailable_error
render status: :service_unavailable, json: { message: "Service unavailable" }
end

def render_unprocessable_entity(exception)
render status: :unprocessable_entity, json: { message: exception.message }
def render_unprocessable_entity(message)
render status: :unprocessable_entity, json: { message: message }
end
end
1 change: 1 addition & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class ApplicationController < ActionController::Base
include Authentication
include Pundit::Authorization
include Localization
after_action :verify_authorized
after_action :verify_policy_scoped, only: :index if respond_to?(:index)
before_action :set_menu_context
Expand Down
15 changes: 15 additions & 0 deletions app/controllers/concerns/localization.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Localization
extend ActiveSupport::Concern

included do
before_action :set_sk_locale
end

def set_en_locale
I18n.locale = :en
end

def set_sk_locale
I18n.locale = :sk
end
end
1 change: 1 addition & 0 deletions app/controllers/message_drafts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def submit
redirect_to message_thread_path(@message.thread), notice: "Správa bola zaradená na odoslanie"
else
# TODO: prisposobit chybovu hlasku aj importovanym draftom
# TODO FIX: Tato hlaska sa zobrazuje aj ked je object oznaceny ako to_be_signed, ale nebol este podpisany
redirect_to message_thread_path(@message.thread), alert: "Vyplňte obsah správy"
end
end
Expand Down
4 changes: 4 additions & 0 deletions app/jobs/govbox/submit_message_draft_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def perform(message_draft, schedule_sync: true, upvs_client: UpvsEnvironment.upv
success, response_status, response_body = sktalk_api.receive_and_save_to_outbox(message_draft_data)

if success
message_draft.remove_cascading_tag(message_draft.tenant.submission_error_tag)
message_draft.submitted!
Govbox::SyncBoxJob.set(wait: 3.minutes).perform_later(message_draft.thread.box) if schedule_sync
else
Expand Down Expand Up @@ -57,6 +58,9 @@ def build_objects(message_draft)
end

def handle_submit_fail(message_draft, response_status, response_message)
# TODO notification
message_draft.add_cascading_tag(message_draft.tenant.submission_error_tag)

case response_status
when 408, 503
message_draft.metadata["status"] = "temporary_submit_fail"
Expand Down
1 change: 1 addition & 0 deletions app/lib/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module Utils

UUID_PATTERN = %r{\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z}
EXTENSIONS_ALLOW_LIST = %w(pdf xml asice asics xzep zip txt doc docx jpg jpeg png tif tiff)
MIMETYPES_ALLOW_LIST = %w(application/x-eform-xml application/xml application/msword application/pdf application/vnd.etsi.asic-e+zip application/vnd.etsi.asic-s+zip application/vnd.openxmlformats-officedocument.wordprocessingml.document application/x-xades_zep application/x-zip-compressed image/jpg image/jpeg image/png image/tiff)

def file_directory(file_path)
File.dirname(file_path)
Expand Down
44 changes: 32 additions & 12 deletions app/models/message_draft.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
class MessageDraft < Message
belongs_to :import, class_name: 'MessageDraftsImport', optional: true

validate :validate_uuid
validates :title, presence: { message: "Title can't be blank" }
validates :delivered_at, presence: true

after_create do
add_cascading_tag(thread.box.tenant.draft_tag!)
end
Expand All @@ -49,7 +53,6 @@ class MessageDraft < Message
end

with_options on: :validate_data do |message_draft|
message_draft.validates :uuid, format: { with: Utils::UUID_PATTERN }, allow_blank: false
message_draft.validate :validate_metadata
message_draft.validate :validate_form
message_draft.validate :validate_objects
Expand Down Expand Up @@ -99,7 +102,7 @@ def invalid?
end

def not_yet_submitted?
metadata["status"] == "created" || metadata["status"] == "invalid"
metadata["status"].in?(%w[created invalid])
end

def being_submitted?
Expand All @@ -111,7 +114,7 @@ def submitted?
end

def submit_failed?
metadata["status"] == "submit_fail"
metadata["status"].in?(%w[submit_fail temporary_submit_fail])
end

def being_submitted!
Expand Down Expand Up @@ -156,7 +159,12 @@ def create_form_object

# TODO remove UPVS stuff from core domain
def validate_form
raise "Disallowed form" unless ::Upvs::ServiceWithFormAllowRule.matching_metadata(all_metadata).any?
return if errors[:metadata].any?

unless ::Upvs::ServiceWithFormAllowRule.matching_metadata(all_metadata).where(institution_uri: metadata['recipient_uri']).any?
errors.add(:base, :disallowed_form_for_recipient)
return
end

raise "Missing XSD schema" unless upvs_form&.xsd_schema

Expand All @@ -178,25 +186,37 @@ def validate_form
end

private

def validate_uuid
if uuid
errors.add(:metadata, "UUID must be in UUID format") unless uuid.match?(Utils::UUID_PATTERN)
else
errors.add(:metadata, "UUID can't be blank")
end
end

def validate_objects
errors.add(:objects, "No objects found for draft") if objects.size == 0
if objects.size == 0
errors.add(:objects, "Message contains no objects")
return
end

objects.each do |object|
object.valid?(:validate_data)
errors.merge!(object.errors)
end

forms = objects.select { |o| o.form? }
errors.add(:objects, "Draft has to contain exactly one form") if forms.size != 1
errors.add(:objects, "Message has to contain exactly one form object") if forms.size != 1
end

def validate_metadata
errors.add(:metadata, "No recipient URI") unless all_metadata["recipient_uri"].present?
errors.add(:metadata, "No posp ID") unless all_metadata["posp_id"].present?
errors.add(:metadata, "No posp version") unless all_metadata["posp_version"].present?
errors.add(:metadata, "No message type") unless all_metadata["message_type"].present?
errors.add(:metadata, "No correlation ID") unless all_metadata["correlation_id"].present?
errors.add(:metadata, "Correlation ID must be UUID") unless all_metadata["correlation_id"]&.match?(Utils::UUID_PATTERN)
errors.add(:metadata, "No recipient URI") unless all_metadata&.dig("recipient_uri")
errors.add(:metadata, "No posp ID") unless all_metadata&.dig("posp_id")
errors.add(:metadata, "No posp version") unless all_metadata&.dig("posp_version")
errors.add(:metadata, "No message type") unless all_metadata&.dig("message_type")

errors.add(:metadata, "Reference ID must be UUID") if all_metadata&.dig("reference_id") && !all_metadata&.dig("reference_id")&.match?(Utils::UUID_PATTERN)
end

def validate_with_message_template
Expand Down
16 changes: 10 additions & 6 deletions app/models/message_object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class MessageObject < ApplicationRecord
scope :to_be_signed, -> { where(to_be_signed: true) }
scope :should_be_signed, -> { where(to_be_signed: true, is_signed: false) }

validates :name, presence: true, on: :validate_data
validates :name, presence: { message: "Name can't be blank" }, on: :validate_data
validate :allowed_mime_type?, on: :validate_data

after_update ->(message_object) { EventBus.publish(:message_object_changed, message_object) }
Expand Down Expand Up @@ -107,20 +107,24 @@ def downloadable_archived_object?
archived_object&.archived?
end

def assign_tag(tag)
message_objects_tags.find_or_create_by!(tag: tag)
end

private

def allowed_mime_type?
errors.add(:mime_type, "of #{name} object is disallowed, allowed_mime_types: #{Utils::EXTENSIONS_ALLOW_LIST.join(", ")}") unless mimetype
if mimetype
errors.add(:mime_type, "MimeType of #{name} object is disallowed, allowed mimetypes: #{Utils::MIMETYPES_ALLOW_LIST.join(", ")}") unless Utils::MIMETYPES_ALLOW_LIST.include?(mimetype)
else
errors.add(:mime_type, "MimeType of #{name} object is disallowed, allowed file types: #{Utils::EXTENSIONS_ALLOW_LIST.join(", ")}")
end
end

def has_tag?(tag)
message_objects_tags.joins(:tag).where(tag: tag).exists?
end

def assign_tag(tag)
message_objects_tags.find_or_create_by!(tag: tag)
end

def unassign_tag(tag)
message_objects_tags.find_by(tag: tag)&.destroy
end
Expand Down
8 changes: 4 additions & 4 deletions app/models/message_thread.rb
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ def remove_signature_requested_from_user(user)
end
end

def assign_tag(tag)
message_threads_tags.find_or_create_by!(tag: tag)
end

private

def has_tag?(tag)
Expand All @@ -168,10 +172,6 @@ def has_tag_in_message_objects?(tag)
objects.joins(:tags).where(tags: tag).exists?
end

def assign_tag(tag)
message_threads_tags.find_or_create_by!(tag: tag)
end

def unassign_tag(tag)
message_threads_tags.find_by(tag: tag)&.destroy
end
Expand Down
Loading

0 comments on commit 5aa5f92

Please sign in to comment.