Files
sure/app/models/mint_import.rb
Carlos Adames b56dbdb9eb Feat: /import endpoint & drag-n-drop imports (#501)
* Implement API v1 Imports controller

- Add Api::V1::ImportsController with index, show, and create actions
- Add Jbuilder views for index and show
- Add integration tests
- Implement row generation logic in create action
- Update routes

* Validate import account belongs to family

- Add validation to Import model to ensure account belongs to the same family
- Add regression test case in Api::V1::ImportsControllerTest

* updating docs to be more detailed

* Rescue StandardError instead of bare rescue in ImportsController

* Optimize Imports API and fix documentation

- Implement rows_count counter cache for Imports
- Preload rows in Api::V1::ImportsController#show
- Update documentation to show correct OAuth scopes

* Fix formatting in ImportsControllerTest

* Permit all import parameters and fix unknown attribute error

* Restore API routes for auth, chats, and messages

* removing pr summary

* Fix trailing whitespace and configured? test failure

- Update Import#configured? to use rows_count for performance and consistency
- Mock rows_count in TransactionImportTest
- Fix trailing whitespace in migration

* Harden security and fix mass assignment in ImportsController

- Handle type and account_id explicitly in create action
- Rename import_params to import_config_params for clarity
- Validate type against Import::TYPES

* Fix MintImport rows_count update and migration whitespace

- Update MintImport#generate_rows_from_csv to update rows_count counter cache
- Fix trailing whitespace and final newline in AddRowsCountToImports migration

* Implement full-screen Drag and Drop CSV import on Transactions page

- Add DragAndDropImport Stimulus controller listening on document
- Add full-screen overlay with icon and text to Transactions index
- Update ImportsController to handle direct file uploads via create action
- Add system test for drag and drop functionality

* Implement Drag and Drop CSV upload on Import Upload page

- Add drag-and-drop-import controller to import/uploads/show
- Add full-screen overlay to import/uploads/show
- Annotate upload form and input with drag-and-drop targets
- Add PR_SUMMARY.md

* removing pr summary

* Add file validation to ImportsController

- Validate file size (max 10MB) and MIME type in create action
- Prevent memory exhaustion and invalid file processing
- Defined MAX_CSV_SIZE and ALLOWED_MIME_TYPES in Import model

* Refactor dragLeave logic with counter pattern to prevent flickering

* Extract shared drag-and-drop overlay partial

- Create app/views/imports/_drag_drop_overlay.html.erb
- Update transactions/index and import/uploads/show to use the partial
- Reduce code duplication in views

* Update Brakeman and harden ImportsController security

- Update brakeman to 7.1.2
- Explicitly handle type assignment in ImportsController#create to avoid mass assignment
- Remove :type from permitted import parameters

* Fix trailing whitespace in DragAndDropImportTest

* Don't commit LLM comments as file

* FIX add api validation

---------

Co-authored-by: Carlos Adames <cj@Carloss-MacBook-Air.local>
Co-authored-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: sokie <sokysrm@gmail.com>
2026-01-10 16:39:18 +01:00

100 lines
2.8 KiB
Ruby

class MintImport < Import
after_create :set_mappings
def generate_rows_from_csv
rows.destroy_all
mapped_rows = csv_rows.map do |row|
{
account: row[account_col_label].to_s,
date: row[date_col_label].to_s,
amount: signed_csv_amount(row).to_s,
currency: (row[currency_col_label] || default_currency).to_s,
name: (row[name_col_label] || default_row_name).to_s,
category: row[category_col_label].to_s,
tags: row[tags_col_label].to_s,
notes: row[notes_col_label].to_s
}
end
rows.insert_all!(mapped_rows)
update_column(:rows_count, rows.count)
end
def import!
transaction do
mappings.each(&:create_mappable!)
rows.each do |row|
account = mappings.accounts.mappable_for(row.account)
category = mappings.categories.mappable_for(row.category)
tags = row.tags_list.map { |tag| mappings.tags.mappable_for(tag) }.compact
# Use account's currency when no currency column was mapped in CSV, with family currency as fallback
effective_currency = currency_col_label.present? ? row.currency : (account.currency.presence || family.currency)
entry = account.entries.build \
date: row.date_iso,
amount: row.signed_amount,
name: row.name,
currency: effective_currency,
notes: row.notes,
entryable: Transaction.new(category: category, tags: tags),
import: self
entry.save!
end
end
end
def mapping_steps
[ Import::CategoryMapping, Import::TagMapping, Import::AccountMapping ]
end
def required_column_keys
%i[date amount]
end
def column_keys
%i[date amount name currency category tags account notes]
end
def csv_template
template = <<-CSV
Date,Amount,Account Name,Description,Category,Labels,Currency,Notes,Transaction Type
01/01/2024,-8.55,Checking,Starbucks,Food & Drink,Coffee|Breakfast,USD,Morning coffee,debit
04/15/2024,2000,Savings,Paycheck,Income,,USD,Bi-weekly salary,credit
CSV
CSV.parse(template, headers: true)
end
def signed_csv_amount(csv_row)
amount = csv_row[amount_col_label]
type = csv_row["Transaction Type"]
if type == "credit"
amount.to_d
else
amount.to_d * -1
end
end
private
def set_mappings
self.signage_convention = "inflows_positive"
self.date_col_label = "Date"
self.date_format = "%m/%d/%Y"
self.name_col_label = "Description"
self.amount_col_label = "Amount"
self.currency_col_label = "Currency"
self.account_col_label = "Account Name"
self.category_col_label = "Category"
self.tags_col_label = "Labels"
self.notes_col_label = "Notes"
self.entity_type_col_label = "Transaction Type"
save!
end
end