data_porter 1.0.2 → 1.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ca6bfabfc9f831d71c60a1942516a5dccf95c85e3787f16a1217188c9feb3a0
4
- data.tar.gz: f703da9261612953fcacad2674e38bef3037b804191e7fb3087577846b096461
3
+ metadata.gz: 46a9ae914232272194aaa961ed2619a423e2fcbbe9b5dc4dbdb2762bbcdb7129
4
+ data.tar.gz: 4de6b7f13955136df74552b6788792b3f9e8ba0b201e8b0fa25000e28294e13b
5
5
  SHA512:
6
- metadata.gz: a7d8ad32cb5d80e027d9adfe2a089a84703c6e8e5b00901c0d057a4b2bb24cb2ffbe0f6edb53f61021219fd03384830517192657fbe4c693dd74b6977279b22a
7
- data.tar.gz: 6d96ecefa39d191cea801ff8e4075f5c9bb4e13979f7754041db33a6685701d98f4521230500a1d0a47a417ce141f1b08973b5cbfd65868b0c3920c99afb61f6
6
+ metadata.gz: 0af51f459c999859b1ef723787998134e96a371bfe25c4e99a086a38cd787e35f4c8b2e58331b5594a1baeb7bc0c8220d0ccd5a9c2738f0cbabc1cc69021a754
7
+ data.tar.gz: 735f5bc4d814f7e641fd8e2d9498487ddf75a5ce784f40439fbcfcdd7a995b131e243538dd261ea5d5b4494148a0035b83b7c5bac924de2f9279f2a019dd6af5
data/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.1.0] - 2026-02-08
9
+
10
+ ### Added
11
+
12
+ - **Scoped imports** -- `config.scope` lambda returns the owner object (any ActiveRecord model) for multi-tenant isolation; used for both storage and filtering; IDOR-safe
13
+ - **Scoped mapping templates** -- Templates are filtered and assigned by owner, consistent with import scope
14
+ - **`ScopeManagement` concern** -- Shared `resolve_owner` logic extracted for both controllers
15
+ - **Polymorphic user on `mapping_templates`** -- Migration template adds `user` reference for template ownership
16
+
17
+ ### Changed
18
+
19
+ - `build_import` uses `resolve_owner` instead of raw `current_user`
20
+ - 413 RSpec examples (up from 402), 0 failures
21
+
8
22
  ## [1.0.2] - 2026-02-07
9
23
 
10
24
  ### Changed
data/README.md CHANGED
@@ -31,7 +31,8 @@ Supports CSV, JSON, XLSX, and API sources with a declarative DSL for defining im
31
31
  - **Per-target source filtering** -- Each target declares its allowed sources, the UI filters accordingly
32
32
  - **Import deletion & auto-purge** -- Delete imports from the UI, or schedule `rake data_porter:purge` for automatic cleanup
33
33
  - **Reject rows export** -- Download a CSV of failed/errored records with error messages after import
34
- - **Security validations** -- File size limit, MIME type check, strong parameter whitelisting
34
+ - **Scoped imports** -- `config.scope` for multi-tenant isolation; each user only sees their own imports
35
+ - **Security validations** -- File size limit, MIME type check, strong parameter whitelisting, IDOR protection via scope
35
36
  - **Safety guards** -- Max records limit (`config.max_records`), configurable transaction mode (`:per_record` or `:all`)
36
37
  - **Declarative Target DSL** -- One class per import type, zero boilerplate ([docs](docs/TARGETS.md))
37
38
 
@@ -141,7 +142,7 @@ pending -> parsing -> previewing -> importing -> completed
141
142
  git clone https://github.com/SerylLns/data_porter.git
142
143
  cd data_porter
143
144
  bin/setup
144
- bundle exec rspec # 391 specs
145
+ bundle exec rspec # 405 specs
145
146
  bundle exec rubocop # 0 offenses
146
147
  ```
147
148
 
@@ -19,7 +19,8 @@ module DataPorter
19
19
  def load_templates
20
20
  return [] unless defined?(DataPorter::MappingTemplate)
21
21
 
22
- DataPorter::MappingTemplate.for_target(@import.target_key)
22
+ scope = scoped_template_base
23
+ scope.for_target(@import.target_key)
23
24
  end
24
25
 
25
26
  def save_column_mapping
@@ -31,10 +32,19 @@ module DataPorter
31
32
  return unless params[:save_template] == "1"
32
33
  return unless defined?(DataPorter::MappingTemplate)
33
34
 
34
- DataPorter::MappingTemplate.find_or_initialize_by(
35
+ template = scoped_template_base.find_or_initialize_by(
35
36
  target_key: @import.target_key,
36
37
  name: params[:template_name].presence || "Default"
37
- ).update!(mapping: permitted_column_mapping)
38
+ )
39
+ template.user ||= resolve_owner
40
+ template.update!(mapping: permitted_column_mapping)
41
+ end
42
+
43
+ def scoped_template_base
44
+ owner = resolve_owner
45
+ return DataPorter::MappingTemplate unless owner
46
+
47
+ DataPorter::MappingTemplate.where(user: owner)
38
48
  end
39
49
 
40
50
  def permitted_column_mapping
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DataPorter
4
+ module Concerns
5
+ module ScopeManagement
6
+ private
7
+
8
+ def resolve_owner
9
+ return unless respond_to?(:current_user, true)
10
+ return unless current_user
11
+
12
+ scope = DataPorter.configuration.scope
13
+ scope ? scope.call(current_user) : current_user
14
+ end
15
+ end
16
+ end
17
+ end
@@ -5,6 +5,7 @@ module DataPorter
5
5
  include Concerns::ImportValidation
6
6
  include Concerns::MappingManagement
7
7
  include Concerns::RecordPagination
8
+ include Concerns::ScopeManagement
8
9
 
9
10
  layout "data_porter/application"
10
11
 
@@ -12,7 +13,7 @@ module DataPorter
12
13
  before_action :load_targets, only: %i[index new create]
13
14
 
14
15
  def index
15
- @imports = DataPorter::DataImport.order(created_at: :desc)
16
+ @imports = scoped_imports.order(created_at: :desc)
16
17
  end
17
18
 
18
19
  def new
@@ -88,7 +89,14 @@ module DataPorter
88
89
  private
89
90
 
90
91
  def set_import
91
- @import = DataPorter::DataImport.find(params[:id])
92
+ @import = scoped_imports.find(params[:id])
93
+ end
94
+
95
+ def scoped_imports
96
+ owner = resolve_owner
97
+ return DataPorter::DataImport.all unless owner
98
+
99
+ DataPorter::DataImport.where(user: owner)
92
100
  end
93
101
 
94
102
  def load_targets
@@ -97,7 +105,7 @@ module DataPorter
97
105
 
98
106
  def build_import
99
107
  @import = DataPorter::DataImport.new(import_params)
100
- @import.user = current_user if respond_to?(:current_user, true)
108
+ @import.user = resolve_owner
101
109
  @import.status = :pending
102
110
  end
103
111
 
@@ -2,12 +2,14 @@
2
2
 
3
3
  module DataPorter
4
4
  class MappingTemplatesController < DataPorter.configuration.parent_controller.constantize
5
+ include Concerns::ScopeManagement
6
+
5
7
  layout "data_porter/application"
6
8
 
7
9
  before_action :set_template, only: %i[edit update destroy]
8
10
 
9
11
  def index
10
- @templates = MappingTemplate.order(:target_key, :name)
12
+ @templates = scoped_templates.order(:target_key, :name)
11
13
  @grouped = @templates.group_by(&:target_key)
12
14
  end
13
15
 
@@ -18,6 +20,7 @@ module DataPorter
18
20
 
19
21
  def create
20
22
  @template = MappingTemplate.new(template_params)
23
+ @template.user = resolve_owner
21
24
 
22
25
  if @template.save
23
26
  redirect_to mapping_templates_path
@@ -48,7 +51,14 @@ module DataPorter
48
51
  private
49
52
 
50
53
  def set_template
51
- @template = MappingTemplate.find(params[:id])
54
+ @template = scoped_templates.find(params[:id])
55
+ end
56
+
57
+ def scoped_templates
58
+ owner = resolve_owner
59
+ return MappingTemplate.all unless owner
60
+
61
+ MappingTemplate.where(user: owner)
52
62
  end
53
63
 
54
64
  def template_params
@@ -4,6 +4,8 @@ module DataPorter
4
4
  class MappingTemplate < ActiveRecord::Base
5
5
  self.table_name = "data_porter_mapping_templates"
6
6
 
7
+ belongs_to :user, polymorphic: true, optional: true
8
+
7
9
  attribute :mapping, :json, default: -> { {} }
8
10
 
9
11
  validates :target_key, presence: true
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DataPorter
4
- VERSION = "1.0.2"
4
+ VERSION = "1.1.0"
5
5
  end
@@ -7,6 +7,8 @@ class CreateDataPorterMappingTemplates < ActiveRecord::Migration[<%= ActiveRecor
7
7
  t.string :name, null: false
8
8
  t.jsonb :mapping, null: false, default: {}
9
9
 
10
+ t.references :user, polymorphic: true
11
+
10
12
  t.timestamps
11
13
  end
12
14
 
@@ -26,6 +26,12 @@ DataPorter.configure do |config|
26
26
  # Enabled source types.
27
27
  # config.enabled_sources = %i[csv json xlsx api]
28
28
 
29
+ # Scope imports per owner (multi-tenant isolation).
30
+ # The lambda receives current_user and returns the owner object.
31
+ # Works with any model: User, Member, Hotel, Organization...
32
+ # config.scope = ->(user) { user }
33
+ # config.scope = ->(user) { user.hotel }
34
+
29
35
  # Auto-purge completed/failed imports older than this duration.
30
36
  # Set to nil to disable auto-purge. Run `rake data_porter:purge` manually or via cron.
31
37
  # config.purge_after = 60.days
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: data_porter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Seryl Lounis
@@ -122,6 +122,7 @@ files:
122
122
  - app/controllers/data_porter/concerns/import_validation.rb
123
123
  - app/controllers/data_porter/concerns/mapping_management.rb
124
124
  - app/controllers/data_porter/concerns/record_pagination.rb
125
+ - app/controllers/data_porter/concerns/scope_management.rb
125
126
  - app/controllers/data_porter/imports_controller.rb
126
127
  - app/controllers/data_porter/mapping_templates_controller.rb
127
128
  - app/jobs/data_porter/dry_run_job.rb