data_migration_for_rails 0.1.1

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 (94) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +17 -0
  3. data/README.md +196 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/manifest.js +2 -0
  6. data/app/assets/stylesheets/application.css +15 -0
  7. data/app/channels/application_cable/channel.rb +6 -0
  8. data/app/channels/application_cable/connection.rb +6 -0
  9. data/app/controllers/concerns/data_migration/pundit_authorization.rb +12 -0
  10. data/app/controllers/data_migration/application_controller.rb +63 -0
  11. data/app/controllers/data_migration/exports_controller.rb +68 -0
  12. data/app/controllers/data_migration/imports_controller.rb +78 -0
  13. data/app/controllers/data_migration/migration_executions_controller.rb +75 -0
  14. data/app/controllers/data_migration/migration_plans_controller.rb +103 -0
  15. data/app/controllers/data_migration/migration_steps_controller.rb +164 -0
  16. data/app/controllers/data_migration/users_controller.rb +71 -0
  17. data/app/controllers/users/sessions_controller.rb +30 -0
  18. data/app/helpers/data_migration/application_helper.rb +24 -0
  19. data/app/jobs/application_job.rb +9 -0
  20. data/app/jobs/export_job.rb +27 -0
  21. data/app/jobs/import_job.rb +28 -0
  22. data/app/mailers/application_mailer.rb +6 -0
  23. data/app/models/application_record.rb +5 -0
  24. data/app/models/data_migration_user.rb +43 -0
  25. data/app/models/migration_execution.rb +93 -0
  26. data/app/models/migration_plan.rb +23 -0
  27. data/app/models/migration_record.rb +60 -0
  28. data/app/models/migration_step.rb +150 -0
  29. data/app/policies/application_policy.rb +53 -0
  30. data/app/policies/data_migration/user_policy.rb +27 -0
  31. data/app/policies/data_migration_user_policy.rb +37 -0
  32. data/app/policies/migration_execution_policy.rb +33 -0
  33. data/app/policies/migration_plan_policy.rb +41 -0
  34. data/app/policies/migration_step_policy.rb +29 -0
  35. data/app/services/data_migration/model_registry.rb +95 -0
  36. data/app/services/exports/generator_service.rb +444 -0
  37. data/app/services/imports/processor_service.rb +457 -0
  38. data/app/services/migration_plans/export_config_service.rb +41 -0
  39. data/app/services/migration_plans/import_config_service.rb +158 -0
  40. data/app/views/data_migration/devise/registrations/edit.html.erb +41 -0
  41. data/app/views/data_migration/devise/sessions/new.html.erb +35 -0
  42. data/app/views/data_migration/devise/shared/_error_messages.html.erb +13 -0
  43. data/app/views/data_migration/devise/shared/_links.html.erb +21 -0
  44. data/app/views/data_migration/exports/new.html.erb +85 -0
  45. data/app/views/data_migration/imports/new.html.erb +70 -0
  46. data/app/views/data_migration/migration_executions/index.html.erb +78 -0
  47. data/app/views/data_migration/migration_executions/show.html.erb +338 -0
  48. data/app/views/data_migration/migration_plans/_form.html.erb +28 -0
  49. data/app/views/data_migration/migration_plans/edit.html.erb +12 -0
  50. data/app/views/data_migration/migration_plans/index.html.erb +118 -0
  51. data/app/views/data_migration/migration_plans/new.html.erb +9 -0
  52. data/app/views/data_migration/migration_plans/show.html.erb +105 -0
  53. data/app/views/data_migration/migration_steps/_form.html.erb +473 -0
  54. data/app/views/data_migration/migration_steps/edit.html.erb +12 -0
  55. data/app/views/data_migration/migration_steps/new.html.erb +9 -0
  56. data/app/views/data_migration/users/_form.html.erb +49 -0
  57. data/app/views/data_migration/users/edit.html.erb +2 -0
  58. data/app/views/data_migration/users/index.html.erb +41 -0
  59. data/app/views/data_migration/users/new.html.erb +2 -0
  60. data/app/views/data_migration/users/show.html.erb +133 -0
  61. data/app/views/layouts/_navbar.html.erb +38 -0
  62. data/app/views/layouts/data_migration.html.erb +37 -0
  63. data/app/views/layouts/mailer.html.erb +13 -0
  64. data/app/views/layouts/mailer.text.erb +1 -0
  65. data/app/views/users/registrations/edit.html.erb +41 -0
  66. data/app/views/users/sessions/new.html.erb +35 -0
  67. data/app/views/users/shared/_error_messages.html.erb +13 -0
  68. data/app/views/users/shared/_links.html.erb +21 -0
  69. data/config/initializers/assets.rb +14 -0
  70. data/config/initializers/content_security_policy.rb +27 -0
  71. data/config/initializers/devise.rb +313 -0
  72. data/config/initializers/filter_parameter_logging.rb +10 -0
  73. data/config/initializers/inflections.rb +18 -0
  74. data/config/initializers/permissions_policy.rb +15 -0
  75. data/config/initializers/warden.rb +14 -0
  76. data/config/locales/devise.en.yml +65 -0
  77. data/config/locales/en.yml +31 -0
  78. data/config/routes.rb +62 -0
  79. data/db/migrate/20251102121659_create_migration_plans.rb +13 -0
  80. data/db/migrate/20251102122012_create_migration_steps.rb +24 -0
  81. data/db/migrate/20251105215702_create_migration_executions.rb +23 -0
  82. data/db/migrate/20251105215853_create_migration_records.rb +16 -0
  83. data/db/migrate/20251115154000_remove_unused_attributes.rb +17 -0
  84. data/db/migrate/20251116120000_add_filter_params_to_migration_executions.rb +7 -0
  85. data/db/migrate/20251118140000_create_data_migration_users.rb +27 -0
  86. data/db/migrate/20251118200641_add_user_foreign_keys.rb +15 -0
  87. data/db/migrate/20251124140000_add_attachment_export_mode_to_migration_steps.rb +9 -0
  88. data/db/schema.rb +102 -0
  89. data/db/seeds.rb +19 -0
  90. data/lib/data_migration/engine.rb +28 -0
  91. data/lib/data_migration/version.rb +5 -0
  92. data/lib/data_migration.rb +8 -0
  93. data/lib/tasks/data_migration_tasks.rake +40 -0
  94. metadata +279 -0
@@ -0,0 +1,28 @@
1
+ <%= form_with(model: migration_plan, local: true, class: "needs-validation") do |form| %>
2
+ <% if migration_plan.errors.any? %>
3
+ <div class="alert alert-danger">
4
+ <h4><%= pluralize(migration_plan.errors.count, "error") %> prohibited this migration plan from being saved:</h4>
5
+ <ul class="mb-0">
6
+ <% migration_plan.errors.full_messages.each do |message| %>
7
+ <li><%= message %></li>
8
+ <% end %>
9
+ </ul>
10
+ </div>
11
+ <% end %>
12
+
13
+ <div class="mb-3">
14
+ <%= form.label :name, class: "form-label" %>
15
+ <%= form.text_field :name, class: "form-control", placeholder: "e.g., User Data Migration", required: true %>
16
+ <div class="form-text">A unique name to identify this migration plan, use enough identfiers to make it unique and recognizable.</div>
17
+ </div>
18
+
19
+ <div class="mb-3">
20
+ <%= form.label :description, class: "form-label" %>
21
+ <%= form.text_area :description, class: "form-control", rows: 4, placeholder: "Describe what this migration plan does..." %>
22
+ <div class="form-text">Optional description of what data this plan migrates.</div>
23
+ </div>
24
+
25
+ <div class="mb-3">
26
+ <%= form.submit migration_plan.new_record? ? "Create Migration Plan" : "Update Migration Plan", class: "btn btn-primary" %>
27
+ </div>
28
+ <% end %>
@@ -0,0 +1,12 @@
1
+ <div class="row justify-content-center">
2
+ <div class="col-md-8">
3
+ <h1 class="mb-4">Edit Migration Plan</h1>
4
+
5
+ <%= render 'form', migration_plan: @migration_plan %>
6
+
7
+ <div class="mt-3">
8
+ <%= link_to "View Plan", @migration_plan, class: "btn btn-primary" %>
9
+ <%= link_to "Cancel", migration_plans_path, class: "btn btn-secondary" %>
10
+ </div>
11
+ </div>
12
+ </div>
@@ -0,0 +1,118 @@
1
+ <div class="d-flex justify-content-between align-items-center mb-4">
2
+ <h1>Migration Plans</h1>
3
+ <div class="btn-group">
4
+ <% if policy(MigrationPlan).create? %>
5
+ <%= link_to "New Migration Plan", new_migration_plan_path, class: "btn btn-primary" %>
6
+ <% end %>
7
+ <% if policy(MigrationPlan.new).import_config? %>
8
+ <button type="button" class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#importConfigModal">
9
+ 📋 Import Config
10
+ </button>
11
+ <% end %>
12
+ </div>
13
+ </div>
14
+
15
+ <!-- Import Config Modal -->
16
+ <div class="modal fade" id="importConfigModal" tabindex="-1" aria-labelledby="importConfigModalLabel" aria-hidden="true">
17
+ <div class="modal-dialog">
18
+ <div class="modal-content">
19
+ <div class="modal-header">
20
+ <h5 class="modal-title" id="importConfigModalLabel">Import Plan Configuration</h5>
21
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
22
+ </div>
23
+ <%= form_with url: import_config_migration_plans_path, method: :post, multipart: true do |f| %>
24
+ <div class="modal-body">
25
+ <div class="mb-3">
26
+ <%= f.label :config_file, "Select Configuration File (.json)", class: "form-label" %>
27
+ <%= f.file_field :config_file, class: "form-control", accept: "application/json,.json", required: true %>
28
+ <div class="form-text">
29
+ Upload a JSON configuration file exported from another environment.
30
+ This will create or update a migration plan with all its steps.
31
+ </div>
32
+ </div>
33
+
34
+ <div class="alert alert-info small">
35
+ <strong>Note:</strong> If a plan with the same name exists, it will be updated with the imported configuration.
36
+ </div>
37
+ </div>
38
+ <div class="modal-footer">
39
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
40
+ <%= f.submit "Import Configuration", class: "btn btn-primary" %>
41
+ </div>
42
+ <% end %>
43
+ </div>
44
+ </div>
45
+ </div>
46
+
47
+ <% if @migration_plans.any? %>
48
+ <div class="table-responsive">
49
+ <table class="table table-hover">
50
+ <thead class="table-light">
51
+ <tr>
52
+ <th>Name</th>
53
+ <th>Description</th>
54
+ <th>Steps</th>
55
+ <th>Last Execution</th>
56
+ <th>Actions</th>
57
+ </tr>
58
+ </thead>
59
+ <tbody>
60
+ <% @migration_plans.each do |plan| %>
61
+ <tr>
62
+ <td>
63
+ <%= link_to plan.name, plan, class: "text-decoration-none" %>
64
+ </td>
65
+ <td><%= truncate(plan.description, length: 60) %></td>
66
+ <td>
67
+ <span class="badge bg-secondary"><%= plan.migration_steps.count %></span>
68
+ </td>
69
+ <td>
70
+ <% if plan.last_execution %>
71
+ <span class="badge bg-<%= execution_status_color(plan.last_execution.status) %>">
72
+ <%= plan.last_execution.status.titleize %>
73
+ </span>
74
+ <small class="text-muted d-block">
75
+ <%= time_ago_in_words(plan.last_execution.created_at) %> ago
76
+ </small>
77
+ <% else %>
78
+ <span class="text-muted">Never executed</span>
79
+ <% end %>
80
+ </td>
81
+ <td>
82
+ <div class="btn-group btn-group-sm" role="group">
83
+ <%= link_to "View", plan, class: "btn btn-outline-primary" %>
84
+
85
+ <% if policy(plan).execute? %>
86
+ <%= link_to "Export", migration_plan_new_export_path(plan),
87
+ class: "btn btn-outline-success" %>
88
+
89
+ <%= link_to "Import", migration_plan_new_import_path(plan),
90
+ class: "btn btn-outline-info" %>
91
+ <% end %>
92
+
93
+ <% if policy(plan).update? %>
94
+ <%= link_to "Edit", edit_migration_plan_path(plan), class: "btn btn-outline-warning" %>
95
+ <% end %>
96
+
97
+ <% if policy(plan).destroy? %>
98
+ <%= button_to "Delete", plan,
99
+ method: :delete,
100
+ class: "btn btn-outline-danger",
101
+ data: { confirm: "Are you sure? This will delete all steps." } %>
102
+ <% end %>
103
+ </div>
104
+ </td>
105
+ </tr>
106
+ <% end %>
107
+ </tbody>
108
+ </table>
109
+ </div>
110
+ <% else %>
111
+ <div class="alert alert-info">
112
+ <h4>No Migration Plans Yet</h4>
113
+ <p>Get started by creating your first migration plan.</p>
114
+ <% if policy(MigrationPlan).create? %>
115
+ <%= link_to "Create Migration Plan", new_migration_plan_path, class: "btn btn-primary" %>
116
+ <% end %>
117
+ </div>
118
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <div class="row justify-content-center">
2
+ <div class="col-md-8">
3
+ <h1 class="mb-4">New Migration Plan</h1>
4
+
5
+ <%= render 'form', migration_plan: @migration_plan %>
6
+
7
+ <%= link_to "Cancel", migration_plans_path, class: "btn btn-secondary mt-3" %>
8
+ </div>
9
+ </div>
@@ -0,0 +1,105 @@
1
+ <div class="d-flex justify-content-between align-items-center mb-4">
2
+ <div>
3
+ <h1><%= @migration_plan.name %></h1>
4
+ <p class="text-muted"><%= @migration_plan.description %></p>
5
+ </div>
6
+ <div class="btn-group">
7
+ <% if policy(@migration_plan).execute? %>
8
+ <%= link_to "Export Data", migration_plan_new_export_path(@migration_plan),
9
+ class: "btn btn-success" %>
10
+ <%= link_to "Import Data", migration_plan_new_import_path(@migration_plan),
11
+ class: "btn btn-info" %>
12
+ <% end %>
13
+ <% if policy(@migration_plan).export_config? %>
14
+ <%= link_to "📋 Export Config", export_config_migration_plan_path(@migration_plan),
15
+ class: "btn btn-outline-primary",
16
+ title: "Export plan configuration as JSON" %>
17
+ <% end %>
18
+ <% if policy(@migration_plan).update? %>
19
+ <%= link_to "Edit Plan", edit_migration_plan_path(@migration_plan), class: "btn btn-warning" %>
20
+ <% end %>
21
+ <%= link_to "Back", migration_plans_path, class: "btn btn-secondary" %>
22
+ </div>
23
+ </div>
24
+
25
+ <div class="row">
26
+ <div class="col-md-8">
27
+ <div class="card mb-4">
28
+ <div class="card-header d-flex justify-content-between align-items-center">
29
+ <h5 class="mb-0">Migration Steps</h5>
30
+ <% if policy(@migration_plan).update? %>
31
+ <%= link_to "Add Step", new_migration_plan_migration_step_path(@migration_plan), class: "btn btn-sm btn-primary" %>
32
+ <% end %>
33
+ </div>
34
+ <div class="card-body">
35
+ <% if @migration_plan.migration_steps.any? %>
36
+ <div class="list-group">
37
+ <% @migration_plan.migration_steps.order(:sequence).each do |step| %>
38
+ <div class="list-group-item">
39
+ <div class="d-flex justify-content-between align-items-start">
40
+ <div>
41
+ <h6 class="mb-1">
42
+ <span class="badge bg-primary me-2"><%= step.sequence %></span>
43
+ <%= step.source_model_name %>
44
+ </h6>
45
+ <% if step.filter_query.present? %>
46
+ <small class="text-muted">
47
+ <strong>Filter:</strong> <%= truncate(step.filter_query, length: 80) %>
48
+ </small>
49
+ <% end %>
50
+ </div>
51
+ <% if policy(step).update? %>
52
+ <div class="btn-group btn-group-sm">
53
+ <%= link_to "Edit", edit_migration_plan_migration_step_path(@migration_plan, step), class: "btn btn-sm btn-outline-warning" %>
54
+ <%= button_to "Delete", migration_plan_migration_step_path(@migration_plan, step),
55
+ method: :delete,
56
+ class: "btn btn-sm btn-outline-danger",
57
+ data: { confirm: "Delete this step?" } %>
58
+ </div>
59
+ <% end %>
60
+ </div>
61
+ </div>
62
+ <% end %>
63
+ </div>
64
+ <% else %>
65
+ <p class="text-muted text-center py-4">No steps configured yet.</p>
66
+ <% end %>
67
+ </div>
68
+ </div>
69
+ </div>
70
+
71
+ <div class="col-md-4">
72
+ <div class="card">
73
+ <div class="card-header">
74
+ <h5 class="mb-0">Recent Executions</h5>
75
+ </div>
76
+ <div class="card-body">
77
+ <% if @migration_plan.migration_executions.any? %>
78
+ <div class="list-group list-group-flush">
79
+ <% @migration_plan.migration_executions.recent.limit(5).each do |execution| %>
80
+ <%= link_to migration_execution_path(execution), class: "list-group-item list-group-item-action" do %>
81
+ <div class="d-flex justify-content-between align-items-center">
82
+ <div>
83
+ <%= execution_type_icon(execution.execution_type) %>
84
+ <strong><%= execution.execution_type.titleize %></strong>
85
+ <br>
86
+ <small class="text-muted">
87
+ <%= time_ago_in_words(execution.created_at) %> ago
88
+ by <%= execution.user.email %>
89
+ </small>
90
+ </div>
91
+ <span class="badge bg-<%= execution_status_color(execution.status) %>">
92
+ <%= execution.status.titleize %>
93
+ </span>
94
+ </div>
95
+ <% end %>
96
+ <% end %>
97
+ </div>
98
+ <%= link_to "View All Executions", migration_executions_path(plan_id: @migration_plan.id), class: "btn btn-sm btn-outline-primary w-100 mt-3" %>
99
+ <% else %>
100
+ <p class="text-muted text-center">No executions yet.</p>
101
+ <% end %>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ </div>