Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,28 @@
import com.dotcms.rest.ResponseEntityView;
import com.dotcms.rest.WebResource;
import com.dotcms.rest.annotation.NoCache;
import com.dotcms.rest.annotation.SwaggerCompliant;
import com.google.common.collect.ImmutableList;
import com.liferay.portal.model.User;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;

import static com.dotcms.util.CollectionsUtils.toImmutableList;

/**
* This end-point provides access to information associated to dotCMS FieldType.
*/
@SwaggerCompliant(value = "Content management and workflow APIs", batch = 2)
@Path("/v1/fieldTypes")
@Tag(name = "Content Type Field", description = "Content type field definitions and configuration")
@Tag(name = "Content Type Field")
public class FieldTypeResource {

private final WebResource webResource;
Expand All @@ -43,10 +50,23 @@ public FieldTypeResource(final WebResource webresource, FieldTypeAPI fieldTypeAP
this.fieldTypeAPI = fieldTypeAPI;
}

@Operation(
summary = "Get field types",
description = "Retrieves all available field types in dotCMS for content type configuration"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Field types retrieved successfully",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = ResponseEntityFieldTypeListView.class))),
@ApiResponse(responseCode = "401",
description = "Unauthorized - authentication required",
content = @Content(mediaType = "application/json"))
})
@GET
@JSONP
@NoCache
@Produces({ MediaType.APPLICATION_JSON, "application/javascript" })
@Produces({ MediaType.APPLICATION_JSON })
public Response getFieldTypes(@Context final HttpServletRequest req) {

final InitDataObject initData = this.webResource.init(null, true, req, true, null);
Expand All @@ -56,6 +76,6 @@ public Response getFieldTypes(@Context final HttpServletRequest req) {
.map(FieldType::toMap)
.collect(toImmutableList());

return Response.ok( new ResponseEntityView<List<Map<String, Object>>>( fieldTypesMap ) ).build();
return Response.ok( new ResponseEntityFieldTypeListView( fieldTypesMap ) ).build();
}
}
349 changes: 324 additions & 25 deletions dotCMS/src/main/java/com/dotcms/rest/ContentResource.java

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.dotcms.rest.api.v1.categories;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import java.io.InputStream;

/**
* Runtime binding bean for multipart form parts.
*/
@Schema(name = "CategoryImportFormSchema", description = "Category import form data with CSV file and processing parameters")
public class CategoryImportData {

private InputStream fileInputStream;
private FormDataContentDisposition fileDetail;
private String filter;
private String exportType;
private String contextInode;

@FormDataParam("file")
@JsonProperty("file")
@Schema(name = "file", description = "CSV file containing categories to import", type = "string", format = "binary")
Copy link
Contributor

@fabrizzio-dotCMS fabrizzio-dotCMS Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you been able to load this into the API Playground?
Once, I attempted to document an InputStream setter and it broke the playground

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting! So, on the Playground end, it seems to be good:

Screenshot 2025-12-22 at 9 32 54 AM

And it generates a 200 when I import a category export CSV. But no new categories appear; system logs indicate a null pointer exception.

[22/12/25 14:35:28:333 GMT] ERROR categories.CategoriesResource: Error importing categories
java.lang.NullPointerException: null
	at java.base/java.io.Reader.<init>(Reader.java:168) ~[?:?]
	at java.base/java.io.InputStreamReader.<init>(InputStreamReader.java:123) ~[?:?]
	at com.dotcms.rest.api.v1.categories.CategoriesResource.processImport(CategoriesResource.java:942) ~[?:?]
	at com.dotcms.rest.api.v1.categories.CategoriesResource.importCategories(CategoriesResource.java:908) ~[?:?]
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[?:?]
	at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[?:?]
	at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory.lambda$static$0(ResourceMethodInvocationHandlerFactory.java:52) ~[?:?]
	at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:124) ~[?:?]
	at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:167) ~[?:?]
	at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$ResponseOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:176) ~[?:?]
	at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:79) ~[?:?]
	at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:469) ~[?:?]
	at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:391) ~[?:?]
	at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:80) ~[?:?]
	at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:253) ~[?:?]
...

public void setFileInputStream(final InputStream inputStream) { this.fileInputStream = inputStream; }

@FormDataParam("file")
@Schema(hidden = true)
public void setFileDetail(final FormDataContentDisposition detail) { this.fileDetail = detail; }

@FormDataParam("filter")
@Schema(description = "Filter pattern for categories")
public void setFilter(final String filter) { this.filter = filter; }

@FormDataParam("exportType")
@Schema(description = "Import behavior", allowableValues = {"replace", "merge"})
public void setExportType(final String exportType) { this.exportType = exportType; }

@FormDataParam("contextInode")
@Schema(description = "Context category inode to import into")
public void setContextInode(final String contextInode) { this.contextInode = contextInode; }

public InputStream getFileInputStream() { return fileInputStream; }
public FormDataContentDisposition getFileDetail() { return fileDetail; }
public String getFilter() { return filter; }
public String getExportType() { return exportType; }
public String getContextInode() { return contextInode; }
}

Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,26 @@
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import com.dotcms.rest.InitDataObject;
import com.dotcms.rest.WebResource;
import com.dotcms.rest.annotation.NoCache;
import com.dotcms.rest.annotation.SwaggerCompliant;
import com.dotcms.rest.exception.mapper.ExceptionMapperUtil;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.portlets.contentlet.model.Contentlet;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.json.JSONException;
import com.liferay.portal.model.User;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;

import javax.servlet.http.HttpServletRequest;
Expand All @@ -40,8 +48,9 @@
* 3 --> The contentlet object will contain the related contentlets, which in turn will contain a list of their related contentlets
* null --> Relationships will not be sent in the response
*/
@SwaggerCompliant(value = "Content management and workflow APIs", batch = 2)
@Path("/v1/contentrelationships")
@Tag(name = "Content", description = "Endpoints for managing content and contentlets")
@Tag(name = "Content")
@Deprecated
public class ContentRelationshipsResource {

Expand Down Expand Up @@ -87,11 +96,29 @@ protected ContentRelationshipsResource(final ContentRelationshipsHelper
* @param params A Map of parameters that will define the search criteria.
* @return The list of associated contents.
*/
@Operation(
summary = "Get content with relationships (deprecated)",
description = "Retrieves content with relationships based on query parameters, identifier, or inode. This endpoint is deprecated - use /v1/content with depth parameter instead."
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Content with relationships retrieved successfully",
content = @Content(mediaType = "application/json",
schema = @Schema(type = "object", description = "Content with relationships in JSON format including contentlets and their related content"))),
@ApiResponse(responseCode = "401",
description = "Unauthorized - authentication required",
content = @Content(mediaType = "application/json")),
@ApiResponse(responseCode = "500",
description = "Internal server error",
content = @Content(mediaType = "application/json"))
})
@NoCache
@GET
@Path("/{params: .*}")
@Produces(MediaType.APPLICATION_JSON)
public Response getContent(@Context final HttpServletRequest request,
@Context final HttpServletResponse response,
@Parameter(description = "Query parameters, identifier, or inode for content lookup", required = true)
@PathParam("params") final String params) {
final InitDataObject initData = this.webResource.init(params, request, response, false, null);
final Map<String, String> paramsMap = initData.getParamsMap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.dotcms.rest.WebResource;
import com.dotcms.rest.annotation.NoCache;
import com.dotcms.rest.annotation.SwaggerCompliant;
import com.dotcms.rest.api.v1.site.ResponseSiteVariablesEntityView;
import com.dotcms.util.PaginationUtil;
import com.dotcms.util.pagination.ContentReportPaginator;
Expand All @@ -13,6 +14,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.liferay.portal.model.User;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
Expand Down Expand Up @@ -48,6 +50,7 @@
* @author Jose Castro
* @since Mar 7th, 2024
*/
@SwaggerCompliant(value = "Content management and workflow APIs", batch = 2)
@Path("/v1/contentreport")
@Tag(name = "Content Report")
public class ContentReportResource {
Expand Down Expand Up @@ -91,9 +94,10 @@ public ContentReportResource(final WebResource webResource) {
@Path("/site/{site}")
@JSONP
@NoCache
@Produces({MediaType.APPLICATION_JSON, "application/javascript"})
@Operation(summary = "Generates a report of the different Content Types living under a Site, " +
"and the number of content items for each type",
@Produces({MediaType.APPLICATION_JSON})
@Operation(
summary = "Generate site content report",
description = "Generates a detailed report of the different Content Types living under a Site and the number of content items for each type. Useful for data analysis and deletion planning.",
responses = {
@ApiResponse(
responseCode = "200",
Expand All @@ -106,11 +110,11 @@ public ContentReportResource(final WebResource webResource) {
})
public Response getSiteContentReport(@Context final HttpServletRequest httpServletRequest,
@Context final HttpServletResponse httpServletResponse,
@PathParam(ContentReportPaginator.SITE_PARAM) final String site,
@QueryParam(PaginationUtil.PAGE) final int page,
@QueryParam(PaginationUtil.PER_PAGE) final int perPage,
@DefaultValue("upper(name)") @QueryParam(PaginationUtil.ORDER_BY) final String orderBy,
@DefaultValue("ASC") @QueryParam(PaginationUtil.DIRECTION) final String direction) {
@Parameter(description = "Site ID or key to generate the report for", required = true) @PathParam(ContentReportPaginator.SITE_PARAM) final String site,
@Parameter(description = "Page number for pagination") @QueryParam(PaginationUtil.PAGE) final int page,
@Parameter(description = "Number of items per page") @QueryParam(PaginationUtil.PER_PAGE) final int perPage,
@Parameter(description = "Field to order results by") @DefaultValue("upper(name)") @QueryParam(PaginationUtil.ORDER_BY) final String orderBy,
@Parameter(description = "Sort direction (ASC or DESC)") @DefaultValue("ASC") @QueryParam(PaginationUtil.DIRECTION) final String direction) {
final User user = new WebResource.InitBuilder(this.webResource)
.requestAndResponse(httpServletRequest, httpServletResponse)
.requiredBackendUser(true)
Expand Down Expand Up @@ -154,9 +158,10 @@ public Response getSiteContentReport(@Context final HttpServletRequest httpServl
@Path("/folder/{folder: .*}")
@JSONP
@NoCache
@Produces({MediaType.APPLICATION_JSON, "application/javascript"})
@Operation(summary = "Generates a report of the different Content Types living under a " +
"Folder, and the number of content items for each type",
@Produces({MediaType.APPLICATION_JSON})
@Operation(
summary = "Generate folder content report",
description = "Generates a detailed report of the different Content Types living under a Folder and the number of content items for each type. Supports both folder ID and folder path (requires site parameter).",
responses = {
@ApiResponse(
responseCode = "200",
Expand All @@ -168,12 +173,12 @@ public Response getSiteContentReport(@Context final HttpServletRequest httpServl
})
public Response getFolderContentReport(@Context final HttpServletRequest httpServletRequest,
@Context final HttpServletResponse httpServletResponse,
@PathParam(ContentReportPaginator.FOLDER_PARAM) final String folder,
@QueryParam(ContentReportPaginator.SITE_PARAM) final String site,
@QueryParam(PaginationUtil.PAGE) final int page,
@QueryParam(PaginationUtil.PER_PAGE) final int perPage,
@DefaultValue("upper(name)") @QueryParam(PaginationUtil.ORDER_BY) final String orderBy,
@DefaultValue("ASC") @QueryParam(PaginationUtil.DIRECTION) final String direction) {
@Parameter(description = "Folder ID or path to generate the report for", required = true) @PathParam(ContentReportPaginator.FOLDER_PARAM) final String folder,
@Parameter(description = "Site ID or key (required when using folder path)") @QueryParam(ContentReportPaginator.SITE_PARAM) final String site,
@Parameter(description = "Page number for pagination") @QueryParam(PaginationUtil.PAGE) final int page,
@Parameter(description = "Number of items per page") @QueryParam(PaginationUtil.PER_PAGE) final int perPage,
@Parameter(description = "Field to order results by") @DefaultValue("upper(name)") @QueryParam(PaginationUtil.ORDER_BY) final String orderBy,
@Parameter(description = "Sort direction (ASC or DESC)") @DefaultValue("ASC") @QueryParam(PaginationUtil.DIRECTION) final String direction) {
final User user = new WebResource.InitBuilder(this.webResource)
.requestAndResponse(httpServletRequest, httpServletResponse)
.requiredBackendUser(true)
Expand Down
Loading
Loading