Skip to content

maintenance portlet | Implement bulk delete contentlets API #35191#35233

Open
hassandotcms wants to merge 9 commits intomainfrom
35203-maintenance-delete-contentlets
Open

maintenance portlet | Implement bulk delete contentlets API #35191#35233
hassandotcms wants to merge 9 commits intomainfrom
35203-maintenance-delete-contentlets

Conversation

@hassandotcms
Copy link
Copy Markdown
Member

Summary

Add 1 new REST endpoint to MaintenanceResource, replacing legacy DWR bulk contentlet deletion with a modern REST API for the Maintenance portlet Tools tab.

New endpoint:

  • DELETE /api/v1/maintenance/_contentlets — Takes a JSON array of contentlet identifiers, resolves all language siblings for each, and permanently destroys them (bypasses trash). Each contentlet is destroyed independently — one failure does not block others. Returns deleted count and list of failed identifiers.

Implementation details:

  • DeleteContentletsForm extends Validated with @JsonCreator, validates identifiers list is non-empty
  • DeleteContentletsResultView (@Value.Immutable) with deleted count and errors list
  • One-by-one destroy with error collection (matches legacy DWR behavior and BulkActionsResultView pattern)
  • Uses respectFrontendRoles=false (modern REST convention, no effect for admin users)
  • SecurityLogger entry logs user and identifier count
  • Accepts JSON array instead of legacy comma-separated string

Test plan

  • Integration tests pass: ./mvnw verify -pl :dotcms-integration -Dcoreit.test.skip=false -Dit.test=MaintenanceResourceIntegrationTest
  • DELETE /_contentlets with valid identifier destroys contentlet and all siblings
  • DELETE /_contentlets with multiple identifiers destroys all
  • DELETE /_contentlets with non-existent identifier returns {deleted: 0, errors: []}
  • DELETE /_contentlets with empty/null identifiers returns 400
  • DELETE /_contentlets without request body returns 400
  • Non-admin users get 403
  • SecurityLogger entry appears with user and identifier count

Closes #35203

…s, and delete pushed assets #35191

Add three new endpoints to MaintenanceResource:
- POST /_searchAndReplace: database-wide find/replace across content tables
- DELETE /_oldVersions: drop versionable objects older than a given date
- DELETE /_pushedAssets: clear all push publishing history

Includes Validated forms, @Value.Immutable response views, full Swagger
annotations, and SecurityLogger entries for all destructive operations.
…ions, and pushed assets #35191

Tests cover:
- _searchAndReplace: admin success, non-admin rejection, null/empty form validation,
  empty replaceString allowed
- _oldVersions: admin success, non-admin rejection, missing date, invalid date formats
- _pushedAssets: admin success, non-admin rejection
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 7, 2026

Claude finished @hassandotcms's task in 2m 50s —— View job


PR Review

  • Read changed files
  • Analyze for bugs, risks, and design issues

1. Bug — getSiblings() failures are silently swallowed (not reported in errors)

MaintenanceResource.java:966-972 — when getSiblings() throws, the identifier is only logged; it is never added to errors and never counted as deleted. The caller receives {deleted: 0, errors: []} and has no way to know the identifier could not be resolved.

// current (line 966-972)
try {
    contentlets.addAll(conAPI.getSiblings(trimmedId));
} catch (final Exception e) {
    Logger.warn(this, ...);
    // ← trimmedId is silently dropped; should be: errors.add(trimmedId);
}

Fix: add errors.add(trimmedId) inside the catch block. Fix this →


2. API contract — errors field name is misleading

AbstractDeleteContentletsResultView.java:32 — the field is named errors but contains a list of identifiers that failed, not error messages. This is confusing to API consumers and conflicts with the field description ("Identifiers of contentlets that failed to be destroyed"). failedIdentifiers would be accurate and self-documenting. Changing this now is cheaper than after clients adopt it.


3. No upper bound on identifiers list

DeleteContentletsForm.java:39 — validation only rejects empty lists. A caller can send thousands of identifiers; each triggers a getSiblings() call that may itself return multiple contentlets, all of which are then destroy()-ed serially. This is an unbounded synchronous operation with no timeout, no size cap, and no feedback while running. Worth adding a configurable max (e.g., Config.getIntProperty("MAX_BULK_DELETE_CONTENTLETS", 1000)) and throwing 400 if exceeded.


4. Blank identifiers silently ignored

MaintenanceResource.java:965 — blank strings in the identifiers list (e.g., ["", " "]) pass form validation, are trimmed and skipped silently, and the caller gets {deleted: 0, errors: []} with no indication the input was unusable. Either validate in DeleteContentletsForm.checkValid() that no element is blank, or add those to errors.


5. AbstractSearchAndReplaceResultView — redundant dual fields

AbstractSearchAndReplaceResultView.java:25-33success() and hasErrors() are always logical inverses (set via success(!hasErrors) in MaintenanceResource.java:591). Dual fields risk desynchronization if the builder logic changes. One field should drive the other.


6. Misplaced ArrayList import (minor)

MaintenanceResource.java:72import java.util.ArrayList is in the middle of com.dot* imports, separated from java.util.List at line 81. Cosmetic but worth fixing.


No issues with: auth (assertBackendUser + admin check is correct), respectFrontendRoles=false (appropriate for admin-only endpoint), the Immutables pattern, @JsonCreator deserialization, SecurityLogger entries, or the test coverage (which is solid for the happy paths).

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 7, 2026

Claude finished @hassandotcms's task in 1m 31s —— View job


I'll analyze this and get back to you.

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 7, 2026

Rollback Safety Analysis

  • Read rollback-unsafe categories reference
  • Get full PR diff
  • Analyze diff against all unsafe categories
  • Apply label

Result: ✅ Safe To Rollback

This PR adds a new DELETE /api/v1/maintenance/_contentlets REST endpoint. The changes were checked against all rollback-unsafe categories:

Category Verdict
C-1 Structural Data Model Change ✅ No runonce tasks, no core table changes
C-2 Elasticsearch Mapping Change ✅ No ES mapping changes
C-3 Content JSON Model Version Bump ✅ No CURRENT_MODEL_VERSION changes
C-4 DROP TABLE / DROP COLUMN ✅ No DDL drop statements
H-1 One-Way Data Migration ✅ No batch UPDATE/DELETE on business data
H-2 RENAME TABLE / RENAME COLUMN ✅ None
H-3 PK Restructuring ✅ None
H-4 New ContentType Field Type ✅ None
H-5 Storage Provider Change ✅ None
H-6 DROP PROCEDURE / DROP FUNCTION ✅ None
H-7 NOT NULL Column Without Default ✅ None
M-1 Non-Broadening Column Type Change ✅ None
M-2 Push Publishing Bundle Format Change ✅ None
M-3 REST/GraphQL API Contract Change ✅ Additive only — new endpoint, no existing contracts changed
M-4 OSGi Plugin API Breakage ✅ No interface changes

The PR is purely additive: new Java classes (DeleteContentletsForm, AbstractDeleteContentletsResultView, ResponseEntityDeleteContentletsResultView) and a new endpoint in MaintenanceResource. Rolling back to N-1 simply removes the new endpoint — no existing data, schema, mappings, or API contracts are affected.

Label "AI: Safe To Rollback" has been applied.

View job run

@hassandotcms hassandotcms changed the base branch from 35200-maintenance-fix-assets-clean-assets to main April 7, 2026 13:09
@github-actions github-actions bot added the Area : Backend PR changes Java/Maven backend code label Apr 7, 2026
@hassandotcms hassandotcms force-pushed the 35203-maintenance-delete-contentlets branch from 2ce0df1 to d91001d Compare April 7, 2026 14:53
…ts with polling #35191

Add four new endpoints to MaintenanceResource:
- POST /_fixAssets: run all FixTask classes to fix DB inconsistencies
- GET /_fixAssets: poll fix assets task results
- POST /_cleanAssets: start background thread to clean orphan asset dirs
- GET /_cleanAssets: poll clean assets progress status

POST /_cleanAssets returns 409 Conflict if already running.
Includes @Value.Immutable CleanAssetsStatusView, full Swagger annotations,
and integration tests.
Add DELETE /_contentlets endpoint to MaintenanceResource:
- Accepts JSON array of contentlet identifiers
- Resolves all language siblings for each identifier
- Destroys each contentlet independently with error collection
- Returns deleted count and list of failed identifiers

Includes Validated form, @Value.Immutable result view, full Swagger
annotations, SecurityLogger entry, and integration tests.
@hassandotcms hassandotcms force-pushed the 35203-maintenance-delete-contentlets branch from d91001d to 7497dae Compare April 8, 2026 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[TASK] Implement delete contentlets endpoint

1 participant