add drag and drop builder
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
package com.indie.controller;
|
||||
|
||||
import com.indie.config.UserPrincipal;
|
||||
import com.indie.dto.ComponentResponse;
|
||||
import com.indie.dto.PageWithComponentsResponse;
|
||||
import com.indie.dto.SaveComponentsRequest;
|
||||
import com.indie.model.Component;
|
||||
import com.indie.model.Page;
|
||||
import com.indie.model.Site;
|
||||
import com.indie.repository.ComponentRepository;
|
||||
import com.indie.repository.PageRepository;
|
||||
import com.indie.repository.SiteRepository;
|
||||
import com.indie.service.BuilderService;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/builder/sites/{siteId}")
|
||||
public class BuilderController {
|
||||
|
||||
private final SiteRepository siteRepository;
|
||||
private final PageRepository pageRepository;
|
||||
private final ComponentRepository componentRepository;
|
||||
private final BuilderService builderService;
|
||||
|
||||
public BuilderController(SiteRepository siteRepository, PageRepository pageRepository,
|
||||
ComponentRepository componentRepository, BuilderService builderService) {
|
||||
this.siteRepository = siteRepository;
|
||||
this.pageRepository = pageRepository;
|
||||
this.componentRepository = componentRepository;
|
||||
this.builderService = builderService;
|
||||
}
|
||||
|
||||
@GetMapping("/pages")
|
||||
public ResponseEntity<List<PageWithComponentsResponse>> listPagesWithComponents(
|
||||
@AuthenticationPrincipal UserPrincipal principal,
|
||||
@PathVariable UUID siteId) {
|
||||
Site site = findSiteForUser(siteId, principal.id());
|
||||
List<Page> pages = pageRepository.findBySiteIdOrderByOrderIndexAsc(site.getId());
|
||||
|
||||
List<PageWithComponentsResponse> response = pages.stream()
|
||||
.map(page -> {
|
||||
List<ComponentResponse> components = componentRepository
|
||||
.findByPageIdOrderByOrderIndexAsc(page.getId())
|
||||
.stream()
|
||||
.map(ComponentResponse::from)
|
||||
.toList();
|
||||
return PageWithComponentsResponse.from(page, components);
|
||||
})
|
||||
.toList();
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@PutMapping("/pages/{pageId}/components")
|
||||
public ResponseEntity<List<ComponentResponse>> saveComponents(
|
||||
@AuthenticationPrincipal UserPrincipal principal,
|
||||
@PathVariable UUID siteId,
|
||||
@PathVariable UUID pageId,
|
||||
@Valid @RequestBody SaveComponentsRequest request) {
|
||||
Site site = findSiteForUser(siteId, principal.id());
|
||||
Page page = findPageForSite(pageId, site.getId());
|
||||
|
||||
List<Component> components = builderService.saveComponents(page.getId(), request);
|
||||
|
||||
List<ComponentResponse> response = components.stream()
|
||||
.map(ComponentResponse::from)
|
||||
.toList();
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@GetMapping("/theme")
|
||||
public ResponseEntity<Map<String, Object>> getTheme(
|
||||
@AuthenticationPrincipal UserPrincipal principal,
|
||||
@PathVariable UUID siteId) {
|
||||
Site site = findSiteForUser(siteId, principal.id());
|
||||
return ResponseEntity.ok(site.getThemeConfig());
|
||||
}
|
||||
|
||||
@PutMapping("/theme")
|
||||
public ResponseEntity<Map<String, Object>> updateTheme(
|
||||
@AuthenticationPrincipal UserPrincipal principal,
|
||||
@PathVariable UUID siteId,
|
||||
@RequestBody Map<String, Object> themeConfig) {
|
||||
Site site = findSiteForUser(siteId, principal.id());
|
||||
site.setThemeConfig(themeConfig);
|
||||
siteRepository.save(site);
|
||||
return ResponseEntity.ok(site.getThemeConfig());
|
||||
}
|
||||
|
||||
private Site findSiteForUser(UUID siteId, UUID userId) {
|
||||
Site site = siteRepository.findById(siteId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Site not found"));
|
||||
if (!site.getUserId().equals(userId)) {
|
||||
throw new IllegalArgumentException("Site not found");
|
||||
}
|
||||
return site;
|
||||
}
|
||||
|
||||
private Page findPageForSite(UUID pageId, UUID siteId) {
|
||||
Page page = pageRepository.findById(pageId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Page not found"));
|
||||
if (!page.getSiteId().equals(siteId)) {
|
||||
throw new IllegalArgumentException("Page not found");
|
||||
}
|
||||
return page;
|
||||
}
|
||||
}
|
||||
34
backend/src/main/java/com/indie/dto/ComponentResponse.java
Normal file
34
backend/src/main/java/com/indie/dto/ComponentResponse.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package com.indie.dto;
|
||||
|
||||
import com.indie.model.Component;
|
||||
import com.indie.model.ComponentType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class ComponentResponse {
|
||||
|
||||
private UUID id;
|
||||
private UUID pageId;
|
||||
private ComponentType type;
|
||||
private Map<String, Object> config;
|
||||
private Map<String, Object> styles;
|
||||
private int orderIndex;
|
||||
|
||||
public static ComponentResponse from(Component component) {
|
||||
return ComponentResponse.builder()
|
||||
.id(component.getId())
|
||||
.pageId(component.getPageId())
|
||||
.type(component.getType())
|
||||
.config(component.getConfig())
|
||||
.styles(component.getStyles())
|
||||
.orderIndex(component.getOrderIndex())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.indie.dto;
|
||||
|
||||
import com.indie.model.Page;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class PageWithComponentsResponse {
|
||||
|
||||
private UUID id;
|
||||
private UUID siteId;
|
||||
private String title;
|
||||
private String slug;
|
||||
private String seoTitle;
|
||||
private String seoDescription;
|
||||
private int orderIndex;
|
||||
private LocalDateTime createdAt;
|
||||
private List<ComponentResponse> components;
|
||||
|
||||
public static PageWithComponentsResponse from(Page page, List<ComponentResponse> components) {
|
||||
return PageWithComponentsResponse.builder()
|
||||
.id(page.getId())
|
||||
.siteId(page.getSiteId())
|
||||
.title(page.getTitle())
|
||||
.slug(page.getSlug())
|
||||
.seoTitle(page.getSeoTitle())
|
||||
.seoDescription(page.getSeoDescription())
|
||||
.orderIndex(page.getOrderIndex())
|
||||
.createdAt(page.getCreatedAt())
|
||||
.components(components)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.indie.dto;
|
||||
|
||||
import com.indie.model.ComponentType;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
public class SaveComponentsRequest {
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
private List<ComponentDTO> components;
|
||||
|
||||
@Data
|
||||
public static class ComponentDTO {
|
||||
private UUID id;
|
||||
@NotNull
|
||||
private ComponentType type;
|
||||
private Map<String, Object> config;
|
||||
private Map<String, Object> styles;
|
||||
private int orderIndex;
|
||||
}
|
||||
}
|
||||
55
backend/src/main/java/com/indie/service/BuilderService.java
Normal file
55
backend/src/main/java/com/indie/service/BuilderService.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package com.indie.service;
|
||||
|
||||
import com.indie.dto.SaveComponentsRequest;
|
||||
import com.indie.model.Component;
|
||||
import com.indie.repository.ComponentRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class BuilderService {
|
||||
|
||||
private final ComponentRepository componentRepository;
|
||||
|
||||
static final int MAX_COMPONENTS_PER_PAGE = 50;
|
||||
|
||||
public void validateComponents(SaveComponentsRequest request) {
|
||||
if (request.getComponents() == null || request.getComponents().isEmpty()) {
|
||||
throw new IllegalArgumentException("Component list must not be empty");
|
||||
}
|
||||
if (request.getComponents().size() > MAX_COMPONENTS_PER_PAGE) {
|
||||
throw new IllegalArgumentException("Maximum " + MAX_COMPONENTS_PER_PAGE + " components allowed per page");
|
||||
}
|
||||
|
||||
for (SaveComponentsRequest.ComponentDTO dto : request.getComponents()) {
|
||||
if (dto.getType() == null) {
|
||||
throw new IllegalArgumentException("Component type is required");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public List<Component> saveComponents(UUID pageId, SaveComponentsRequest request) {
|
||||
validateComponents(request);
|
||||
|
||||
componentRepository.deleteByPageId(pageId);
|
||||
|
||||
List<Component> components = request.getComponents().stream()
|
||||
.map(dto -> Component.builder()
|
||||
.id(dto.getId() != null ? dto.getId() : UUID.randomUUID())
|
||||
.pageId(pageId)
|
||||
.type(dto.getType())
|
||||
.config(dto.getConfig())
|
||||
.styles(dto.getStyles())
|
||||
.orderIndex(dto.getOrderIndex())
|
||||
.build())
|
||||
.toList();
|
||||
|
||||
return componentRepository.saveAll(components);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user