Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
341 changes: 173 additions & 168 deletions src/main/api/studio-api.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.craftercms.studio.api.v2.exception.contentType;

import org.craftercms.studio.api.v1.exception.ServiceLayerException;

/**
* Exception to be thrown when trying to delete a content type that is still in use by content items
*/
public class ContentTypeUsageException extends ServiceLayerException {

public ContentTypeUsageException(String siteId, String contentType) {
super(String.format("Content type '%s' is still in use by content items in site '%s'", contentType, siteId));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2007-2025 Crafter Software Corporation. All Rights Reserved.
* Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
Expand All @@ -17,9 +17,11 @@

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.craftercms.studio.api.v1.exception.ServiceLayerException;
import org.craftercms.studio.api.v1.exception.SiteNotFoundException;
import org.craftercms.studio.api.v1.exception.security.AuthenticationException;
import org.craftercms.studio.api.v1.exception.security.UserNotFoundException;
import org.craftercms.studio.api.v2.dal.QuickCreateItem;
import org.craftercms.studio.model.contentType.ContentType;
import org.craftercms.studio.model.contentType.ContentTypeUsage;
import org.springframework.core.io.Resource;

Expand Down Expand Up @@ -109,4 +111,30 @@ void deleteContentType(String siteId, String contentType, boolean deleteDependen
* @return List of quick creatable content types
*/
List<QuickCreateItem> getQuickCreatableContentTypes(String siteId) throws ServiceLayerException;

/**
* Get all content types for the given site.
* @param siteId the id of the site
* @return a collection of content types
* @throws SiteNotFoundException if the site with the given id does not exist
*/
Collection<ContentType> getAllContentTypes(String siteId) throws ServiceLayerException;

/**
* Get the content type configuration for a given content type id
*
* @param siteId the id of the site
* @param contentTypeId the id of the content type
* @return the content type configuration
*/
ContentType getContentType(String siteId, String contentTypeId) throws ServiceLayerException;

/**
* Get a collection of the ids of the content types allowed for the given site and path
*
* @param siteId the id of the site
* @param path the path of the content item to be created
* @return a collection of the ids of the content types allowed for the given site and path
*/
Collection<String> getAllowedContentTypes(String siteId, String path) throws ServiceLayerException;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2007-2025 Crafter Software Corporation. All Rights Reserved.
* Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
Expand All @@ -21,6 +21,7 @@
import org.apache.commons.io.IOUtils;
import org.craftercms.commons.http.RequestContext;
import org.craftercms.studio.api.v1.constant.StudioConstants;
import org.craftercms.studio.model.contentType.ContentType;
import org.dom4j.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -35,6 +36,7 @@
import java.nio.file.Paths;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;

import static org.apache.commons.io.FilenameUtils.directoryContains;
import static org.apache.commons.lang3.StringUtils.isEmpty;
Expand Down Expand Up @@ -116,6 +118,7 @@ public static Path getStudioTemporaryFilesRoot() {
* @throws IOException if an error occurs while creating the file
*/
public static Path createTempFile(String name) throws IOException {
Files.createDirectories(getStudioTemporaryFilesRoot());
return Files.createTempFile(getStudioTemporaryFilesRoot(), UUID.randomUUID().toString(), "." +
FilenameUtils.getExtension(name));
}
Expand Down Expand Up @@ -240,4 +243,24 @@ public static boolean underPagesRoot(String path) {
public static String movePath(String sourceRoot, String targetRoot, String sourcePath) {
return Path.of(targetRoot).resolve(Path.of(sourceRoot).relativize(Path.of(sourcePath))).toString();
}

/**
* Get the content type type by its id, which is determined by the content type naming convention.
*
* @param contentTypeId the content type id to check
* @return <ul>
* <li><b>component</b> if the name matches component naming convention</li>
* <li><b>page</b> if the name matches page naming convention</li>
* <li><b>unknown</b> if name don't match any known convention</li>
* </ul>
*/
public static ContentType.Type getContentTypeTypeById(String contentTypeId) {
if (Pattern.matches("/component/.*?", contentTypeId)) {
return ContentType.Type.component;
}
if (Pattern.matches("/page/.*?", contentTypeId)){
return ContentType.Type.page;
}
return ContentType.Type.unknown;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2007-2023 Crafter Software Corporation. All Rights Reserved.
* Copyright (C) 2007-2026 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
Expand All @@ -16,10 +16,7 @@

package org.craftercms.studio.controller.rest.v2;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.validation.Valid;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.craftercms.commons.validation.annotations.param.EsapiValidatedParam;
import org.craftercms.commons.validation.annotations.param.ValidConfigurationPath;
import org.craftercms.commons.validation.annotations.param.ValidSiteId;
Expand All @@ -28,19 +25,14 @@
import org.craftercms.studio.api.v1.exception.security.UserNotFoundException;
import org.craftercms.studio.api.v2.annotation.LogExecutionTime;
import org.craftercms.studio.api.v2.service.config.ConfigurationService;
import org.craftercms.studio.api.v2.service.content.ContentTypeService;
import org.craftercms.studio.api.v2.utils.StudioConfiguration;
import org.craftercms.studio.api.v2.utils.StudioUtils;
import org.craftercms.studio.model.config.TranslationConfiguration;
import org.craftercms.studio.model.rest.ConfigurationHistory;
import org.craftercms.studio.model.rest.Result;
import org.craftercms.studio.model.rest.ResultOne;
import org.craftercms.studio.model.rest.WriteConfigurationRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

Expand All @@ -51,38 +43,36 @@
import static org.apache.commons.lang3.Strings.CS;
import static org.craftercms.commons.validation.annotations.param.EsapiValidationType.ALPHANUMERIC;
import static org.craftercms.studio.api.v2.utils.StudioConfiguration.CONFIGURATION_GLOBAL_SYSTEM_SITE;
import static org.craftercms.studio.controller.rest.v2.ResultConstants.*;
import static org.craftercms.studio.model.rest.ApiResponse.DELETED;
import static org.craftercms.studio.controller.rest.v2.RequestMappingConstants.*;
import static org.craftercms.studio.controller.rest.v2.ResultConstants.RESULT_KEY_CONFIG;
import static org.craftercms.studio.controller.rest.v2.ResultConstants.RESULT_KEY_HISTORY;
import static org.craftercms.studio.model.rest.ApiResponse.OK;

@Validated
@RestController
@RequestMapping("/api/2/configuration")
@RequestMapping(API_2 + CONFIGURATION)
public class ConfigurationController {

private final ConfigurationService configurationService;
private final StudioConfiguration studioConfiguration;
private final ContentTypeService contentTypeService;
@SuppressWarnings("unused")
private static final Logger logger = LoggerFactory.getLogger(ConfigurationController.class);

@ConstructorProperties({"configurationService", "studioConfiguration", "contentTypeService"})
public ConfigurationController(ConfigurationService configurationService, StudioConfiguration studioConfiguration,
ContentTypeService contentTypeService) {
@ConstructorProperties({"configurationService", "studioConfiguration"})
public ConfigurationController(ConfigurationService configurationService, StudioConfiguration studioConfiguration) {
this.configurationService = configurationService;
this.studioConfiguration = studioConfiguration;
this.contentTypeService = contentTypeService;
}

@GetMapping("clear_cache")
@GetMapping(CLEAR_CACHE)
public Result clearCache(@ValidSiteId @RequestParam String siteId) {
configurationService.invalidateConfiguration(siteId);
var result = new Result();
result.setResponse(OK);
return result;
}

@GetMapping("/get_configuration")
@GetMapping(GET_CONFIGURATION)
@LogExecutionTime
public ResultOne<String> getConfiguration(@ValidSiteId @RequestParam(name = "siteId", required = true) String siteId,
@EsapiValidatedParam(type = ALPHANUMERIC) @RequestParam(name = "module", required = true) String module,
Expand All @@ -102,7 +92,7 @@ public ResultOne<String> getConfiguration(@ValidSiteId @RequestParam(name = "sit
return result;
}

@PostMapping("/write_configuration")
@PostMapping(WRITE_CONFIGURATION)
public Result writeConfiguration(@Validated @RequestBody WriteConfigurationRequest wcRequest)
throws ServiceLayerException, UserNotFoundException, AuthenticationException {
InputStream is = IOUtils.toInputStream(wcRequest.getContent(), UTF_8);
Expand All @@ -118,7 +108,7 @@ public Result writeConfiguration(@Validated @RequestBody WriteConfigurationReque
return result;
}

@GetMapping("/get_configuration_history")
@GetMapping(GET_CONFIGURATION_HISTORY)
public ResultOne<ConfigurationHistory> getConfigurationHistory(@ValidSiteId @RequestParam(name = "siteId", required = true) String siteId,
@EsapiValidatedParam(type = ALPHANUMERIC) @RequestParam(name = "module", required = true) String module,
@ValidConfigurationPath @RequestParam(name = "path", required = true) String path,
Expand All @@ -132,95 +122,12 @@ public ResultOne<ConfigurationHistory> getConfigurationHistory(@ValidSiteId @Req
return result;
}

@GetMapping("translation")
@GetMapping(TRANSLATION)
public ResultOne<TranslationConfiguration> getTranslationConfiguration(@ValidSiteId @RequestParam String siteId) throws ServiceLayerException {
ResultOne<TranslationConfiguration> result = new ResultOne<>();
result.setEntity(RESULT_KEY_CONFIG, configurationService.getTranslationConfiguration(siteId));
result.setResponse(OK);
return result;
}

@GetMapping("content-type/usage")
public ResultOne<Object> getContentTypeUsage(@ValidSiteId @RequestParam String siteId,
@ValidConfigurationPath @RequestParam String contentType)
throws Exception {
var result = new ResultOne<>();
result.setResponse(OK);
result.setEntity(RESULT_KEY_USAGE, contentTypeService.getContentTypeUsage(siteId, contentType));

return result;
}

@GetMapping("content-type/form_controller")
public ResponseEntity<Resource> getContentTypeFormController(@ValidSiteId @RequestParam String siteId,
@ValidConfigurationPath @RequestParam String contentTypeId) throws ServiceLayerException {
ImmutablePair<String, Resource> resource = contentTypeService.getContentTypeFormController(siteId, contentTypeId);
return getResourceResponse(resource.getKey(), resource.getValue());
}

@GetMapping("content-type/preview_image")
public ResponseEntity<Resource> getContentTypePreviewImage(@ValidSiteId @RequestParam String siteId,
@ValidConfigurationPath @RequestParam String contentTypeId)
throws ServiceLayerException {
ImmutablePair<String, Resource> resource = contentTypeService.getContentTypePreviewImage(siteId, contentTypeId);
return getResourceResponse(resource.getKey(), resource.getValue());
}

private ResponseEntity<Resource> getResourceResponse(String name, Resource resource) {
String mimeType = StudioUtils.getMimeType(name);

return ResponseEntity
.ok()
.header(HttpHeaders.CONTENT_TYPE, mimeType)
.body(resource);
}

@PostMapping("content-type/delete")
public Result deleteContentType(@RequestBody @Valid DeleteContentTypeRequest request)
throws ServiceLayerException, AuthenticationException, UserNotFoundException {
contentTypeService.deleteContentType(request.getSiteId(), request.getContentType(),
request.isDeleteDependencies());
var result = new Result();
result.setResponse(DELETED);

return result;
}

@JsonIgnoreProperties
public static class DeleteContentTypeRequest {

@ValidSiteId
protected String siteId;

@ValidConfigurationPath
protected String contentType;

protected boolean deleteDependencies;

public String getSiteId() {
return siteId;
}

public void setSiteId(String siteId) {
this.siteId = siteId;
}

public String getContentType() {
return contentType;
}

public void setContentType(String contentType) {
this.contentType = contentType;
}

public boolean isDeleteDependencies() {
return deleteDependencies;
}

public void setDeleteDependencies(boolean deleteDependencies) {
this.deleteDependencies = deleteDependencies;
}

}

}
Loading