3.2 KiB
3.2 KiB
Redmine Contacts duplicate-merge feasibility (API vs script)
Scope
Assess whether duplicate contact merge (web flow: /contacts/<contact_id>/duplicates?project_id=<project_slug> with duplicate_id=<target_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.rbmatch '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_tagtocontacts_duplicates#mergewithproject_id,contact_id, and postedduplicate_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:
- Authenticate as a normal Redmine user (session/cookie auth).
- GET duplicate page for the source contact to obtain valid session + CSRF authenticity token.
- POST to merge action with:
project_idcontact_id(source)duplicate_id(target)- authenticity token
- 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
--applystyle confirmation, - dry-run mode that only validates both contacts and project scope,
- optional
--test-instance-onlysafety default, - write operation log with source/target IDs and timestamp.
- explicit
Recommendation for redmine_contacts.py
- Do not add duplicate merge via API-key-only path unless plugin gains a dedicated JSON API action.
- 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 <id> --target-contact <id> --project <slug> [--apply]
- 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.