Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class PageSpanCE {
public static final String GET_PAGE_WITHOUT_BRANCH = PAGES + "without_branch";
public static final String GET_PAGE_WITH_BRANCH = PAGES + "with_branch";
public static final String FETCH_PAGE_FROM_DB = PAGES + "fetch_page";
public static final String FETCH_PAGES_WITH_BASE_ID = PAGES + "fetch_pages_with_base_id";

public static final String FETCH_PAGES_BY_APP_ID_DB = PAGES + "fetch_pages_by_app_id";
public static final String MARK_RECENTLY_ACCESSED_RESOURCES_PAGES = PAGES + "update_recently_accessed_pages";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.appsmith.external.dtos;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
@RequiredArgsConstructor
public class UniqueSlugDTO {

String branchedPageId;

String branchedApplicationId;

String uniquePageSlug;

String uniqueApplicationSlug;

Boolean staticUrlEnabled;

Boolean isUniqueSlugAvailable;
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ public enum FeatureFlagEnum {
release_git_autocommit_eligibility_enabled,
release_dynamodb_connection_time_to_live_enabled,
release_reactive_actions_enabled,
/**
* Enables static and human-readable URLs for applications and pages. When enabled, Appsmith apps use
* predictable, unique slugs for app and page routes instead of dynamic URLs,
* improving usability, cross-instance navigation, and compatibility with Git.
*/
release_static_url_enabled,
/**
* Feature flag to enable alphabetical ordering for workspaces and applications
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,9 @@ Mono<Application> findByBaseIdBranchNameAndApplicationMode(
String defaultApplicationId, String branchName, ApplicationMode mode);

Mono<Application> findByBranchedApplicationIdAndApplicationMode(String branchedApplicationId, ApplicationMode mode);

Flux<Application> findByUniqueAppName(String uniqueAppName, AclPermission aclPermission);

Flux<Application> findByUniqueAppNameRefNameAndApplicationMode(
String uniqueAppName, String refName, ApplicationMode applicationMode);
}
Original file line number Diff line number Diff line change
Expand Up @@ -1050,4 +1050,23 @@ public Mono<Application> findByBranchedApplicationIdAndApplicationMode(
.switchIfEmpty(Mono.error(new AppsmithException(
AppsmithError.NO_RESOURCE_FOUND, FieldName.APPLICATION, branchedApplicationId)));
}

@Override
public Flux<Application> findByUniqueAppName(String uniqueAppName, AclPermission aclPermission) {
return repository.findByUniqueAppName(uniqueAppName, aclPermission);
}

@Override
public Flux<Application> findByUniqueAppNameRefNameAndApplicationMode(
String uniqueAppName, String refName, ApplicationMode applicationMode) {
AclPermission permissionForApplication = ApplicationMode.PUBLISHED.equals(applicationMode)
? applicationPermission.getReadPermission()
: applicationPermission.getEditPermission();

if (!StringUtils.hasText(refName)) {
return repository.findByUniqueAppName(uniqueAppName, permissionForApplication);
}

return repository.findByUniqueAppSlugRefName(uniqueAppName, refName, permissionForApplication);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.appsmith.server.solutions.DatasourcePermission;
import com.appsmith.server.solutions.PagePermission;
import com.appsmith.server.solutions.WorkspacePermission;
import com.appsmith.server.staticurl.StaticUrlService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
Expand Down Expand Up @@ -66,6 +67,7 @@ public class ApplicationImportServiceCEImpl
implements ArtifactBasedImportServiceCE<Application, ApplicationImportDTO, ApplicationJson> {

private final ApplicationService applicationService;
protected final StaticUrlService staticUrlService;
private final ApplicationPageService applicationPageService;
private final NewActionService newActionService;
private final UpdateLayoutService updateLayoutService;
Expand Down Expand Up @@ -397,9 +399,12 @@ public Mono<Application> updateAndSaveArtifactInContext(
});

if (!StringUtils.hasText(importingMetaDTO.getArtifactId())) {
importApplicationMono = importApplicationMono.flatMap(application -> {
return applicationPageService.createOrUpdateSuffixedApplication(application, application.getName(), 0);
});
importApplicationMono = importApplicationMono
.flatMap(staticUrlService::generateAndUpdateApplicationSlugForNewImports)
.flatMap(application -> {
return applicationPageService.createOrUpdateSuffixedApplication(
application, application.getName(), 0);
});
} else {
Mono<Application> existingApplicationMono = applicationService
.findById(
Expand Down Expand Up @@ -432,18 +437,24 @@ public Mono<Application> updateAndSaveArtifactInContext(
} else {
importApplicationMono = importApplicationMono
.zipWith(existingApplicationMono)
.map(objects -> {
Application newApplication = objects.getT1();
Application existingApplication = objects.getT2();
// This method sets the published mode properties in the imported
// application.When a user imports an application from the git repo,
// since the git only stores the unpublished version, the current
// deployed version in the newly imported app is not updated.
// This function sets the initial deployed version to the same as the
// edit mode one.
setPublishedApplicationProperties(newApplication);
setPropertiesToExistingApplication(newApplication, existingApplication);
return existingApplication;
.flatMap(applicationFromJsonAndDB -> {
Application appFromJson = applicationFromJsonAndDB.getT1();
Application existingAppFromDB = applicationFromJsonAndDB.getT2();

return staticUrlService
.generateAndUpdateApplicationSlugForImportsOnExistingApps(
appFromJson, existingAppFromDB)
.map(newApplication -> {
// This method sets the published mode properties in the imported
// application.When a user imports an application from the git repo,
// since the git only stores the unpublished version, the current
// deployed version in the newly imported app is not updated.
// This function sets the initial deployed version to the same as the
// edit mode one.
setPublishedApplicationProperties(newApplication);
setPropertiesToExistingApplication(newApplication, existingAppFromDB);
return existingAppFromDB;
});
})
.flatMap(application -> {
Mono<Application> parentApplicationMono;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.appsmith.server.solutions.DatasourcePermission;
import com.appsmith.server.solutions.PagePermission;
import com.appsmith.server.solutions.WorkspacePermission;
import com.appsmith.server.staticurl.StaticUrlService;
import org.springframework.stereotype.Service;

@Service
Expand All @@ -28,6 +29,7 @@ public class ApplicationImportServiceImpl extends ApplicationImportServiceCEImpl

public ApplicationImportServiceImpl(
ApplicationService applicationService,
StaticUrlService staticUrlService,
ApplicationPageService applicationPageService,
NewActionService newActionService,
UpdateLayoutService updateLayoutService,
Expand All @@ -44,6 +46,7 @@ public ApplicationImportServiceImpl(
ImportableService<ActionCollection> actionCollectionImportableService) {
super(
applicationService,
staticUrlService,
applicationPageService,
newActionService,
updateLayoutService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.appsmith.server.services.ApplicationPageService;
import com.appsmith.server.services.ApplicationSnapshotService;
import com.appsmith.server.solutions.UserReleaseNotes;
import com.appsmith.server.staticurl.StaticUrlService;
import com.appsmith.server.themes.base.ThemeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -25,6 +26,7 @@ public class ApplicationController extends ApplicationControllerCE {
public ApplicationController(
ArtifactService artifactService,
ApplicationService service,
StaticUrlService staticUrlService,
ApplicationPageService applicationPageService,
UserReleaseNotes userReleaseNotes,
ApplicationForkingService applicationForkingService,
Expand All @@ -37,6 +39,7 @@ public ApplicationController(
super(
artifactService,
service,
staticUrlService,
applicationPageService,
userReleaseNotes,
applicationForkingService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.appsmith.server.newpages.base.NewPageService;
import com.appsmith.server.services.ApplicationPageService;
import com.appsmith.server.solutions.CreateDBTablePageSolution;
import com.appsmith.server.staticurl.StaticUrlService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -17,8 +18,9 @@ public class PageController extends PageControllerCE {
public PageController(
ApplicationPageService applicationPageService,
NewPageService newPageService,
CreateDBTablePageSolution createDBTablePageSolution) {
CreateDBTablePageSolution createDBTablePageSolution,
StaticUrlService staticUrlService) {

super(applicationPageService, newPageService, createDBTablePageSolution);
super(applicationPageService, newPageService, createDBTablePageSolution, staticUrlService);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.appsmith.server.controllers.ce;

import com.appsmith.external.dtos.UniqueSlugDTO;
import com.appsmith.external.models.Datasource;
import com.appsmith.external.views.Views;
import com.appsmith.server.applications.base.ApplicationService;
Expand Down Expand Up @@ -31,6 +32,7 @@
import com.appsmith.server.services.ApplicationPageService;
import com.appsmith.server.services.ApplicationSnapshotService;
import com.appsmith.server.solutions.UserReleaseNotes;
import com.appsmith.server.staticurl.StaticUrlService;
import com.appsmith.server.themes.base.ThemeService;
import com.fasterxml.jackson.annotation.JsonView;
import jakarta.validation.Valid;
Expand Down Expand Up @@ -66,6 +68,7 @@ public class ApplicationControllerCE {

protected final ArtifactService artifactService;
protected final ApplicationService service;
protected final StaticUrlService staticUrlService;
private final ApplicationPageService applicationPageService;
private final UserReleaseNotes userReleaseNotes;
private final ApplicationForkingService applicationForkingService;
Expand Down Expand Up @@ -337,4 +340,47 @@ public Mono<ResponseDTO<BuildingBlockResponseDTO>> importBlock(@RequestBody Buil
.importBuildingBlock(buildingBlockDTO)
.map(fetchedResource -> new ResponseDTO<>(HttpStatus.CREATED, fetchedResource));
}

@JsonView(Views.Public.class)
@GetMapping(value = "/{branchedApplicationId}/static-url/suggest-app-slug")
public Mono<ResponseDTO<String>> suggestUniqueApplicationSlug(@PathVariable String branchedApplicationId) {
return staticUrlService
.suggestUniqueApplicationSlug(branchedApplicationId)
.map(url -> new ResponseDTO<>(HttpStatus.OK, url));
}

@JsonView(Views.Public.class)
@PostMapping(value = "/{branchedApplicationId}/static-url")
public Mono<ResponseDTO<Application>> autoGenerateStaticUrl(
@PathVariable String branchedApplicationId, @RequestBody UniqueSlugDTO uniqueSlugDTO) {
return staticUrlService
.autoGenerateStaticUrl(branchedApplicationId, uniqueSlugDTO)
.map(url -> new ResponseDTO<>(HttpStatus.OK, url));
}

@JsonView(Views.Public.class)
@PatchMapping(value = "/{branchedApplicationId}/static-url")
public Mono<ResponseDTO<Application>> updateApplicationSlug(
@PathVariable String branchedApplicationId, @RequestBody UniqueSlugDTO staticUrlDTO) {
return staticUrlService
.updateApplicationSlug(branchedApplicationId, staticUrlDTO)
.map(url -> new ResponseDTO<>(HttpStatus.OK, url));
}

@JsonView(Views.Public.class)
@DeleteMapping(value = "/{branchedApplicationId}/static-url")
public Mono<ResponseDTO<Application>> deleteUnique(@PathVariable String branchedApplicationId) {
return staticUrlService
.deleteStaticUrlSettings(branchedApplicationId)
.map(url -> new ResponseDTO<>(HttpStatus.OK, url));
}

@JsonView(Views.Public.class)
@GetMapping(value = "/{branchedApplicationId}/static-url/{uniqueSlugName}")
public Mono<ResponseDTO<UniqueSlugDTO>> isApplicationSlugUnique(
@PathVariable String branchedApplicationId, @PathVariable String uniqueSlugName) {
return staticUrlService
.isApplicationSlugUnique(branchedApplicationId, uniqueSlugName)
.map(url -> new ResponseDTO<>(HttpStatus.OK, url));
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.appsmith.server.controllers.ce;

import com.appsmith.external.dtos.UniqueSlugDTO;
import com.appsmith.external.git.constants.ce.RefType;
import com.appsmith.external.views.Views;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.constants.Url;
import com.appsmith.server.domains.ApplicationMode;
import com.appsmith.server.domains.NewPage;
import com.appsmith.server.dtos.ApplicationPagesDTO;
import com.appsmith.server.dtos.CRUDPageResourceDTO;
import com.appsmith.server.dtos.CRUDPageResponseDTO;
Expand All @@ -15,6 +17,7 @@
import com.appsmith.server.newpages.base.NewPageService;
import com.appsmith.server.services.ApplicationPageService;
import com.appsmith.server.solutions.CreateDBTablePageSolution;
import com.appsmith.server.staticurl.StaticUrlService;
import com.fasterxml.jackson.annotation.JsonView;
import jakarta.validation.Valid;
import lombok.NonNull;
Expand All @@ -23,6 +26,7 @@
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
Expand All @@ -44,6 +48,7 @@ public class PageControllerCE {
private final ApplicationPageService applicationPageService;
private final NewPageService newPageService;
private final CreateDBTablePageSolution createDBTablePageSolution;
protected final StaticUrlService staticUrlService;

@JsonView(Views.Public.class)
@PostMapping
Expand Down Expand Up @@ -193,4 +198,19 @@ public Mono<ResponseDTO<String>> updateDependencyMap(
.updateDependencyMap(defaultPageId, dependencyMap, RefType.branch, branchName)
.map(updatedResource -> new ResponseDTO<>(HttpStatus.OK, updatedResource));
}

@JsonView(Views.Public.class)
@PatchMapping(value = "/static-url")
public Mono<ResponseDTO<NewPage>> updatePageSlug(@RequestBody UniqueSlugDTO uniqueSlugDTO) {
return staticUrlService.updatePageSlug(uniqueSlugDTO).map(url -> new ResponseDTO<>(HttpStatus.OK, url));
}

@JsonView(Views.Public.class)
@GetMapping(value = "/{branchedPageId}/static-url/verify/{requestedSlug}")
public Mono<ResponseDTO<UniqueSlugDTO>> isPageSlugUnique(
@PathVariable String branchedPageId, @PathVariable String requestedSlug) {
return staticUrlService
.isPageSlugUnique(branchedPageId, requestedSlug)
.map(url -> new ResponseDTO<>(HttpStatus.OK, url));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,21 @@ public AppPositioning(AppPositioning.Type type) {
}
}

/**
* StaticUrlSettings stores the static URL configuration for the application
*/
@Data
@NoArgsConstructor
@FieldNameConstants
@EqualsAndHashCode(callSuper = true)
public static class StaticUrlSettings extends ApplicationCE.StaticUrlSettingsCE {
public StaticUrlSettings(boolean enabled, String uniqueSlug) {
super(enabled, uniqueSlug);
}

public static class Fields extends ApplicationCE.StaticUrlSettingsCE.Fields {}
}

@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
Expand Down
Loading