hoàn thành phần tạo theme

This commit is contained in:
2026-07-02 18:30:12 +07:00
parent 986bd08e5f
commit e91e607d14
13 changed files with 313 additions and 3 deletions
@@ -0,0 +1,44 @@
package com.sisvietnamvn.web.controller;
import com.sisvietnamvn.web.service.SettingService;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import java.util.Map;
/**
* Provides global model attributes for all frontend controllers.
*/
@ControllerAdvice(basePackages = "com.sisvietnamvn.web.controller")
public class GlobalControllerAdvice {
private final SettingService settingService;
private final PathMatchingResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
public GlobalControllerAdvice(SettingService settingService) {
this.settingService = settingService;
}
/**
* Makes the 'activeTheme' variable available to all Thymeleaf templates handled by controllers in this package.
*/
@ModelAttribute("activeTheme")
public String getActiveTheme() {
Map<String, String> settings = settingService.getAllAsMap();
String activeTheme = settings.getOrDefault("active_theme", "default");
// Validate that the layout actually exists to prevent Thymeleaf TemplateInputException crashes
try {
Resource[] resources = resourceResolver.getResources("classpath*:templates/themes/" + activeTheme + "/layout.html");
if (resources == null || resources.length == 0) {
return "default"; // Fallback to safe layout
}
} catch (Exception e) {
return "default";
}
return activeTheme;
}
}
@@ -0,0 +1,98 @@
package com.sisvietnamvn.web.controller.manage;
import com.sisvietnamvn.web.service.SettingService;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.sisvietnamvn.web.security.AuthoritiesConstants;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Controller for the admin Themes page.
*/
@Controller
@RequestMapping("/manage/themes")
@PreAuthorize("hasAnyAuthority(\"" + AuthoritiesConstants.ADMIN + "\", \"" + AuthoritiesConstants.EDITOR + "\")")
public class ManageThemeController {
private final SettingService settingService;
private final PathMatchingResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
public ManageThemeController(SettingService settingService) {
this.settingService = settingService;
}
private List<String> getAvailableLayouts() {
List<String> layouts = new ArrayList<>();
try {
Resource[] resources = resourceResolver.getResources("classpath*:templates/themes/*/layout.html");
for (Resource res : resources) {
String path = res.getURL().toString();
java.util.regex.Matcher m = java.util.regex.Pattern.compile("themes/([^/]+)/layout\\.html$").matcher(path);
if (m.find()) {
layouts.add(m.group(1));
}
}
} catch (IOException e) {
// Ignore if directory doesn't exist
}
// Ensure we always have at least a fallback if empty
if (layouts.isEmpty()) {
layouts.add("default");
}
return layouts;
}
@GetMapping
public String listThemes(Model model) {
Map<String, String> settings = settingService.getAllAsMap();
String activeTheme = settings.getOrDefault("active_theme", "default");
model.addAttribute("activeTheme", activeTheme);
model.addAttribute("availableThemes", getAvailableLayouts());
return "manage/themes/list";
}
private boolean isThemeComplete(String themeKey) {
try {
Resource[] layouts = resourceResolver.getResources("classpath*:templates/themes/" + themeKey + "/layout.html");
Resource[] headers = resourceResolver.getResources("classpath*:templates/themes/" + themeKey + "/header.html");
Resource[] footers = resourceResolver.getResources("classpath*:templates/themes/" + themeKey + "/footer.html");
return layouts.length > 0 && headers.length > 0 && footers.length > 0;
} catch (Exception e) {
return false;
}
}
@PostMapping("/activate")
public String activateTheme(@RequestParam("themeKey") String themeKey, RedirectAttributes redirectAttributes) {
List<String> validThemes = getAvailableLayouts();
if (!validThemes.contains(themeKey)) {
redirectAttributes.addFlashAttribute("errorMessage", "Invalid theme selected.");
return "redirect:/manage/themes";
}
if (!isThemeComplete(themeKey)) {
redirectAttributes.addFlashAttribute("errorMessage", "Cannot activate theme: Missing required files (layout.html, header.html, or footer.html).");
return "redirect:/manage/themes";
}
settingService.setValue("active_theme", themeKey);
redirectAttributes.addFlashAttribute("successMessage", "New theme activated successfully.");
return "redirect:/manage/themes";
}
}