All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.xlrit.gears.server.frontend.FrontendController Maven / Gradle / Ivy

The newest version!
package com.xlrit.gears.server.frontend;

import java.util.Objects;

import com.xlrit.gears.server.security.AuthProperties;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Preconditions;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import static java.util.Objects.requireNonNullElse;
import static org.springframework.http.MediaType.*;

@Controller
public class FrontendController {
    private static final Logger LOG = LoggerFactory.getLogger(FrontendController.class);
    private static final String REACT_PREFIX = "REACT_APP_";
    private static final String IMAGE_SVG_VALUE = "image/svg+xml";
    private static final MediaType IMAGE_SVG = MediaType.valueOf(IMAGE_SVG_VALUE);

    private final ObjectMapper objectMapper;
    private final ObjectNode configNode;

    public FrontendController(
        FrontendProperties frontendProperties,
        AuthProperties authProperties,
        ObjectMapper objectMapper
    ) {
        this.objectMapper = objectMapper;
        this.configNode = objectMapper.createObjectNode();

        copy(configNode, "TITLE",         requireNonNullElse(frontendProperties.getTitle(), frontendProperties.getName())); // "name" was renamed to "title"
        copy(configNode, "THEME",         frontendProperties.getTheme());
        copy(configNode, "ROUTER_MODE",   frontendProperties.getRouterMode());
        copy(configNode, "ENABLE_HTML",   frontendProperties.isEnableHtml());
        copy(configNode, "LANGS_ENABLED", frontendProperties.getLanguagesAsString());
        copy(configNode, "AUTH_MODE",     authProperties.getMode());

        if ("external".equals(authProperties.getMode())) {
            copy(configNode, "AUTH_CLIENT_ID",      authProperties.getExternal().getClientId());
            copy(configNode, "AUTH_AUTHORITY",      authProperties.getExternal().getAuthority());
            copy(configNode, "AUTH_REDIRECT_URI",   authProperties.getExternal().getRedirectUri());
            copy(configNode, "AUTH_CACHE_LOCATION", authProperties.getExternal().getCacheLocation());
        }

        if (frontendProperties.getAdditional() != null) {
            frontendProperties.getAdditional().forEach((name, value) -> {
                copy(configNode, name, value);
            });
        }

        LOG.info("Frontend configuration: {}", configNode);
    }

    // see https://stackoverflow.com/questions/47689971/how-to-work-with-react-routers-and-spring-boot-controller
    @RequestMapping(value = { "/", "/gears", "/gears/**" })
    public String getIndex(HttpServletRequest request) {
        LOG.debug("getIndex: requestURI={}", request.getRequestURI());
        return "/index.html";
    }

    @GetMapping(value = "/config.json", produces = "application/json")
    @ResponseBody
    public JsonNode configJson() {
        return configNode;
    }

    @GetMapping(value = "/config.js", produces = "text/javascript")
    @ResponseBody
    public String configJs() throws JsonProcessingException {
        return "window._env=" + objectMapper.writeValueAsString(configNode);
    }

    @GetMapping(value = "/menu.json", produces = "application/json")
    @ResponseBody
    public Resource menuJson() {
        return new ClassPathResource("menu.json");
    }

    @GetMapping(value = "/theme.json", produces = "application/json")
    @ResponseBody
    public Resource themeJson() {
        return new ClassPathResource("theme.json");
    }

    @GetMapping(value = "/logos/{filename}", produces = { IMAGE_JPEG_VALUE, IMAGE_PNG_VALUE, IMAGE_SVG_VALUE })
    @ResponseBody
    public ResponseEntity logo(@PathVariable String filename) {
        Preconditions.checkNotNull(filename, "Filename is required");
        Preconditions.checkArgument(!filename.contains("/"), "Invalid filename");
        ClassPathResource resource = new ClassPathResource("logos/" + filename);
        return ResponseEntity.ok()
            .contentType(deriveMediaType(filename))
            .body(resource);
    }

    @GetMapping(value = "/icons/{filename}", produces = { IMAGE_JPEG_VALUE, IMAGE_PNG_VALUE, IMAGE_SVG_VALUE })
    @ResponseBody
    public ResponseEntity icon(@PathVariable String filename) {
        Preconditions.checkNotNull(filename, "Filename is required");
        Preconditions.checkArgument(!filename.contains("/"), "Invalid filename");
        ClassPathResource resource = new ClassPathResource("icons/" + filename);
        return ResponseEntity.ok()
            .contentType(deriveMediaType(filename))
            .body(resource);
    }

    private MediaType deriveMediaType(String filename) {
        String extension = StringUtils.getFilenameExtension(filename.toLowerCase());
        return switch (Objects.requireNonNull(extension)) {
            case "jpg" -> IMAGE_JPEG;
            case "png" -> IMAGE_PNG;
            case "svg" -> IMAGE_SVG;
            default    -> throw new IllegalArgumentException("Unsupported extension '" + extension + "'");
        };
    }

    private static void copy(ObjectNode configNode, String key, String value) {
        if (value != null) configNode.put(REACT_PREFIX + key, value);
    }

    private static void copy(ObjectNode configNode, String key, boolean value) {
        // the Frontend expects all values to be strings
        configNode.put(REACT_PREFIX + key, String.valueOf(value));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy