# Redmine Contacts duplicate-merge feasibility (API vs script) ## Scope Assess whether duplicate contact merge (web flow: `/contacts//duplicates?project_id=` with `duplicate_id=`) can be safely automated from `redmine_contacts.py` or shell tooling. ## Findings from local plugin source ### 1) Merge flow exists as a controller action, not a JSON API endpoint - Controller: `plugins/redmine_contacts/app/controllers/contacts_duplicates_controller.rb` - Routes: `plugins/redmine_contacts/config/routes.rb` - `match 'contacts/:contact_id/duplicates' => 'contacts_duplicates#index', :via => [:get, :post]` - `match 'contacts_duplicates/:action' => 'contacts_duplicates', :via => [:get, :post]` `ContactsDuplicatesController` does **not** declare `accept_api_auth` and does **not** implement `format.api` responses. ### 2) Merge behavior is server-side and destructive Inside `merge` action: - combines notes, deals, related_deals, issues, projects from source contact into duplicate target, - unions email/phone/tag sets, - saves target, - destroys source contact. This matches the intended business behavior (source contact removed, linked records moved). ### 3) Web form posts to merge action with `duplicate_id` - View: `plugins/redmine_contacts/app/views/contacts_duplicates/index.html.erb` - Uses Rails `form_tag` to `contacts_duplicates#merge` with `project_id`, `contact_id`, and posted `duplicate_id`. ## Practical feasibility ## A) Native Redmine REST API with API key **Not currently feasible** for this merge operation. Reason: no API auth route/serializer for duplicate merge in this plugin path. ## B) Scripted browser-equivalent flow (`curl`/requests with session cookies) **Feasible with caution**. Required mechanics: 1. Authenticate as a normal Redmine user (session/cookie auth). 2. GET duplicate page for the source contact to obtain valid session + CSRF authenticity token. 3. POST to merge action with: - `project_id` - `contact_id` (source) - `duplicate_id` (target) - authenticity token 4. Verify redirect/result contact and audit that source contact no longer exists. ## Risk notes - This is a destructive operation (deletes source contact). - Needs stronger guardrails than normal update operations: - explicit `--apply` style confirmation, - dry-run mode that only validates both contacts and project scope, - optional `--test-instance-only` safety default, - write operation log with source/target IDs and timestamp. ## Recommendation for `redmine_contacts.py` 1. **Do not** add duplicate merge via API-key-only path unless plugin gains a dedicated JSON API action. 2. If we implement now, add a **separate command** using session-auth web flow (not mixed into normal contact update path), e.g.: - `merge-duplicate --source-contact --target-contact --project [--apply]` 3. Keep it disabled-by-default for production base URL, with explicit override flag. ## Suggested next technical step Prototype a standalone script against `http://192.168.50.170` that: - logs in, - resolves CSRF token, - performs merge POST, - verifies resulting contact IDs/issue linkage, - emits structured JSON result for later CLI integration.