ecoportal-api-v2 1.1.8 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.markdownlint.json +4 -0
  3. data/.rubocop.yml +1 -1
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +501 -374
  6. data/ecoportal-api-v2.gemspec +13 -12
  7. data/lib/ecoportal/api/common/concerns/benchmarkable.rb +47 -34
  8. data/lib/ecoportal/api/common/concerns/threadable.rb +41 -0
  9. data/lib/ecoportal/api/common/concerns.rb +1 -0
  10. data/lib/ecoportal/api/common/content/array_model.rb +6 -3
  11. data/lib/ecoportal/api/common/content/class_helpers.rb +12 -8
  12. data/lib/ecoportal/api/common/content/collection_model.rb +7 -7
  13. data/lib/ecoportal/api/common/content/wrapped_response.rb +11 -11
  14. data/lib/ecoportal/api/v2/page/component/reference_field.rb +17 -13
  15. data/lib/ecoportal/api/v2/page/component.rb +7 -4
  16. data/lib/ecoportal/api/v2/page/force.rb +1 -1
  17. data/lib/ecoportal/api/v2/page/stages.rb +5 -6
  18. data/lib/ecoportal/api/v2/page.rb +26 -22
  19. data/lib/ecoportal/api/v2/pages/page_stage/task.rb +63 -0
  20. data/lib/ecoportal/api/v2/pages/page_stage/tasks.rb +69 -0
  21. data/lib/ecoportal/api/v2/pages/page_stage.rb +30 -22
  22. data/lib/ecoportal/api/v2/pages/stages.rb +3 -4
  23. data/lib/ecoportal/api/v2/pages.rb +24 -14
  24. data/lib/ecoportal/api/v2/people.rb +2 -3
  25. data/lib/ecoportal/api/v2/registers.rb +28 -13
  26. data/lib/ecoportal/api/v2/s3/data.rb +27 -0
  27. data/lib/ecoportal/api/v2/s3/files/batch_upload.rb +110 -0
  28. data/lib/ecoportal/api/v2/s3/files/poll.rb +82 -0
  29. data/lib/ecoportal/api/v2/s3/files/poll_status.rb +52 -0
  30. data/lib/ecoportal/api/v2/s3/files.rb +132 -0
  31. data/lib/ecoportal/api/v2/s3/upload.rb +154 -0
  32. data/lib/ecoportal/api/v2/s3.rb +66 -0
  33. data/lib/ecoportal/api/v2.rb +10 -3
  34. data/lib/ecoportal/api/v2_version.rb +1 -1
  35. metadata +55 -54
@@ -3,7 +3,7 @@ module Ecoportal
3
3
  class V2
4
4
  class Page < Common::Content::DoubleModel
5
5
  ALLOWED_KEYS = %w[
6
- id patch_ver name template_id
6
+ id external_id patch_ver name template_id
7
7
  base_tags tags
8
8
  time_zone created_at updated_at
9
9
  components sections stages
@@ -11,9 +11,11 @@ module Ecoportal
11
11
  state task_priority
12
12
  votes_enabled upvotes downvotes
13
13
  forces force_errors subtags
14
+ tasks
14
15
  ].freeze
15
16
 
16
17
  passkey :id
18
+ passthrough :external_id
17
19
  passforced :patch_ver, default: 1
18
20
  passthrough :name, :template_id
19
21
  passarray :base_tags, :tags, order_matters: false
@@ -41,12 +43,13 @@ module Ecoportal
41
43
 
42
44
  def as_update
43
45
  super.tap do |hash|
44
- if hash
45
- hash["data"].select! do |key, value|
46
- ALLOWED_KEYS.include?(key)
47
- end
48
- return nil if (hash["data"].keys - ["patch_ver"]).empty?
46
+ next unless hash
47
+
48
+ hash["data"].select! do |key, _value|
49
+ ALLOWED_KEYS.include?(key)
49
50
  end
51
+
52
+ return nil if (hash["data"].keys - ["patch_ver"]).empty?
50
53
  end
51
54
  end
52
55
 
@@ -57,32 +60,33 @@ module Ecoportal
57
60
  # @return [String] with feedback, if for this page instance, there are any of:
58
61
  # 1. components multi-section (fields belonging to more than one section)
59
62
  def validate
60
- msg = ""
61
- if (multi = components.multi_section).length.positive?
62
- msg += "There are fields attached to more than one section:\n • "
63
- msg += multi.map do |fld|
64
- fld.label
65
- end.join("\n • ") + "\n"
66
- end
63
+ multi = components.multi_section
64
+ return true unless multi.length.positive?
67
65
 
68
- msg.empty?? true : msg
66
+ msg = ""
67
+ msg << "There are fields attached to more than one section:"
68
+ msg << "\n • "
69
+ msg << multi.map(&:label).join("\n • ")
70
+ msg << "\n"
71
+ msg
69
72
  end
70
73
 
71
74
  private
72
75
 
73
76
  def _doc_bug_fix(hash)
74
- hash.tap do |hash|
77
+ hash.tap do
75
78
  _fix_doc(hash["stages"], "flow_node_ids", "section_ids") if hash.key?("stages")
76
- if hash.key?("sections")
77
- _fix_doc(hash["sections"], "membrane_ids", "component_ids")
78
- _fix_doc(hash["sections"], "left_membrane_ids", "left_component_ids")
79
- _fix_doc(hash["sections"], "right_membrane_ids", "right_component_ids")
80
- end
79
+
80
+ next unless hash.key?("sections")
81
+
82
+ _fix_doc(hash["sections"], "membrane_ids", "component_ids")
83
+ _fix_doc(hash["sections"], "left_membrane_ids", "left_component_ids")
84
+ _fix_doc(hash["sections"], "right_membrane_ids", "right_component_ids")
81
85
  end
82
86
  end
83
87
 
84
88
  def _fix_doc(value, source, dest)
85
- value.tap do |value|
89
+ value.tap do
86
90
  case value
87
91
  when Array
88
92
  value.each {|v| _fix_doc(v, source, dest)}
@@ -91,7 +95,7 @@ module Ecoportal
91
95
  value[dest] = value[source]
92
96
  value.delete(source)
93
97
  end
94
- else
98
+ else # rubocop:disable Style/EmptyElse
95
99
  # Do nothing!
96
100
  end
97
101
  end
@@ -0,0 +1,63 @@
1
+ module Ecoportal
2
+ module API
3
+ class V2
4
+ class Pages
5
+ class PageStage
6
+ class Task < Common::Content::DoubleModel
7
+ passkey :id
8
+ passforced :patch_ver, default: 0
9
+
10
+ passthrough :name
11
+ passthrough :user_lookups, :user_ids
12
+ passthrough :strategies, :scheduled_callbacks
13
+ passboolean :historic
14
+
15
+ passboolean :complete, :completable
16
+ passdate :due, :complete_at
17
+ passthrough :complete_by_id, :completed_by_name
18
+
19
+ passthrough :last_strategy
20
+ passboolean :is_retry, :rejected
21
+ passthrough :retry_reason
22
+ passthrough :rejected_by, :rejected_by_name, :rejected_notes
23
+ passdate :rejected_at
24
+
25
+ passboolean :escalated
26
+ passdate :escalated_at
27
+
28
+ passboolean :submitted
29
+ passthrough :type
30
+ passthrough :view_type
31
+
32
+ def ooze
33
+ self._parent.ooze
34
+ end
35
+
36
+ def fill_in?
37
+ type == 'complete_page'
38
+ end
39
+
40
+ def review?
41
+ type == 'review_page'
42
+ end
43
+
44
+ def complete!
45
+ return mark_as_submit if fill_in?
46
+ return mark_as_sign_off if review?
47
+ end
48
+
49
+ private
50
+
51
+ def mark_as_submit
52
+ doc['submitted'] = true
53
+ end
54
+
55
+ def mark_as_sign_off
56
+ doc['sign_off'] = true
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,69 @@
1
+ module Ecoportal
2
+ module API
3
+ class V2
4
+ class Pages
5
+ class PageStage
6
+ class Tasks < Common::Content::CollectionModel
7
+ class_resolver :task_class, "Ecoportal::API::V2::Pages::PageStage::Task"
8
+
9
+ self.klass = :task_class
10
+
11
+ def ooze
12
+ self._parent.ooze
13
+ end
14
+
15
+ # @return [Ecoportal::API::V2::Pages::PageStage::Task]
16
+ def get_by_id(id)
17
+ self.find do |task|
18
+ task.id == id
19
+ end
20
+ end
21
+
22
+ # @return [Array<Ecoportal::API::V2::Pages::PageStage::Task>]
23
+ def get_by_type(type)
24
+ select do |task|
25
+ task.type == type
26
+ end
27
+ end
28
+
29
+ # @return [Array<Ecoportal::API::V2::Pages::PageStage::Task>]
30
+ def open(&block)
31
+ reject(&:complete).tap do |res|
32
+ res.each(&block)
33
+ end
34
+ end
35
+
36
+ # @return [Array<Ecoportal::API::V2::Pages::PageStage::Task>]
37
+ def complete(&block)
38
+ select(&:complete).tap do |res|
39
+ res.each(&block)
40
+ end
41
+ end
42
+
43
+ # @return [Array<Ecoportal::API::V2::Pages::PageStage::Task>]
44
+ def fill_in(active: :unused, &block)
45
+ target = self
46
+ target = open if active == true
47
+ target = complete if active == false
48
+
49
+ target.select(&:fill_in?).tap do |res|
50
+ res.each(&block)
51
+ end
52
+ end
53
+
54
+ # @return [Array<Ecoportal::API::V2::Pages::PageStage::Task>]
55
+ def review(active: :unused, &block)
56
+ target = self
57
+ target = open if active == true
58
+ target = complete if active == false
59
+
60
+ target.select(&:review?).tap do |res|
61
+ res.each(&block)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -8,6 +8,9 @@ module Ecoportal
8
8
  passthrough :task_priority, :state, :status
9
9
  passthrough :votes_enabled, :upvotes, :downvotes
10
10
 
11
+ class_resolver :tasks_class, "Ecoportal::API::V2::Pages::PageStage::Tasks"
12
+ embeds_many :tasks, enum_class: :tasks_class
13
+
11
14
  #embeds_many :permits, klass: "Ecoportal::API::V2::Page::Permit"
12
15
  passarray :force_errors, :subtags, order_matters: false
13
16
 
@@ -18,11 +21,10 @@ module Ecoportal
18
21
 
19
22
  # @return [String] unique id
20
23
  def uid
21
- if mould_counter? && counter = mould_counter
22
- counter.render
23
- else
24
- id
25
- end
24
+ return id unless mould_counter?
25
+ return id unless (counter = mould_counter)
26
+
27
+ counter.render
26
28
  end
27
29
 
28
30
  # @return [String] `id` of the stage we got the data of.
@@ -32,46 +34,52 @@ module Ecoportal
32
34
 
33
35
  # @return [Ecoportal::API::V2::Page::Stage]
34
36
  def current_stage
35
- if stage_id = current_stage_id
36
- stages[stage_id]
37
- end
37
+ return false unless (stage_id = current_stage_id)
38
+
39
+ stages[stage_id]
38
40
  end
39
41
 
40
42
  # @return [String] with feedback, if for this page instance, there are any of:
41
43
  # 1. orphaned components (fields not belonging to any section)
42
44
  # 2. orphaned sections (sections not belonging to any stage)
43
- def validate
45
+ def validate # rubocop:disable Metrics/AbcSize
44
46
  msg = super
45
47
  msg = "" unless msg.is_a?(String)
46
48
 
47
49
  orphans = components.unattached.select {|comp| comp.global_binding.to_s.strip.empty?}
48
- if orphans.length > 0
49
- msg += "There are fields not attached to any sections:\n • "
50
- msg += orphans.map do |fld|
51
- fld.label
52
- end.join("\n • ") + "\n"
50
+ if orphans.length.positive?
51
+ msg << "There are fields not attached to any sections:"
52
+ msg << "\n • "
53
+ msg << orphans.map(&:label).join("\n • ")
54
+ msg << "\n"
53
55
  end
54
56
 
55
- if (orphans = sections.unattached).length > 0
56
- msg += "There are sections not attached to any stage:\n • "
57
- msg += orphans.map do |sec|
57
+ if (orphans = sections.unattached).length.positive?
58
+ msg << "There are sections not attached to any stage:"
59
+ msg << "\n • "
60
+ msg << orphans.map do |sec|
58
61
  "'#{sec.heading}' (#{sec.id})"
59
- end.join("\n • ") + "\n"
62
+ end.join("\n • ")
63
+ msg << "\n"
60
64
  end
65
+
61
66
  msg.empty?? true : msg
62
67
  end
63
68
 
64
69
  def mark_as_submit
65
- doc["submitted"] = true
66
- doc["type"] = "complete_page"
70
+ tasks.fill_in(active: true).each(&:complete!)
67
71
  end
72
+ alias_method :submit!, :mark_as_submit
68
73
 
69
74
  def mark_as_sign_off
70
- doc["sign_off"] = true
71
- doc["type"] = "review_page"
75
+ tasks.review(active: true).each(&:complete!)
72
76
  end
77
+ alias_method :sign_off!, :mark_as_sign_off
73
78
  end
74
79
  end
75
80
  end
76
81
  end
77
82
  end
83
+
84
+ require 'ecoportal/api/v2/pages/page_stage/task'
85
+ require 'ecoportal/api/v2/pages/page_stage/tasks'
@@ -40,12 +40,11 @@ module Ecoportal
40
40
  # @param stage_id [String] the `id` of the target **stage**.
41
41
  # @return [Response] an object with the api response.
42
42
  def update(doc, id: nil, stage_id:)
43
- body = get_body(doc)
44
- id = id || get_id(doc)
45
- path = "/pages/#{CGI.escape(id)}/stages/#{CGI.escape(stage_id)}/"
43
+ body = get_body(doc)
44
+ id ||= get_id(doc)
45
+ path = "/pages/#{CGI.escape(id)}/stages/#{CGI.escape(stage_id)}/"
46
46
  client.patch(path, data: body)
47
47
  end
48
-
49
48
  end
50
49
  end
51
50
  end
@@ -1,16 +1,17 @@
1
1
  module Ecoportal
2
2
  module API
3
3
  class V2
4
- # @attr_reader client [Common::Client] a `Common::Client` object that holds the configuration of the api connection.
4
+ # @attr_reader client [Common::Client] a `Common::Client` object that
5
+ # holds the configuration of the api connection.
5
6
  class Pages
6
- STAGE_REX = /stages\/(?<sid>.*)/
7
+ STAGE_REX = /stages\/(?<sid>.*)/.freeze
7
8
  extend Common::BaseClass
8
9
  include Common::Content::DocHelpers
9
10
 
10
11
  class_resolver :stages_class, "Ecoportal::API::V2::Pages::Stages"
11
- class_resolver :page_class, "Ecoportal::API::V2::Page"
12
- class_resolver :page_stage_class, "Ecoportal::API::V2::Pages::PageStage"
13
- class_resolver :create_page_response_class, "Ecoportal::API::V2::Pages::PageCreateResponse"
12
+ class_resolver :page_class, "Ecoportal::API::V2::Page"
13
+ class_resolver :page_stage_class, "Ecoportal::API::V2::Pages::PageStage"
14
+ class_resolver :create_page_response_class, "Ecoportal::API::V2::Pages::PageCreateResponse"
14
15
 
15
16
  attr_reader :client
16
17
 
@@ -35,16 +36,19 @@ module Ecoportal
35
36
  # @return [Ecoportal::API::V2::Page, Ecoportal::API::V2::Pages::PageStage] the target page.
36
37
  def get(id, stage_id: nil)
37
38
  return stages.get(id: id, stage_id: stage_id) if stage_id
39
+
38
40
  id = get_id(id)
39
41
  response = client.get("/pages/#{CGI.escape(id)}")
40
42
  wrapped = Common::Content::WrappedResponse.new(response, page_class)
41
43
 
42
44
  return wrapped.result if wrapped.success?
43
- if (response.status == 302) && (url = response.body["data"])
44
- if stage_id = url_to_stage_id(url)
45
- return stages.get(id: id, stage_id: stage_id)
46
- end
47
- end
45
+
46
+ url = nil
47
+ url = response.body["data"] if response.status == 302
48
+ stage_id = url_to_stage_id(url) unless url.nil?
49
+
50
+ return stages.get(id: id, stage_id: stage_id) if stage_id
51
+
48
52
  raise "Could not get page #{id} - Error #{response.status}: #{response.body}"
49
53
  end
50
54
 
@@ -53,10 +57,17 @@ module Ecoportal
53
57
  # @param doc [Hash, Page] data that at least contains an `id` (internal or external) of the target page.
54
58
  # @return [Ecoportal::API::Common::Response] an object with the api response.
55
59
  def update(doc)
60
+ if doc.is_a?(Ecoportal::API::V2::Pages::PageStage)
61
+ stage_id = doc.current_stage_id
62
+ return stages.update(doc, stage_id: stage_id)
63
+ end
64
+
56
65
  body = get_body(doc) # , level: "page"
57
66
  # Launch only if there are changes
58
67
  raise "Missing page object" unless body && body["page"]
59
- id = get_id(doc)
68
+
69
+ id = get_id(doc)
70
+
60
71
  client.patch("/pages/#{CGI.escape(id)}", data: body)
61
72
  end
62
73
 
@@ -81,9 +92,9 @@ module Ecoportal
81
92
  body = get_body(doc).tap do |hash|
82
93
  unless hash["page"]
83
94
  hash["page"] = {
84
- "id" => "111111111111111111111111",
95
+ "id" => "111111111111111111111111",
85
96
  "operation" => "changed",
86
- "data" => {"patch_ver" => 0}
97
+ "data" => {"patch_ver" => 0}
87
98
  }
88
99
  end
89
100
  end
@@ -98,7 +109,6 @@ module Ecoportal
98
109
  def url_to_stage_id(url)
99
110
  (matches = url.match(STAGE_REX)) && matches[:sid]
100
111
  end
101
-
102
112
  end
103
113
  end
104
114
  end
@@ -1,9 +1,9 @@
1
1
  module Ecoportal
2
2
  module API
3
3
  class V2
4
- # @attr_reader client [Common::Client] a `Common::Client` object that holds the configuration of the api connection.
4
+ # @attr_reader client [Common::Client] a `Common::Client` object that
5
+ # holds the configuration of the api connection.
5
6
  class People < API::Internal::People
6
-
7
7
  def batch
8
8
  unavailable_method!(__method__)
9
9
  end
@@ -24,7 +24,6 @@ module Ecoportal
24
24
  def unavailable_method!(str)
25
25
  raise "Unavailable method '#{str}' for api '#{VERSION}'"
26
26
  end
27
-
28
27
  end
29
28
  end
30
29
  end
@@ -2,26 +2,29 @@ require 'base64'
2
2
  module Ecoportal
3
3
  module API
4
4
  class V2
5
- # @attr_reader client [Common::Client] a `Common::Client` object that holds the configuration of the api connection.
5
+ # @attr_reader client [Common::Client] a `Common::Client` object that
6
+ # holds the configuration of the api connection.
6
7
  class Registers
7
8
  extend Common::BaseClass
8
9
  include Enumerable
9
10
  include Common::Content::DocHelpers
10
11
 
11
- class_resolver :register_class, "Ecoportal::API::V2::Registers::Register"
12
+ class_resolver :register_class, "Ecoportal::API::V2::Registers::Register"
12
13
  class_resolver :register_search_result_class, "Ecoportal::API::V2::Registers::PageResult"
13
14
  class_resolver :register_search_results, "Ecoportal::API::V2::Registers::SearchResults"
14
15
 
15
16
  attr_reader :client
16
17
 
17
- # @param client [Common::Client] a `Common::Client` object that holds the configuration of the api connection.
18
+ # @param client [Common::Client] a `Common::Client` object that
19
+ # holds the configuration of the api connection.
18
20
  # @return [Registers] an instance object ready to make registers api requests.
19
21
  def initialize(client)
20
22
  @client = client
21
23
  end
22
24
 
23
25
  def each(params: {}, &block)
24
- return to_enum(:each) unless block
26
+ return to_enum(:each, params: params) unless block
27
+
25
28
  get.each(&block)
26
29
  end
27
30
 
@@ -29,7 +32,11 @@ module Ecoportal
29
32
  # @return [Enumerable<Register>] an `Enumerable` with all schemas already wrapped as `Register` objects.
30
33
  def get
31
34
  response = client.get("/templates")
32
- Common::Content::WrappedResponse.new(response, register_class, key: "registers")
35
+ Common::Content::WrappedResponse.new(
36
+ response,
37
+ register_class,
38
+ key: "registers"
39
+ )
33
40
  end
34
41
 
35
42
  # Gets all the oozes/pages of `register_id` matching the `options`
@@ -41,18 +48,21 @@ module Ecoportal
41
48
  # @yield [result] something to do with search page-result.
42
49
  # @yieldparam result [Ecoportal::V2::Registers::PageResult] a page result.
43
50
  # @return [Ecoportal::API::V2::Registers, Ecoportal::API::V2::Registers::SearchResults]
44
- def search(register_id, options = {})
51
+ def search(register_id, options = {}) # rubocop:disable Metrics/AbcSize
45
52
  only_first = options.delete(:only_first)
46
53
  options = build_options(options)
47
54
 
48
55
  if only_first
49
56
  response = client.get("/registers/#{register_id}/search", params: options)
50
57
  raise "Request failed - Status #{response.status}: #{response.body}" unless response.success?
58
+
51
59
  return register_search_results.new(response.body["data"])
52
60
  end
53
61
 
54
62
  cursor_id = nil
55
- results = 0; total = nil
63
+ results = 0
64
+ total = nil
65
+
56
66
  loop do
57
67
  options.update(cursor_id: cursor_id) if cursor_id
58
68
  response = client.get("/registers/#{register_id}/search", params: options)
@@ -61,12 +71,14 @@ module Ecoportal
61
71
  data = response.body["data"]
62
72
  total ||= data["total"]
63
73
  if total != data["total"]
64
- msg = "Change of total in search results. Probably due to changes that affect the filter (register: #{register_id}):"
74
+ msg = "Change of total in search results. "
75
+ msg << "Probably due to changes that affect the filter"
76
+ msg << "(register: #{register_id}):"
65
77
  print_search_status(msg, total, results, cursor_id, data, options)
66
78
  #total = data["total"]
67
79
  end
68
80
 
69
- unless total == 0
81
+ unless total&.zero?
70
82
  results += data["results"].length
71
83
  print_progress(results, total)
72
84
  end
@@ -77,12 +89,15 @@ module Ecoportal
77
89
  end
78
90
 
79
91
  break if total <= results
92
+
80
93
  unless data["cursor_id"]
81
- msg = "Possible error... finishing search for lack of cursor_id in response:"
94
+ msg = "Possible error... finishing search for lack of cursor_id in response:"
82
95
  print_search_status(msg, total, results, cursor_id, data, options)
83
96
  end
97
+
84
98
  break unless (cursor_id = data["cursor_id"])
85
99
  end
100
+
86
101
  self
87
102
  end
88
103
 
@@ -96,14 +111,14 @@ module Ecoportal
96
111
  options.each do |key, value|
97
112
  if key == :filters && value.any?
98
113
  ret[key] = {filters: value}.to_json
99
- else
100
- ret[key] = value if key
114
+ elsif key
115
+ ret[key] = value
101
116
  end
102
117
  end
103
118
  end
104
119
  end
105
120
 
106
- def print_search_status(msg, total, results, cursor_id, data, options)
121
+ def print_search_status(msg, total, results, cursor_id, data, options) # rubocop:disable Metrics/ParameterLists
107
122
  msg += "\n"
108
123
  msg += " • Original total: #{total}\n"
109
124
  msg += " • Current total: #{data&.dig("total")}\n"
@@ -0,0 +1,27 @@
1
+ module Ecoportal
2
+ module API
3
+ class V2
4
+ class S3
5
+ class Data < Common::Content::DoubleModel
6
+ passthrough :s3_endpoint, :AWSAccessKeyId
7
+ passthrough :policy, :signature
8
+ passthrough :user_tmpdir
9
+
10
+ def x_amz_server_side_encryption
11
+ doc['x-amz-server-side-encryption']
12
+ end
13
+
14
+ def [](key)
15
+ doc[key.to_s]
16
+ end
17
+
18
+ def user_id
19
+ return unless user_tmpdir
20
+
21
+ user_tmpdir.split('uploads/').last
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end