feat: add UMass Amherst theme support, implement HTML snippet management system, and update page rendering logic
This commit is contained in:
+1
-1
@@ -57,7 +57,7 @@ public class SecurityConfiguration {
|
||||
.requestMatchers(HttpMethod.GET, "/", "/about", "/flex-finish", "/tin-tuc", "/tin-tuc/**", "/lien-he",
|
||||
"/manage/login", "/css/**", "/images/**", "/js/**", "/vendor/**", "/fonts/**", "/login-assets/**", "/UMass*/**", "/Undergraduate*/**",
|
||||
"/favicon.ico", "/favicons/**", "/flex-finish/**", "/contact-us/**", "/uploads/**", "/api/manage/snippets/**", "/page/**", "/news/article/**", "/post/**", "/error",
|
||||
"/about-us", "/specialty", "/doctor", "/service", "/health-library", "/news-and-events", "/patient-support", "/medical-expert", "/umcers", "/bidding", "/contact-us")
|
||||
"/about-us", "/specialty", "/doctor", "/service", "/health-library", "/news-and-events", "/patient-support", "/medical-expert", "/umcers", "/bidding", "/contact-us", "/theme-assets/**")
|
||||
.permitAll()
|
||||
.requestMatchers(HttpMethod.POST, "/api/manage/media/upload").permitAll()
|
||||
.requestMatchers(HttpMethod.GET, "/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
||||
|
||||
+27
-1
@@ -17,12 +17,18 @@ public class GlobalControllerAdvice {
|
||||
private final SettingService settingService;
|
||||
private final com.sisvietnamvn.web.service.MenuService menuService;
|
||||
private final com.sisvietnamvn.web.hook.HookManager hookManager;
|
||||
private final com.sisvietnamvn.web.security.AdminContext adminContext;
|
||||
private final com.sisvietnamvn.web.security.AdminMenuManager adminMenuManager;
|
||||
private final com.sisvietnamvn.web.security.AdminSettingsManager adminSettingsManager;
|
||||
private final PathMatchingResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
|
||||
|
||||
public GlobalControllerAdvice(SettingService settingService, com.sisvietnamvn.web.service.MenuService menuService, com.sisvietnamvn.web.hook.HookManager hookManager) {
|
||||
public GlobalControllerAdvice(SettingService settingService, com.sisvietnamvn.web.service.MenuService menuService, com.sisvietnamvn.web.hook.HookManager hookManager, com.sisvietnamvn.web.security.AdminContext adminContext, com.sisvietnamvn.web.security.AdminMenuManager adminMenuManager, com.sisvietnamvn.web.security.AdminSettingsManager adminSettingsManager) {
|
||||
this.settingService = settingService;
|
||||
this.menuService = menuService;
|
||||
this.hookManager = hookManager;
|
||||
this.adminContext = adminContext;
|
||||
this.adminMenuManager = adminMenuManager;
|
||||
this.adminSettingsManager = adminSettingsManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,6 +77,26 @@ public class GlobalControllerAdvice {
|
||||
return hookManager;
|
||||
}
|
||||
|
||||
@ModelAttribute("adminCtx")
|
||||
public com.sisvietnamvn.web.security.AdminContext getAdminContext() {
|
||||
return adminContext;
|
||||
}
|
||||
|
||||
@ModelAttribute("adminScreen")
|
||||
public com.sisvietnamvn.web.security.AdminScreen getAdminScreen() {
|
||||
return adminContext.getCurrentScreen();
|
||||
}
|
||||
|
||||
@ModelAttribute("dynamicAdminMenus")
|
||||
public java.util.List<com.sisvietnamvn.web.security.AdminMenuItem> getDynamicAdminMenus() {
|
||||
return adminMenuManager.getAuthorizedMenus();
|
||||
}
|
||||
|
||||
@ModelAttribute("settingsManager")
|
||||
public com.sisvietnamvn.web.security.AdminSettingsManager getSettingsManager() {
|
||||
return adminSettingsManager;
|
||||
}
|
||||
|
||||
@ModelAttribute("themeModPrimaryColor")
|
||||
public String getThemeModPrimaryColor() {
|
||||
return settingService.getValue("theme_mod_primaryColor", "#007bff");
|
||||
|
||||
+12
-1
@@ -33,11 +33,13 @@ public class PageController {
|
||||
private final PageService pageService;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final HookManager hookManager;
|
||||
private final com.sisvietnamvn.web.service.HtmlSnippetService snippetService;
|
||||
|
||||
public PageController(PageService pageService, ObjectMapper objectMapper, HookManager hookManager) {
|
||||
public PageController(PageService pageService, ObjectMapper objectMapper, HookManager hookManager, com.sisvietnamvn.web.service.HtmlSnippetService snippetService) {
|
||||
this.pageService = pageService;
|
||||
this.objectMapper = objectMapper;
|
||||
this.hookManager = hookManager;
|
||||
this.snippetService = snippetService;
|
||||
}
|
||||
|
||||
@GetMapping("/page/{slug}")
|
||||
@@ -109,6 +111,15 @@ public class PageController {
|
||||
Map<String, Object> editorData = objectMapper.readValue(page.getContent(), new TypeReference<>() {});
|
||||
if (editorData.containsKey("blocks")) {
|
||||
blocks = (List<Map<String, Object>>) editorData.get("blocks");
|
||||
for (Map<String, Object> block : blocks) {
|
||||
if ("snippet".equals(block.get("type"))) {
|
||||
Map<String, Object> data = (Map<String, Object>) block.get("data");
|
||||
if (data != null && data.containsKey("id")) {
|
||||
String snippetId = (String) data.get("id");
|
||||
data.put("htmlContent", snippetService.getSnippetContent(snippetId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (JsonProcessingException e) {
|
||||
LOG.error("Failed to parse Editor.js JSON for page ID: {}", page.getId(), e);
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package com.sisvietnamvn.web.controller.manage;
|
||||
|
||||
import com.sisvietnamvn.web.domain.HtmlSnippet;
|
||||
import com.sisvietnamvn.web.service.HtmlSnippetService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/manage/snippets")
|
||||
public class ManageSnippetController {
|
||||
|
||||
private final HtmlSnippetService snippetService;
|
||||
|
||||
public ManageSnippetController(HtmlSnippetService snippetService) {
|
||||
this.snippetService = snippetService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public String listSnippets(Model model) {
|
||||
model.addAttribute("snippets", snippetService.findAll());
|
||||
return "manage/snippets/list";
|
||||
}
|
||||
|
||||
@GetMapping("/new")
|
||||
public String newSnippetForm(Model model) {
|
||||
model.addAttribute("snippet", new HtmlSnippet());
|
||||
model.addAttribute("isNew", true);
|
||||
return "manage/snippets/form";
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
public String createSnippet(@ModelAttribute HtmlSnippet snippet, RedirectAttributes redirectAttributes) {
|
||||
snippetService.save(snippet);
|
||||
redirectAttributes.addFlashAttribute("successMessage", "Snippet created successfully.");
|
||||
return "redirect:/manage/snippets";
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/edit")
|
||||
public String editSnippetForm(@PathVariable Long id, Model model, RedirectAttributes redirectAttributes) {
|
||||
return snippetService.findById(id).map(snippet -> {
|
||||
model.addAttribute("snippet", snippet);
|
||||
model.addAttribute("isNew", false);
|
||||
return "manage/snippets/form";
|
||||
}).orElseGet(() -> {
|
||||
redirectAttributes.addFlashAttribute("errorMessage", "Snippet not found.");
|
||||
return "redirect:/manage/snippets";
|
||||
});
|
||||
}
|
||||
|
||||
@PostMapping("/{id}")
|
||||
public String updateSnippet(@PathVariable Long id, @ModelAttribute HtmlSnippet snippet, RedirectAttributes redirectAttributes) {
|
||||
snippet.setId(id);
|
||||
snippetService.save(snippet);
|
||||
redirectAttributes.addFlashAttribute("successMessage", "Snippet updated successfully.");
|
||||
return "redirect:/manage/snippets";
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/delete")
|
||||
public String deleteSnippet(@PathVariable Long id, RedirectAttributes redirectAttributes) {
|
||||
snippetService.deleteById(id);
|
||||
redirectAttributes.addFlashAttribute("successMessage", "Snippet deleted successfully.");
|
||||
return "redirect:/manage/snippets";
|
||||
}
|
||||
}
|
||||
+18
@@ -106,9 +106,17 @@ public class ManageThemeController {
|
||||
|
||||
try {
|
||||
java.nio.file.Path themePath = java.nio.file.Paths.get("src/main/resources/templates/themes/", themeKey);
|
||||
java.nio.file.Path buildThemePath = java.nio.file.Paths.get("build/resources/main/templates/themes/", themeKey);
|
||||
|
||||
if (java.nio.file.Files.exists(themePath)) {
|
||||
// Delete directory recursively
|
||||
org.springframework.util.FileSystemUtils.deleteRecursively(themePath);
|
||||
|
||||
// Also delete from Gradle build cache if it exists
|
||||
if (java.nio.file.Files.exists(buildThemePath)) {
|
||||
org.springframework.util.FileSystemUtils.deleteRecursively(buildThemePath);
|
||||
}
|
||||
|
||||
redirectAttributes.addFlashAttribute("successMessage", "Theme deleted successfully.");
|
||||
} else {
|
||||
redirectAttributes.addFlashAttribute("errorMessage", "Theme folder not found.");
|
||||
@@ -128,6 +136,7 @@ public class ManageThemeController {
|
||||
|
||||
try {
|
||||
java.nio.file.Path targetDir = java.nio.file.Paths.get("src/main/resources/templates/themes/");
|
||||
java.nio.file.Path buildDir = java.nio.file.Paths.get("build/resources/main/templates/themes/");
|
||||
java.util.zip.ZipInputStream zis = new java.util.zip.ZipInputStream(file.getInputStream());
|
||||
java.util.zip.ZipEntry zipEntry = zis.getNextEntry();
|
||||
|
||||
@@ -142,6 +151,15 @@ public class ManageThemeController {
|
||||
}
|
||||
}
|
||||
java.nio.file.Files.copy(zis, newPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
// Also copy to Gradle build cache if it exists, so changes appear without restarting server
|
||||
if (java.nio.file.Files.exists(java.nio.file.Paths.get("build/resources/main"))) {
|
||||
java.nio.file.Path buildNewPath = zipSlipProtect(zipEntry, buildDir);
|
||||
if (buildNewPath.getParent() != null && java.nio.file.Files.notExists(buildNewPath.getParent())) {
|
||||
java.nio.file.Files.createDirectories(buildNewPath.getParent());
|
||||
}
|
||||
java.nio.file.Files.copy(newPath, buildNewPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
zipEntry = zis.getNextEntry();
|
||||
}
|
||||
|
||||
+10
-25
@@ -2,16 +2,13 @@ package com.sisvietnamvn.web.controller.manage;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import com.sisvietnamvn.web.security.AuthoritiesConstants;
|
||||
import com.sisvietnamvn.web.service.HtmlSnippetService;
|
||||
|
||||
/**
|
||||
* REST controller for fetching predefined HTML snippets to be previewed in Editor.js.
|
||||
@@ -22,10 +19,10 @@ import com.sisvietnamvn.web.security.AuthoritiesConstants;
|
||||
public class SnippetController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SnippetController.class);
|
||||
private final ResourceLoader resourceLoader;
|
||||
private final HtmlSnippetService snippetService;
|
||||
|
||||
public SnippetController(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
public SnippetController(HtmlSnippetService snippetService) {
|
||||
this.snippetService = snippetService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,25 +32,13 @@ public class SnippetController {
|
||||
public ResponseEntity<String> getSnippet(@PathVariable("id") String id) {
|
||||
LOG.debug("REST request to get Snippet : {}", id);
|
||||
|
||||
// Prevent path traversal attacks
|
||||
if (id == null || id.contains("..") || id.contains("/")) {
|
||||
return ResponseEntity.badRequest().body("Invalid Snippet ID");
|
||||
String content = snippetService.getSnippetContent(id);
|
||||
|
||||
if (content == null || content.isEmpty()) {
|
||||
LOG.warn("Snippet not found or is inactive: {}", id);
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("<div style=\"padding: 20px; border: 1px dashed red; color: red;\">Snippet ID <strong>" + id + "</strong> not found or inactive.</div>");
|
||||
}
|
||||
|
||||
String path = "classpath:templates/snippets/" + id + ".html";
|
||||
Resource resource = resourceLoader.getResource(path);
|
||||
|
||||
if (!resource.exists() || !resource.isReadable()) {
|
||||
LOG.warn("Snippet not found at path: {}", path);
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("<div style=\"padding: 20px; border: 1px dashed red; color: red;\">Snippet ID <strong>" + id + "</strong> not found.</div>");
|
||||
}
|
||||
|
||||
try {
|
||||
String content = new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
|
||||
return ResponseEntity.ok().body(content);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Error reading snippet file: {}", path, e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error reading snippet file");
|
||||
}
|
||||
return ResponseEntity.ok().body(content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.sisvietnamvn.web.domain;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
|
||||
/**
|
||||
* Entity representing a reusable HTML Snippet.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "html_snippet")
|
||||
public class HtmlSnippet extends AbstractAuditingEntity<Long> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(unique = true, nullable = false)
|
||||
private String slug;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String content;
|
||||
|
||||
private boolean active = true;
|
||||
|
||||
// Getters and Setters
|
||||
|
||||
@Override
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getSlug() {
|
||||
return slug;
|
||||
}
|
||||
|
||||
public void setSlug(String slug) {
|
||||
this.slug = slug;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.sisvietnamvn.web.repository;
|
||||
|
||||
import com.sisvietnamvn.web.domain.HtmlSnippet;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Spring Data JPA repository for the HtmlSnippet entity.
|
||||
*/
|
||||
@Repository
|
||||
public interface HtmlSnippetRepository extends JpaRepository<HtmlSnippet, Long> {
|
||||
|
||||
Optional<HtmlSnippet> findBySlug(String slug);
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package com.sisvietnamvn.web.security;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Utility for managing the Admin context, providing equivalents to WordPress's
|
||||
* is_admin(), current_user_can(), and get_current_screen().
|
||||
*/
|
||||
@Service
|
||||
public class AdminContext {
|
||||
|
||||
private static final String MANAGE_PREFIX = "/manage";
|
||||
|
||||
// Regex for parsing /manage/{base}/{id}/{section}
|
||||
private static final Pattern SCREEN_PATTERN = Pattern.compile("^/manage/([^/]+)(?:/(\\d+))?(?:/([^/]+))?/?$");
|
||||
|
||||
/**
|
||||
* Map of WordPress capabilities to Spring Boot roles/authorities.
|
||||
*/
|
||||
private static final Map<String, String> CAPABILITY_TO_ROLE = Map.ofEntries(
|
||||
Map.entry("manage_options", AuthoritiesConstants.ADMIN),
|
||||
Map.entry("edit_themes", AuthoritiesConstants.ADMIN),
|
||||
Map.entry("install_plugins", AuthoritiesConstants.ADMIN),
|
||||
Map.entry("edit_users", AuthoritiesConstants.ADMIN),
|
||||
|
||||
Map.entry("edit_others_posts", AuthoritiesConstants.EDITOR),
|
||||
Map.entry("manage_categories", AuthoritiesConstants.EDITOR),
|
||||
Map.entry("moderate_comments", AuthoritiesConstants.EDITOR),
|
||||
Map.entry("edit_pages", AuthoritiesConstants.EDITOR),
|
||||
Map.entry("delete_posts", AuthoritiesConstants.EDITOR),
|
||||
|
||||
Map.entry("publish_posts", AuthoritiesConstants.AUTHOR),
|
||||
Map.entry("upload_files", AuthoritiesConstants.AUTHOR),
|
||||
|
||||
Map.entry("edit_posts", AuthoritiesConstants.CONTRIBUTOR),
|
||||
|
||||
Map.entry("read", AuthoritiesConstants.SUBSCRIBER)
|
||||
);
|
||||
|
||||
/**
|
||||
* Equivalents to WordPress's is_admin().
|
||||
* Checks if the request is for an admin page (/manage).
|
||||
*/
|
||||
public static boolean isAdmin(HttpServletRequest request) {
|
||||
if (request == null) {
|
||||
return false;
|
||||
}
|
||||
String requestUri = request.getRequestURI();
|
||||
return requestUri != null && requestUri.startsWith(MANAGE_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instance method for Thymeleaf usage without passing the request.
|
||||
*/
|
||||
public boolean isAdminRequest() {
|
||||
HttpServletRequest request = getCurrentHttpRequest();
|
||||
return isAdmin(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalents to WordPress's current_user_can().
|
||||
* Checks if the current user has the specified capability or role.
|
||||
*/
|
||||
public boolean currentUserCan(String capability) {
|
||||
if (capability == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If capability maps to a known Spring Boot role, check that role
|
||||
if (CAPABILITY_TO_ROLE.containsKey(capability)) {
|
||||
return SecurityUtils.hasCurrentUserThisAuthority(CAPABILITY_TO_ROLE.get(capability));
|
||||
}
|
||||
|
||||
// Otherwise, assume the capability string itself is the authority (e.g., "ROLE_ADMIN")
|
||||
return SecurityUtils.hasCurrentUserThisAuthority(capability);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalents to WordPress's get_current_screen().
|
||||
* Parses the current request URI to determine the admin screen context.
|
||||
*/
|
||||
public AdminScreen getCurrentScreen(HttpServletRequest request) {
|
||||
if (!isAdmin(request)) {
|
||||
return null; // Not an admin screen
|
||||
}
|
||||
|
||||
String requestUri = request.getRequestURI();
|
||||
|
||||
// Special case for root dashboard
|
||||
if (MANAGE_PREFIX.equals(requestUri) || (MANAGE_PREFIX + "/").equals(requestUri)) {
|
||||
return new AdminScreen("manage-dashboard", "dashboard", "index", null);
|
||||
}
|
||||
|
||||
Matcher matcher = SCREEN_PATTERN.matcher(requestUri);
|
||||
if (matcher.find()) {
|
||||
String base = matcher.group(1);
|
||||
String idStr = matcher.group(2);
|
||||
String sectionStr = matcher.group(3);
|
||||
|
||||
String id = "manage-" + base;
|
||||
Long entityId = null;
|
||||
if (idStr != null) {
|
||||
try {
|
||||
entityId = Long.parseLong(idStr);
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
String section = "list"; // Default for /manage/posts
|
||||
if (sectionStr != null) {
|
||||
section = sectionStr; // e.g., "edit" or "new"
|
||||
} else if (idStr == null && requestUri.endsWith("/new")) {
|
||||
// Handling /manage/posts/new which might not perfectly match the numeric id pattern
|
||||
section = "new";
|
||||
}
|
||||
|
||||
return new AdminScreen(id, base, section, entityId);
|
||||
}
|
||||
|
||||
// Fallback for unknown /manage/xyz paths
|
||||
return new AdminScreen("manage-unknown", "unknown", "index", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instance method for Thymeleaf usage without passing the request.
|
||||
*/
|
||||
public AdminScreen getCurrentScreen() {
|
||||
HttpServletRequest request = getCurrentHttpRequest();
|
||||
return getCurrentScreen(request);
|
||||
}
|
||||
|
||||
private HttpServletRequest getCurrentHttpRequest() {
|
||||
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
return (attrs != null) ? attrs.getRequest() : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.sisvietnamvn.web.security;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a menu item in the WordPress-style admin sidebar.
|
||||
*/
|
||||
public class AdminMenuItem implements Comparable<AdminMenuItem> {
|
||||
|
||||
private String menuTitle;
|
||||
private String capability;
|
||||
private String menuSlug;
|
||||
private String iconUrl;
|
||||
private int position;
|
||||
private String url;
|
||||
private List<AdminMenuItem> submenus;
|
||||
|
||||
public AdminMenuItem(String menuTitle, String capability, String menuSlug, String iconUrl, int position, String url) {
|
||||
this.menuTitle = menuTitle;
|
||||
this.capability = capability;
|
||||
this.menuSlug = menuSlug;
|
||||
this.iconUrl = iconUrl;
|
||||
this.position = position;
|
||||
this.url = url;
|
||||
this.submenus = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void addSubmenu(AdminMenuItem submenu) {
|
||||
this.submenus.add(submenu);
|
||||
this.submenus.sort(null); // Sort by position
|
||||
}
|
||||
|
||||
public String getMenuTitle() {
|
||||
return menuTitle;
|
||||
}
|
||||
|
||||
public void setMenuTitle(String menuTitle) {
|
||||
this.menuTitle = menuTitle;
|
||||
}
|
||||
|
||||
public String getCapability() {
|
||||
return capability;
|
||||
}
|
||||
|
||||
public void setCapability(String capability) {
|
||||
this.capability = capability;
|
||||
}
|
||||
|
||||
public String getMenuSlug() {
|
||||
return menuSlug;
|
||||
}
|
||||
|
||||
public void setMenuSlug(String menuSlug) {
|
||||
this.menuSlug = menuSlug;
|
||||
}
|
||||
|
||||
public String getIconUrl() {
|
||||
return iconUrl;
|
||||
}
|
||||
|
||||
public void setIconUrl(String iconUrl) {
|
||||
this.iconUrl = iconUrl;
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(int position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public List<AdminMenuItem> getSubmenus() {
|
||||
return submenus;
|
||||
}
|
||||
|
||||
public void setSubmenus(List<AdminMenuItem> submenus) {
|
||||
this.submenus = submenus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(AdminMenuItem o) {
|
||||
return Integer.compare(this.position, o.position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.sisvietnamvn.web.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Service for managing dynamic admin menus, equivalent to WordPress's
|
||||
* add_menu_page(), add_submenu_page(), etc.
|
||||
*/
|
||||
@Service
|
||||
public class AdminMenuManager {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AdminMenuManager.class);
|
||||
|
||||
private final List<AdminMenuItem> menuRegistry = new ArrayList<>();
|
||||
private final AdminContext adminContext;
|
||||
|
||||
public AdminMenuManager(AdminContext adminContext) {
|
||||
this.adminContext = adminContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to add_menu_page()
|
||||
*/
|
||||
public void addMenuPage(String pageTitle, String menuTitle, String capability, String menuSlug, String iconUrl, int position) {
|
||||
// Find if exists to avoid duplicates
|
||||
if (getMenuBySlug(menuSlug).isPresent()) {
|
||||
LOG.warn("Menu slug '{}' is already registered.", menuSlug);
|
||||
return;
|
||||
}
|
||||
|
||||
String url = "/manage/" + menuSlug;
|
||||
AdminMenuItem item = new AdminMenuItem(menuTitle, capability, menuSlug, iconUrl, position, url);
|
||||
|
||||
// In WordPress, the main menu also acts as the first submenu.
|
||||
// We will add it as a submenu with the same URL, but empty title to skip rendering duplicate if needed,
|
||||
// or just let the UI handle the top link. For SB Admin 2, top level is just a dropdown trigger.
|
||||
// We will add the main link as the first submenu item.
|
||||
item.addSubmenu(new AdminMenuItem(pageTitle, capability, menuSlug, iconUrl, 0, url));
|
||||
|
||||
menuRegistry.add(item);
|
||||
Collections.sort(menuRegistry);
|
||||
LOG.debug("Added admin menu: {}", menuSlug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to add_submenu_page()
|
||||
*/
|
||||
public void addSubmenuPage(String parentSlug, String pageTitle, String menuTitle, String capability, String menuSlug, int position) {
|
||||
Optional<AdminMenuItem> parentOpt = getMenuBySlug(parentSlug);
|
||||
if (parentOpt.isPresent()) {
|
||||
String url = "/manage/" + menuSlug;
|
||||
// Some special WP cases like manage-settings actually map to /manage/settings/...
|
||||
if (menuSlug.contains("/")) {
|
||||
url = "/manage/" + menuSlug;
|
||||
} else if (parentSlug.startsWith("manage-")) {
|
||||
String base = parentSlug.replace("manage-", "");
|
||||
url = "/manage/" + base + "/" + menuSlug;
|
||||
}
|
||||
|
||||
AdminMenuItem subItem = new AdminMenuItem(menuTitle, capability, menuSlug, "", position, url);
|
||||
parentOpt.get().addSubmenu(subItem);
|
||||
LOG.debug("Added admin submenu: {} to parent: {}", menuSlug, parentSlug);
|
||||
} else {
|
||||
LOG.warn("Cannot add submenu '{}'. Parent menu '{}' not found.", menuSlug, parentSlug);
|
||||
}
|
||||
}
|
||||
|
||||
public void addSubmenuPage(String parentSlug, String pageTitle, String menuTitle, String capability, String menuSlug) {
|
||||
addSubmenuPage(parentSlug, pageTitle, menuTitle, capability, menuSlug, 10);
|
||||
}
|
||||
|
||||
// --- Helper Functions ---
|
||||
|
||||
public void addOptionsPage(String pageTitle, String menuTitle, String capability, String menuSlug) {
|
||||
addSubmenuPage("manage-settings", pageTitle, menuTitle, capability, "settings/" + menuSlug);
|
||||
}
|
||||
|
||||
public void addThemePage(String pageTitle, String menuTitle, String capability, String menuSlug) {
|
||||
addSubmenuPage("manage-themes", pageTitle, menuTitle, capability, menuSlug);
|
||||
}
|
||||
|
||||
public void addPluginsPage(String pageTitle, String menuTitle, String capability, String menuSlug) {
|
||||
addSubmenuPage("manage-plugins", pageTitle, menuTitle, capability, menuSlug);
|
||||
}
|
||||
|
||||
public void addUsersPage(String pageTitle, String menuTitle, String capability, String menuSlug) {
|
||||
addSubmenuPage("manage-users", pageTitle, menuTitle, capability, menuSlug);
|
||||
}
|
||||
|
||||
public void addDashboardPage(String pageTitle, String menuTitle, String capability, String menuSlug) {
|
||||
addSubmenuPage("manage-dashboard", pageTitle, menuTitle, capability, menuSlug);
|
||||
}
|
||||
|
||||
public void addManagementPage(String pageTitle, String menuTitle, String capability, String menuSlug) {
|
||||
addSubmenuPage("manage-tools", pageTitle, menuTitle, capability, "tools/" + menuSlug);
|
||||
}
|
||||
|
||||
// --- Internal & Rendering Helpers ---
|
||||
|
||||
private Optional<AdminMenuItem> getMenuBySlug(String slug) {
|
||||
return menuRegistry.stream().filter(m -> m.getMenuSlug().equals(slug)).findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all menus that the current user has access to.
|
||||
*/
|
||||
public List<AdminMenuItem> getAuthorizedMenus() {
|
||||
return menuRegistry.stream()
|
||||
.filter(menu -> adminContext.currentUserCan(menu.getCapability()))
|
||||
.map(this::filterAuthorizedSubmenus)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private AdminMenuItem filterAuthorizedSubmenus(AdminMenuItem menu) {
|
||||
AdminMenuItem filteredMenu = new AdminMenuItem(
|
||||
menu.getMenuTitle(), menu.getCapability(), menu.getMenuSlug(),
|
||||
menu.getIconUrl(), menu.getPosition(), menu.getUrl()
|
||||
);
|
||||
|
||||
List<AdminMenuItem> authorizedSubmenus = menu.getSubmenus().stream()
|
||||
.filter(sub -> adminContext.currentUserCan(sub.getCapability()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
filteredMenu.setSubmenus(authorizedSubmenus);
|
||||
return filteredMenu;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.sisvietnamvn.web.security;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents the current admin screen context parsed from the request URI.
|
||||
* Equivalent to WordPress's WP_Screen object (returned by get_current_screen()).
|
||||
*/
|
||||
public record AdminScreen(
|
||||
String id, // e.g., "manage-posts"
|
||||
String base, // e.g., "posts", "dashboard", "media"
|
||||
String section, // e.g., "list", "edit", "new", "index"
|
||||
Long entityId // e.g., 42 (if editing entity with ID 42)
|
||||
) {
|
||||
|
||||
/**
|
||||
* Checks if the screen matches a specific ID.
|
||||
*/
|
||||
public boolean is(String screenId) {
|
||||
return Objects.equals(this.id, screenId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the screen belongs to a specific base function.
|
||||
*/
|
||||
public boolean isBase(String baseName) {
|
||||
return Objects.equals(this.base, baseName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this is an editing screen.
|
||||
*/
|
||||
public boolean isEditing() {
|
||||
return "edit".equals(this.section);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this is a creation screen.
|
||||
*/
|
||||
public boolean isCreating() {
|
||||
return "new".equals(this.section);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this is a listing screen.
|
||||
*/
|
||||
public boolean isList() {
|
||||
return "list".equals(this.section);
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package com.sisvietnamvn.web.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.sisvietnamvn.web.service.SettingService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Service for managing the Settings API, equivalent to WordPress's
|
||||
* register_setting(), add_settings_section(), add_settings_field().
|
||||
*/
|
||||
@Service
|
||||
public class AdminSettingsManager {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AdminSettingsManager.class);
|
||||
|
||||
// Map: Page Slug -> List of Sections
|
||||
private final Map<String, List<SettingsSection>> registry = new HashMap<>();
|
||||
private final SettingService settingService;
|
||||
|
||||
public AdminSettingsManager(SettingService settingService) {
|
||||
this.settingService = settingService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to add_settings_section()
|
||||
*/
|
||||
public void addSettingsSection(String page, String sectionId, String title, String description) {
|
||||
registry.putIfAbsent(page, new ArrayList<>());
|
||||
|
||||
List<SettingsSection> sections = registry.get(page);
|
||||
|
||||
// Prevent duplicate sections
|
||||
boolean exists = sections.stream().anyMatch(s -> s.getId().equals(sectionId));
|
||||
if (!exists) {
|
||||
sections.add(new SettingsSection(sectionId, title, description));
|
||||
LOG.debug("Added settings section '{}' to page '{}'", sectionId, page);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to add_settings_field()
|
||||
*/
|
||||
public void addSettingsField(String page, String sectionId, SettingsField field) {
|
||||
List<SettingsSection> sections = registry.get(page);
|
||||
if (sections != null) {
|
||||
sections.stream()
|
||||
.filter(s -> s.getId().equals(sectionId))
|
||||
.findFirst()
|
||||
.ifPresent(section -> {
|
||||
section.addField(field);
|
||||
LOG.debug("Added settings field '{}' to section '{}' on page '{}'", field.getId(), sectionId, page);
|
||||
});
|
||||
} else {
|
||||
LOG.warn("Cannot add field '{}'. Page '{}' or Section '{}' not found.", field.getId(), page, sectionId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to register_setting()
|
||||
* In Spring, settingService handles generic keys automatically, but this ensures a default value exists.
|
||||
*/
|
||||
public void registerSetting(String optionGroup, String optionName, String defaultValue) {
|
||||
String existingValue = settingService.getValue(optionName, null);
|
||||
if (existingValue == null && defaultValue != null) {
|
||||
settingService.setValue(optionName, defaultValue);
|
||||
LOG.debug("Registered setting '{}' with default value '{}'", optionName, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for Thymeleaf to get all sections for a specific page.
|
||||
*/
|
||||
public List<SettingsSection> getSectionsForPage(String page) {
|
||||
return registry.getOrDefault(page, new ArrayList<>());
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package com.sisvietnamvn.web.security;
|
||||
|
||||
import com.sisvietnamvn.web.security.AdminMenuManager;
|
||||
import com.sisvietnamvn.web.security.AdminSettingsManager;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Registers core WordPress-like settings using the dynamic Settings API on startup.
|
||||
*/
|
||||
@Component
|
||||
public class CoreSettingsRegistrar {
|
||||
|
||||
private final AdminSettingsManager settingsManager;
|
||||
private final AdminMenuManager menuManager;
|
||||
|
||||
public CoreSettingsRegistrar(AdminSettingsManager settingsManager, AdminMenuManager menuManager) {
|
||||
this.settingsManager = settingsManager;
|
||||
this.menuManager = menuManager;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void initCoreSettings() {
|
||||
// --- General Settings ---
|
||||
String page = "general";
|
||||
|
||||
settingsManager.addSettingsSection(page, "default", "", "");
|
||||
|
||||
settingsManager.addSettingsField(page, "default",
|
||||
new SettingsField("site_name", "Site Title", "text", null, ""));
|
||||
|
||||
settingsManager.addSettingsField(page, "default",
|
||||
new SettingsField("tagline", "Tagline", "text", null, "In a few words, explain what this site is about."));
|
||||
|
||||
settingsManager.addSettingsField(page, "default",
|
||||
new SettingsField("admin_email", "Administration Email Address", "email", null, "This address is used for admin purposes."));
|
||||
|
||||
Map<String, String> timezones = new LinkedHashMap<>();
|
||||
timezones.put("UTC", "UTC");
|
||||
timezones.put("Asia/Ho_Chi_Minh", "Asia/Ho Chi Minh");
|
||||
timezones.put("America/New_York", "America/New York");
|
||||
timezones.put("Europe/London", "Europe/London");
|
||||
settingsManager.addSettingsField(page, "default",
|
||||
new SettingsField("timezone", "Timezone", "select", timezones, ""));
|
||||
|
||||
Map<String, String> dateFormats = new LinkedHashMap<>();
|
||||
dateFormats.put("F j, Y", "November 6, 2010 (F j, Y)");
|
||||
dateFormats.put("Y-m-d", "2010-11-06 (Y-m-d)");
|
||||
dateFormats.put("m/d/Y", "11/06/2010 (m/d/Y)");
|
||||
dateFormats.put("d/m/Y", "06/11/2010 (d/m/Y)");
|
||||
settingsManager.addSettingsField(page, "default",
|
||||
new SettingsField("date_format", "Date Format", "radio", dateFormats, ""));
|
||||
|
||||
// Register default values for some
|
||||
settingsManager.registerSetting(page, "site_name", "SIS Vietnam");
|
||||
settingsManager.registerSetting(page, "timezone", "UTC");
|
||||
settingsManager.registerSetting(page, "date_format", "F j, Y");
|
||||
|
||||
// --- Menu Registration for Modules ---
|
||||
menuManager.addMenuPage("HTML Snippets", "Snippets", "manage_options", "snippets", "fas fa-code", 30);
|
||||
}
|
||||
}
|
||||
+8
-1
@@ -79,7 +79,14 @@ public class DomainUserDetailsService implements UserDetailsService {
|
||||
}
|
||||
|
||||
public static UserWithId fromUser(User user) {
|
||||
List<GrantedAuthority> grantedAuthorities = Collections.singletonList(new SimpleGrantedAuthority(AuthoritiesConstants.PRE_AUTH_2FA));
|
||||
List<GrantedAuthority> grantedAuthorities;
|
||||
if (user.isUsing2FA()) {
|
||||
grantedAuthorities = Collections.singletonList(new SimpleGrantedAuthority(AuthoritiesConstants.PRE_AUTH_2FA));
|
||||
} else {
|
||||
grantedAuthorities = user.getAuthorities().stream()
|
||||
.map(authority -> (GrantedAuthority) new SimpleGrantedAuthority(authority.getName()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
return new UserWithId(
|
||||
user.getLogin(),
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.sisvietnamvn.web.security;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a single setting field in the Admin Settings API.
|
||||
*/
|
||||
public class SettingsField {
|
||||
private String id;
|
||||
private String title;
|
||||
private String type;
|
||||
private Map<String, String> options;
|
||||
private String description;
|
||||
|
||||
public SettingsField(String id, String title, String type, Map<String, String> options, String description) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.type = type;
|
||||
this.options = options;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Map<String, String> getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public void setOptions(Map<String, String> options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.sisvietnamvn.web.security;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a section of settings fields in the Admin Settings API.
|
||||
*/
|
||||
public class SettingsSection {
|
||||
private String id;
|
||||
private String title;
|
||||
private String description;
|
||||
private List<SettingsField> fields;
|
||||
|
||||
public SettingsSection(String id, String title, String description) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.fields = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void addField(SettingsField field) {
|
||||
this.fields.add(field);
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public List<SettingsField> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public void setFields(List<SettingsField> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.sisvietnamvn.web.service;
|
||||
|
||||
import com.sisvietnamvn.web.domain.HtmlSnippet;
|
||||
import com.sisvietnamvn.web.repository.HtmlSnippetRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Service for managing HtmlSnippets and providing them to Thymeleaf.
|
||||
*/
|
||||
@Service("snippetService") // Named bean so it can be called from Thymeleaf via @snippetService
|
||||
@Transactional
|
||||
public class HtmlSnippetService {
|
||||
|
||||
private final HtmlSnippetRepository snippetRepository;
|
||||
|
||||
public HtmlSnippetService(HtmlSnippetRepository snippetRepository) {
|
||||
this.snippetRepository = snippetRepository;
|
||||
}
|
||||
|
||||
public List<HtmlSnippet> findAll() {
|
||||
return snippetRepository.findAll();
|
||||
}
|
||||
|
||||
public Optional<HtmlSnippet> findById(Long id) {
|
||||
return snippetRepository.findById(id);
|
||||
}
|
||||
|
||||
public HtmlSnippet save(HtmlSnippet snippet) {
|
||||
return snippetRepository.save(snippet);
|
||||
}
|
||||
|
||||
public void deleteById(Long id) {
|
||||
snippetRepository.deleteById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content of a snippet by slug.
|
||||
* If the snippet is not found or is inactive, returns an empty string.
|
||||
* Can be called in Thymeleaf using: ${@snippetService.getSnippetContent('slug')}
|
||||
*
|
||||
* @param slug the unique slug of the snippet
|
||||
* @return the HTML content or empty string
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public String getSnippetContent(String slug) {
|
||||
if (slug == null || slug.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return snippetRepository.findBySlug(slug)
|
||||
.filter(HtmlSnippet::isActive)
|
||||
.map(HtmlSnippet::getContent)
|
||||
.orElse("");
|
||||
}
|
||||
}
|
||||
@@ -203,6 +203,7 @@ public class UserService {
|
||||
user.setImageUrl(userDTO.getImageUrl());
|
||||
user.setActivated(userDTO.isActivated());
|
||||
user.setLangKey(userDTO.getLangKey());
|
||||
user.setUsing2FA(userDTO.isUsing2FA());
|
||||
Set<Authority> managedAuthorities = user.getAuthorities();
|
||||
managedAuthorities.clear();
|
||||
userDTO
|
||||
|
||||
@@ -53,6 +53,8 @@ public class AdminUserDTO implements Serializable {
|
||||
|
||||
private Set<String> authorities;
|
||||
|
||||
private boolean using2FA = false;
|
||||
|
||||
public AdminUserDTO() {
|
||||
// Empty constructor needed for Jackson.
|
||||
}
|
||||
@@ -71,6 +73,7 @@ public class AdminUserDTO implements Serializable {
|
||||
this.lastModifiedBy = user.getLastModifiedBy();
|
||||
this.lastModifiedDate = user.getLastModifiedDate();
|
||||
this.authorities = user.getAuthorities().stream().map(Authority::getName).collect(Collectors.toSet());
|
||||
this.using2FA = user.isUsing2FA();
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
@@ -177,6 +180,14 @@ public class AdminUserDTO implements Serializable {
|
||||
this.authorities = authorities;
|
||||
}
|
||||
|
||||
public boolean isUsing2FA() {
|
||||
return using2FA;
|
||||
}
|
||||
|
||||
public void setUsing2FA(boolean using2FA) {
|
||||
this.using2FA = using2FA;
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
Reference in New Issue
Block a user