Organized

This commit is contained in:
Jason Thistlethwaite
2026-10-17 20:50:35 -04:00
parent bd26c8894f
commit 9ee9eb3ec6
130 changed files with 24172 additions and 140 deletions
@@ -0,0 +1,69 @@
# 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.