chore: Add ApplicationCreationDTO for Application creation API body (#33200)
This PR cleans up the application creation API with stricter validations on the input. We're also moving the `workspaceId` from the query parameter into the body, so it can be validated togather and all input data is in one place. Simplifies code both in client and server. No additional changes for EE, and no conflicts either, and al unit and Cypress tests pass. /ok-to-test tags="@tag.Sanity" <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/9130707553> > Commit: 916b54c802a163910c738e3f8ceb203314a38a6c > Cypress dashboard url: <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=9130707553&attempt=1" target="_blank">Click here!</a> <!-- end of auto-generated comment: Cypress test results -->
This commit is contained in:
parent
8682dd2b90
commit
b831a3943b
|
|
@ -954,9 +954,7 @@ Cypress.Commands.add("startServerAndRoutes", () => {
|
|||
|
||||
cy.intercept("GET", "/api/v1/plugins/*/form").as("getPluginForm");
|
||||
cy.intercept("DELETE", "/api/v1/applications/*").as("deleteApplication");
|
||||
cy.intercept("POST", "/api/v1/applications?workspaceId=*").as(
|
||||
"createNewApplication",
|
||||
);
|
||||
cy.intercept("POST", "/api/v1/applications").as("createNewApplication");
|
||||
cy.intercept("PUT", "/api/v1/applications/*").as("updateApplication");
|
||||
cy.intercept("PUT", "/api/v1/actions/*").as("saveAction");
|
||||
cy.intercept("PUT", "/api/v1/actions/move").as("moveAction");
|
||||
|
|
|
|||
|
|
@ -283,8 +283,6 @@ export class ApplicationApi extends Api {
|
|||
static baseURL = "v1/applications";
|
||||
static publishURLPath = (applicationId: string) =>
|
||||
`/publish/${applicationId}`;
|
||||
static createApplicationPath = (workspaceId: string) =>
|
||||
`?workspaceId=${workspaceId}`;
|
||||
static changeAppViewAccessPath = (applicationId: string) =>
|
||||
`/${applicationId}/changeAccess`;
|
||||
static setDefaultPagePath = (request: SetDefaultPageRequest) =>
|
||||
|
|
@ -341,28 +339,14 @@ export class ApplicationApi extends Api {
|
|||
static async createApplication(
|
||||
request: CreateApplicationRequest,
|
||||
): Promise<AxiosPromise<PublishApplicationResponse>> {
|
||||
const applicationDetail = {
|
||||
appPositioning: {
|
||||
type: request.layoutSystemType,
|
||||
},
|
||||
} as any;
|
||||
|
||||
if (request.showNavbar !== undefined) {
|
||||
applicationDetail.navigationSetting = {
|
||||
showNavbar: request.showNavbar,
|
||||
};
|
||||
}
|
||||
|
||||
return Api.post(
|
||||
ApplicationApi.baseURL +
|
||||
ApplicationApi.createApplicationPath(request.workspaceId),
|
||||
{
|
||||
name: request.name,
|
||||
color: request.color,
|
||||
icon: request.icon,
|
||||
applicationDetail,
|
||||
},
|
||||
);
|
||||
return Api.post(ApplicationApi.baseURL, {
|
||||
workspaceId: request.workspaceId,
|
||||
name: request.name,
|
||||
color: request.color,
|
||||
icon: request.icon,
|
||||
positioningType: request.layoutSystemType,
|
||||
showNavbar: request.showNavbar ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
static async setDefaultApplicationPage(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.appsmith.server.domains.Application;
|
|||
import com.appsmith.server.domains.GitAuth;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.dtos.ApplicationAccessDTO;
|
||||
import com.appsmith.server.dtos.ApplicationCreationDTO;
|
||||
import com.appsmith.server.dtos.ApplicationImportDTO;
|
||||
import com.appsmith.server.dtos.ApplicationJson;
|
||||
import com.appsmith.server.dtos.ApplicationPagesDTO;
|
||||
|
|
@ -19,8 +20,6 @@ import com.appsmith.server.dtos.GitAuthDTO;
|
|||
import com.appsmith.server.dtos.PartialExportFileDTO;
|
||||
import com.appsmith.server.dtos.ReleaseItemsDTO;
|
||||
import com.appsmith.server.dtos.ResponseDTO;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.exports.internal.ExportService;
|
||||
import com.appsmith.server.exports.internal.partial.PartialExportService;
|
||||
import com.appsmith.server.fork.internal.ApplicationForkingService;
|
||||
|
|
@ -77,14 +76,10 @@ public class ApplicationControllerCE {
|
|||
@JsonView(Views.Public.class)
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ResponseDTO<Application>> create(
|
||||
@Valid @RequestBody Application resource, @RequestParam String workspaceId) {
|
||||
if (workspaceId == null) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, "workspace id"));
|
||||
}
|
||||
log.debug("Going to create application in workspace {}", workspaceId);
|
||||
public Mono<ResponseDTO<Application>> create(@Valid @RequestBody ApplicationCreationDTO resource) {
|
||||
log.debug("Going to create application in workspace {}", resource.workspaceId());
|
||||
return applicationPageService
|
||||
.createApplication(resource, workspaceId)
|
||||
.createApplication(resource.toApplication())
|
||||
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -428,12 +428,13 @@ public class Application extends BaseDomain implements Artifact {
|
|||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class AppPositioning {
|
||||
@JsonView({Views.Public.class, Git.class})
|
||||
Type type;
|
||||
|
||||
public AppPositioning(Type type) {
|
||||
this.type = type;
|
||||
public AppPositioning(String type) {
|
||||
setType(Type.valueOf(type));
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
package com.appsmith.server.dtos;
|
||||
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.ApplicationDetail;
|
||||
import com.appsmith.server.meta.validations.IconName;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public record ApplicationCreationDTO(
|
||||
@NotBlank @Size(max = 99) String workspaceId,
|
||||
@NotBlank @Size(max = 99) String name,
|
||||
@IconName String icon,
|
||||
@Pattern(regexp = "#[A-F0-9]{6}") String color,
|
||||
Application.AppPositioning positioningType,
|
||||
Boolean showNavBar) {
|
||||
|
||||
public Application toApplication() {
|
||||
final Application application = new Application();
|
||||
application.setWorkspaceId(workspaceId);
|
||||
application.setName(name.trim());
|
||||
application.setIcon(StringUtils.isBlank(icon) ? null : icon.trim());
|
||||
application.setColor(color);
|
||||
|
||||
final ApplicationDetail applicationDetail = new ApplicationDetail();
|
||||
application.setApplicationDetail(applicationDetail);
|
||||
|
||||
applicationDetail.setAppPositioning(positioningType);
|
||||
|
||||
final Application.NavigationSetting navigationSetting = new Application.NavigationSetting();
|
||||
navigationSetting.setShowNavbar(showNavBar);
|
||||
applicationDetail.setNavigationSetting(navigationSetting);
|
||||
|
||||
return application;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,13 +8,13 @@ import jakarta.validation.constraints.Size;
|
|||
import java.util.List;
|
||||
|
||||
public record PageCreationDTO(
|
||||
@FileName(message = "Page names must be valid file names") @Size(max = 30) String name,
|
||||
@FileName(message = "Page names must be valid file names", isNullValid = false) @Size(max = 30) String name,
|
||||
@NotEmpty @Size(min = 24, max = 50) String applicationId,
|
||||
@NotEmpty List<Layout> layouts) {
|
||||
|
||||
public PageDTO toPageDTO() {
|
||||
final PageDTO page = new PageDTO();
|
||||
page.setName(name);
|
||||
page.setName(name.trim());
|
||||
page.setApplicationId(applicationId);
|
||||
page.setLayouts(layouts);
|
||||
return page;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
package com.appsmith.server.dtos;
|
||||
|
||||
import com.appsmith.server.meta.validations.FileName;
|
||||
import com.appsmith.server.meta.validations.IconName;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public record PageUpdateDTO(
|
||||
@FileName(message = "Page names must be valid file names") @Size(max = 30) String name,
|
||||
@Pattern(regexp = "[-a-z]+") @Size(max = 20) String icon,
|
||||
@IconName String icon,
|
||||
@Pattern(regexp = "[-\\w]*") String customSlug,
|
||||
Boolean isHidden) {
|
||||
|
||||
public PageDTO toPageDTO() {
|
||||
final PageDTO page = new PageDTO();
|
||||
page.setName(name);
|
||||
page.setIcon(icon);
|
||||
page.setName(name == null ? null : name.trim());
|
||||
page.setIcon(StringUtils.isBlank(icon) ? null : icon.trim());
|
||||
page.setCustomSlug(customSlug);
|
||||
page.setIsHidden(isHidden);
|
||||
return page;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package com.appsmith.server.meta.validations;
|
||||
|
||||
import jakarta.validation.Constraint;
|
||||
import jakarta.validation.Payload;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Documented
|
||||
@Constraint(validatedBy = {IconNameValidator.class})
|
||||
@Target({ElementType.FIELD, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface IconName {
|
||||
String message() default "Invalid icon name";
|
||||
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.appsmith.server.meta.validations;
|
||||
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class IconNameValidator implements ConstraintValidator<IconName, String> {
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile("[-a-z]{1,20}");
|
||||
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||
return value == null || PATTERN.matcher(value).matches();
|
||||
}
|
||||
}
|
||||
|
|
@ -58,12 +58,9 @@ public class EqualityTest {
|
|||
|
||||
@Test
|
||||
void testApplicationDetail() {
|
||||
Application.AppPositioning p1 = new Application.AppPositioning();
|
||||
p1.setType(Application.AppPositioning.Type.AUTO);
|
||||
Application.AppPositioning p2 = new Application.AppPositioning();
|
||||
p2.setType(Application.AppPositioning.Type.AUTO);
|
||||
Application.AppPositioning p3 = new Application.AppPositioning();
|
||||
p3.setType(Application.AppPositioning.Type.FIXED);
|
||||
Application.AppPositioning p1 = new Application.AppPositioning(Application.AppPositioning.Type.AUTO);
|
||||
Application.AppPositioning p2 = new Application.AppPositioning(Application.AppPositioning.Type.AUTO);
|
||||
Application.AppPositioning p3 = new Application.AppPositioning(Application.AppPositioning.Type.FIXED);
|
||||
assertThat(p1).isEqualTo(p2).isNotEqualTo(p3);
|
||||
|
||||
ApplicationDetail d1 = new ApplicationDetail();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user