70 lines
3.2 KiB
Markdown
70 lines
3.2 KiB
Markdown
# 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.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 <id> --target-contact <id> --project <slug> [--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.
|